Skip to content

Commit

Permalink
Merge pull request #2 from ubq-testing/ui
Browse files Browse the repository at this point in the history
feat: chatbot ui
  • Loading branch information
Keyrxng authored Oct 14, 2023
2 parents c07454d + fa35c16 commit 99dc6b8
Show file tree
Hide file tree
Showing 15 changed files with 376 additions and 1 deletion.
1 change: 1 addition & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OPENAI_API_KEY=xxxxxxx
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
1 change: 0 additions & 1 deletion README.md

This file was deleted.

99 changes: 99 additions & 0 deletions app/api/chat-with-functions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
OpenAIStream,
StreamingTextResponse,
experimental_StreamData,
} from 'ai';
import OpenAI from 'openai';
import type { ChatCompletionCreateParams } from 'openai/resources/chat';
// Create an OpenAI API client (that's edge friendly!)
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || '',
});

// IMPORTANT! Set the runtime to edge
export const runtime = 'edge';

const functions: ChatCompletionCreateParams.Function[] = [
{
name: 'get_current_weather',
description: 'Get the current weather.',
parameters: {
type: 'object',
properties: {
format: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'The temperature unit to use.',
},
},
required: ['format'],
},
},
{
name: 'eval_code_in_browser',
description: 'Execute javascript code in the browser with eval().',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: `Javascript code that will be directly executed via eval(). Do not use backticks in your response.
DO NOT include any newlines in your response, and be sure to provide only valid JSON when providing the arguments object.
The output of the eval() will be returned directly by the function.`,
},
},
required: ['code'],
},
},
];

export async function POST(req: Request) {
const { messages } = await req.json();

const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-0613',
stream: true,
messages,
functions,
});

const data = new experimental_StreamData();
const stream = OpenAIStream(response, {
experimental_onFunctionCall: async (
{ name, arguments: args },
createFunctionCallMessages,
) => {
if (name === 'get_current_weather') {
// Call a weather API here
const weatherData = {
temperature: 20,
unit: args.format === 'celsius' ? 'C' : 'F',
};

data.append({
text: 'Some custom data',
});

const newMessages = createFunctionCallMessages(weatherData);
return openai.chat.completions.create({
messages: [...messages, ...newMessages],
stream: true,
model: 'gpt-3.5-turbo-0613',
});
}
},
onCompletion(completion) {
console.log('completion', completion);
},
onFinal(completion) {
data.close();
},
experimental_streamData: true,
});

data.append({
text: 'Hello, how are you?',
});

return new StreamingTextResponse(stream, {}, data);
}
28 changes: 28 additions & 0 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// ./app/api/chat/route.ts
import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';

// Create an OpenAI API client (that's edge friendly!)
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || '',
});

// IMPORTANT! Set the runtime to edge
export const runtime = 'edge';

export async function POST(req: Request) {
// Extract the `prompt` from the body of the request
const { messages } = await req.json();

// Ask OpenAI for a streaming chat completion given the prompt
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages,
});

// Convert the response into a friendly text-stream
const stream = OpenAIStream(response);
// Respond with the stream
return new StreamingTextResponse(stream);
}
Binary file added app/favicon.ico
Binary file not shown.
76 changes: 76 additions & 0 deletions app/function-calling/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use client';

import { Message } from 'ai/react';
import { useChat } from 'ai/react';
import { ChatRequest, FunctionCallHandler, nanoid } from 'ai';

export default function Chat() {
const functionCallHandler: FunctionCallHandler = async (
chatMessages,
functionCall,
) => {
if (functionCall.name === 'eval_code_in_browser') {
if (functionCall.arguments) {
// Parsing here does not always work since it seems that some characters in generated code aren't escaped properly.
const parsedFunctionCallArguments: { code: string } = JSON.parse(
functionCall.arguments,
);
// WARNING: Do NOT do this in real-world applications!
eval(parsedFunctionCallArguments.code);
const functionResponse = {
messages: [
...chatMessages,
{
id: nanoid(),
name: 'eval_code_in_browser',
role: 'function' as const,
content: parsedFunctionCallArguments.code,
},
],
};
return functionResponse;
}
}
};

const { messages, input, handleInputChange, handleSubmit, data } = useChat({
api: '/api/chat-with-functions',
experimental_onFunctionCall: functionCallHandler,
});

// Generate a map of message role to text color
const roleToColorMap: Record<Message['role'], string> = {
system: 'red',
user: 'black',
function: 'blue',
assistant: 'green',
};

return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.length > 0
? messages.map((m: Message) => (
<div
key={m.id}
className="whitespace-pre-wrap"
style={{ color: roleToColorMap[m.role] }}
>
<strong>{`${m.role}: `}</strong>
{m.content || JSON.stringify(m.function_call)}
<br />
<br />
</div>
))
: null}
<div id="chart-goes-here"></div>
<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
3 changes: 3 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
21 changes: 21 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import './globals.css';
import { Inter } from 'next/font/google';

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

export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
28 changes: 28 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import { useChat } from 'ai/react';

export default function Chat() {
const { messages, input, handleInputChange, handleSubmit, data } = useChat();
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.length > 0
? messages.map(m => (
<div key={m.id} className="whitespace-pre-wrap">
{m.role === 'user' ? 'User: ' : 'AI: '}
{m.content}
</div>
))
: null}

<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
4 changes: 4 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

module.exports = nextConfig;
29 changes: 29 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "next-openai",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"ai": "2.2.15",
"next": "13.4.12",
"openai": "4.11.0",
"react": "18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^17.0.12",
"@types/react": "18.2.8",
"@types/react-dom": "18.2.4",
"autoprefixer": "^10.4.14",
"eslint": "^7.32.0",
"eslint-config-next": "13.4.12",
"postcss": "^8.4.23",
"tailwindcss": "^3.3.2",
"typescript": "5.1.3"
}
}
6 changes: 6 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
18 changes: 18 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
},
},
plugins: [],
};
28 changes: 28 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

0 comments on commit 99dc6b8

Please sign in to comment.