Skip to content

Commit

Permalink
App added
Browse files Browse the repository at this point in the history
  • Loading branch information
demenskyi committed Jun 12, 2024
1 parent 0f3dd30 commit 5a46e4d
Show file tree
Hide file tree
Showing 31 changed files with 33,861 additions and 362 deletions.
7 changes: 7 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DB_HOST=""
DB_USER=""
DB_PASSWORD=""
DB_NAME=""
OPENAI_API_KEY=""

# .env.local
55 changes: 54 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,56 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "prettier"],
"plugins": ["import", "prettier"],
"rules": {
"prettier/prettier": "warn",
"import/order": [
"error",
{
"groups": ["builtin", "external", "type", "internal", "parent", "sibling", "index"],
"pathGroups": [
{
"pattern": "@/types",
"group": "internal",
"position": "before"
},
{
"pattern": "@/constants",
"group": "internal",
"position": "before"
},
{
"pattern": "@/actions/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/hooks/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/lib/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/components/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@/**/*",
"group": "internal"
}
],
"newlines-between": "always",
"distinctGroup": false,
"alphabetize": {
"order": "asc",
"caseInsensitive": true
},
"warnOnUnassignedImports": true
}
]
}
}
15 changes: 15 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"jsxSingleQuote": false,
"printWidth": 112,
"quoteProps": "consistent",
"semi": true,
"singleAttributePerLine": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"plugins": ["prettier-plugin-tailwindcss"]
}
44 changes: 15 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,22 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
# GenAI App Example

## Getting Started
**Attention**: The code in this repository is intended for experimental use only and is not fully tested, documented, or supported by SingleStore. Visit the [SingleStore Forums](https://www.singlestore.com/forum/) to ask questions about this repository.

First, run the development server:
## Getting started

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
1. Create a database
2. Create a `.env.local` file based on the `.env.local.example` file
3. Install dependencies by running: `npm i`
4. Insert data into the database by running: `npx dotenv -e .env.local -- npx tsx setup-db.ts`
5. Build the app by running: `npm run build`

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Run the dev environment

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
1. Run `npm run dev`
2. Open [http://localhost:3000](http://localhost:3000)

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Build and run the prod environment

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
1. Run `npm run build`
2. Run `npm run start`
3. Open [http://localhost:3000](http://localhost:3000)
121 changes: 121 additions & 0 deletions actions/submit-chat-message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use server";

import { createStreamableUI, createStreamableValue } from "ai/rsc";
import { nanoid } from "nanoid";
import { createElement } from "react";
import { zodToJsonSchema } from "zod-to-json-schema";

import { ChatMessage } from "@/types";
import { db } from "@/lib/db";
import { llm } from "@/lib/llm";
import { llmTools } from "@/lib/llm-tools";

// Helper function to insert a message into the db
function insertChatMessage({ role, content }: Pick<ChatMessage, "role" | "content">) {
return db.controllers.insertOne({
collection: "chat_messages",
value: { role, content: JSON.stringify(content), created_at: new Date().getTime() },
});
}

export async function submitChatMessage(content: string) {
// The text stream that is used to stream the LLM text response to the client
const textStream: ReturnType<typeof createStreamableValue<string>> = createStreamableValue("");

// The initial LLM response message
const message: ChatMessage = {
id: nanoid(),
role: "assistant",
content: textStream.value,
createdAt: new Date(),
};

// The node steram that is used to stream React nodes to the client
const nodeStream = createStreamableUI();

(async () => {
try {
const [completionStream] = await Promise.all([
// The create chat completion call with tools that returns a completion steram
llm.chat.completions.create({
model: "gpt-4o",
temperature: 0,
stream: true,
messages: [
{ role: "system", content: "You are an assistant" },
{ role: "user", content },
],
// The tools normalization for the llm accepted format
tools: Object.values(llmTools).map(({ name, description, schema }) => ({
type: "function",
function: { name, description, parameters: zodToJsonSchema(schema) },
})),
}),

// The user message inserting
insertChatMessage({ role: "user", content }),
]);

// The pasered tool name that should be called
let completionToolName = "";
// The pasered tool args that should be provided to a tool call function
let completionToolArgs = "";
// The simple text response
let completionContent = "";

// The completion stream chunking
for await (const chunk of completionStream) {
const tool = chunk.choices[0].delta.tool_calls?.[0]?.function;
const textToken = chunk.choices[0].delta.content || "";

// Assigning tool-related data
if (tool) {
if (tool?.name) completionToolName = tool.name;
if (tool?.arguments) completionToolArgs += tool.arguments;
}

// Updating the textStream on the new text response
if (textToken) {
completionContent += textToken;
textStream.update(textToken);
}
}

await Promise.all([
// Inserting a message with the completion content into the db
(async () => {
if (!completionContent) return;
return insertChatMessage({
role: "assistant",
content: JSON.stringify(completionContent),
});
})(),

// Calls the tool provided by the LLM and updates the nodeStream with the new React node
(async () => {
if (!completionToolName) return;
const tool = llmTools[completionToolName as keyof typeof llmTools];
if (!tool) return;
const args = JSON.parse(completionToolArgs);
const result = await tool.call(args);
const node = result.props ? createElement(tool.node, result.props) : undefined;

await Promise.all([
nodeStream.update(node),
insertChatMessage({
role: "function",
content: JSON.stringify(result),
}),
]);
})(),
]);
} catch (error) {
console.error(error);
}

textStream.done();
nodeStream.done();
})();

return { ...message, node: nodeStream.value };
}
87 changes: 65 additions & 22 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,75 @@
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
@layer base {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;

--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;

--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;

--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;

--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;

--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;

--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;

--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;

--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;

--radius: 0.5rem;
}
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;

--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;

--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;

--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;

--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;

--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;

--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;

--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;

--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}

@layer utilities {
.text-balance {
text-wrap: balance;
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
13 changes: 9 additions & 4 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";

import type { Metadata } from "next";

import { StoreProdiver } from "@/components/store-provider";

import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "GenAI App Example",
};

export default function RootLayout({
Expand All @@ -16,7 +19,9 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<body className={inter.className}>
<StoreProdiver>{children}</StoreProdiver>
</body>
</html>
);
}
Loading

0 comments on commit 5a46e4d

Please sign in to comment.