From d49b2aa2c312306573ba18b1950de5267f0ee98e Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 10 Mar 2023 01:01:40 +0800 Subject: [PATCH] feat: add basic ui --- .vscode/settings.json | 4 + app/api/chat/.gitignore | 1 + app/api/chat/route.ts | 35 + app/api/chat/typing.ts | 7 + app/components/button.module.css | 35 + app/components/button.tsx | 25 + app/components/home.module.css | 222 ++ app/components/home.tsx | 177 ++ app/globals.css | 126 +- app/icons/add.svg | 23 + app/icons/bot.svg | 28 + app/icons/brain.svg | 25 + app/icons/chat.svg | 27 + app/icons/chatgpt.svg | 16 + app/icons/export.svg | 24 + app/icons/github.svg | 29 + app/icons/send-white.svg | 21 + app/icons/settings.svg | 21 + app/icons/user.svg | 34 + app/layout.tsx | 12 +- app/page.module.css | 271 --- app/page.tsx | 92 +- app/store.ts | 40 + next.config.js | 12 +- package.json | 2 + yarn.lock | 3654 ++++++++++++++++++++++++++++++ 26 files changed, 4500 insertions(+), 463 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 app/api/chat/.gitignore create mode 100644 app/api/chat/route.ts create mode 100644 app/api/chat/typing.ts create mode 100644 app/components/button.module.css create mode 100644 app/components/button.tsx create mode 100644 app/components/home.module.css create mode 100644 app/components/home.tsx create mode 100644 app/icons/add.svg create mode 100644 app/icons/bot.svg create mode 100644 app/icons/brain.svg create mode 100644 app/icons/chat.svg create mode 100644 app/icons/chatgpt.svg create mode 100644 app/icons/export.svg create mode 100644 app/icons/github.svg create mode 100644 app/icons/send-white.svg create mode 100644 app/icons/settings.svg create mode 100644 app/icons/user.svg delete mode 100644 app/page.module.css create mode 100644 app/store.ts create mode 100644 yarn.lock diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..bd3337f9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "typescript.tsdk": "node_modules\\typescript\\lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} \ No newline at end of file diff --git a/app/api/chat/.gitignore b/app/api/chat/.gitignore new file mode 100644 index 00000000..1b8afd08 --- /dev/null +++ b/app/api/chat/.gitignore @@ -0,0 +1 @@ +config.ts \ No newline at end of file diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts new file mode 100644 index 00000000..df43a3fe --- /dev/null +++ b/app/api/chat/route.ts @@ -0,0 +1,35 @@ +import { OpenAIApi, Configuration } from "openai"; +import { apiKey } from "./config"; + +// set up openai api client +const config = new Configuration({ + apiKey, +}); +const openai = new OpenAIApi(config); + +export async function GET(req: Request) { + try { + const completion = await openai.createChatCompletion( + { + messages: [ + { + role: "user", + content: "hello", + }, + ], + model: "gpt-3.5-turbo", + }, + { + proxy: { + protocol: "socks", + host: "127.0.0.1", + port: 7890, + }, + } + ); + + return new Response(JSON.stringify(completion.data)); + } catch (e) { + return new Response(JSON.stringify(e)); + } +} diff --git a/app/api/chat/typing.ts b/app/api/chat/typing.ts new file mode 100644 index 00000000..8c421846 --- /dev/null +++ b/app/api/chat/typing.ts @@ -0,0 +1,7 @@ +import type { + CreateChatCompletionRequest, + CreateChatCompletionResponse, +} from "openai"; + +export type ChatRequest = CreateChatCompletionRequest; +export type ChatReponse = CreateChatCompletionResponse; diff --git a/app/components/button.module.css b/app/components/button.module.css new file mode 100644 index 00000000..6f04a0b3 --- /dev/null +++ b/app/components/button.module.css @@ -0,0 +1,35 @@ +.icon-button { + background-color: var(--white); + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + padding: 10px; + + box-shadow: var(--card-shadow); + cursor: pointer; + transition: all .3s ease; + overflow: hidden; + user-select: none; +} + +.border { + border: var(--border-in-light); +} + +.icon-button:hover { + filter: brightness(0.9) hue-rotate(0.01turn); +} + +.icon-button-icon { + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; +} + +.icon-button-text { + margin-left: 5px; + font-size: 12px; +} \ No newline at end of file diff --git a/app/components/button.tsx b/app/components/button.tsx new file mode 100644 index 00000000..e45f8352 --- /dev/null +++ b/app/components/button.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +import styles from "./button.module.css"; + +export function IconButton(props: { + onClick?: () => void; + icon: JSX.Element; + text?: string; + bordered?: boolean; + className?: string; +}) { + return ( +
+
{props.icon}
+ {props.text && ( +
{props.text}
+ )} +
+ ); +} diff --git a/app/components/home.module.css b/app/components/home.module.css new file mode 100644 index 00000000..b03d5f40 --- /dev/null +++ b/app/components/home.module.css @@ -0,0 +1,222 @@ +.container { + max-width: 1080px; + max-height: 780px; + min-width: 600px; + width: 90vw; + height: 90vh; + background-color: var(--white); + border: var(--border-in-light); + border-radius: 20px; + box-shadow: var(--shadow); + + display: flex; + overflow: hidden; +} + +.sidebar { + max-width: 300px; + padding: 20px; + background-color: var(--second); + display: flex; + flex-direction: column; + box-shadow:inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05); +} + +.sidebar-header { + position: relative; + padding-top: 20px; + padding-bottom: 20px; +} + +.sidebar-logo { + position: absolute; + right: 0; + bottom: 18px; +} + +.sidebar-title { + font-size: 20px; + font-weight: bold; +} + +.sidebar-sub-title { + font-size: 12px; + font-weight: 400px; +} + +.sidebar-body { + flex: 1; + overflow: auto; +} + +.chat-list { + width: 260px; +} + +.chat-item { + padding: 10px 14px; + background-color: var(--white); + border-radius: 10px; + margin-bottom: 10px; + box-shadow: var(--card-shadow); + transition: all .3s ease; + cursor: pointer; + user-select: none; +} + +.chat-item:hover { + background-color: var(--hover-color); +} + +.chat-item-selected { + border: 2px solid var(--primary); +} + +.chat-item-title { + font-size: 14px; + font-weight: bolder; +} + +.chat-item-info { + display: flex; + justify-content: space-between; + color: rgb(166, 166, 166); + font-size: 12px; + margin-top: 8px; +} + +.chat-item-count {} +.chat-item-date {} + +.sidebar-tail { + display: flex; + justify-content: space-between; + padding-top: 20px; +} + +.sidebar-actions { + display: inline-flex; +} +.sidebar-action:last-child { + margin-left: 15px; +} + +.chat { + display: flex; + flex-direction: column; + width: 100%; + position: relative; +} + +.chat-header { + padding: 14px 20px; + border-bottom: rgba(0, 0, 0, 0.1) 1px solid; + + display: flex; + justify-content: space-between; + align-items: center; +} +.chat-header-title { + font-size: 20px; + font-weight: bolder; +} +.chat-header-sub-title { + font-size: 14px; + margin-top: 5px; +} +.chat-actions { + display: inline-flex; +} +.chat-action-button { + margin-left: 10px; +} + +.chat-body { + flex: 1; + overflow: auto; + padding: 20px; + margin-bottom: 100px; +} +.chat-message { + display: flex; + flex-direction: row; +} + +.chat-message-reverse { + display: flex; + flex-direction: row-reverse; +} + +.chat-message-container { + width: 60%; + display: flex; + flex-direction: column; + align-items: flex-start; +} + +.chat-message-reverse > .chat-message-container { + align-items: flex-end; +} + +.chat-message-avtar {} +.chat-message-item { + border-radius: 10px; + background-color: rgba(0, 0, 0, 0.05); + padding: 10px; + font-size: 14px; + margin-top: 5px; + user-select: text; +} +.chat-message-actions{ + display: flex; + flex-direction: row-reverse; + width: 100%; + padding: 5px 10px; + box-sizing: border-box; +} +.chat-message-action-date{ + font-size: 12px; + color: #aaa; +} +.chat-message-action-button{} + +.chat-input-panel { + position: absolute; + bottom: 20px; + display: flex; + width: 100%; + padding: 20px; + box-sizing: border-box; +} + +.chat-input-panel-inner { + display: flex; + flex: 1; +} + +.chat-input-panel-multi {} +.chat-input { + height: 100%; + width: 100%; + border-radius: 10px; + border: var(--border-in-light); + box-shadow: var(--card-shadow); + font-family: inherit; + padding: 10px 14px; + resize: none; + outline: none; + color: #333; +} + +.chat-input:focus { + border: 1px solid var(--primary); +} + +.chat-input-send{ + background-color: var(--primary); + color: white; + + position: absolute; + right: 30px; + bottom: 10px; +} \ No newline at end of file diff --git a/app/components/home.tsx b/app/components/home.tsx new file mode 100644 index 00000000..6c92f875 --- /dev/null +++ b/app/components/home.tsx @@ -0,0 +1,177 @@ +"use client"; + +import { IconButton } from "./button"; +import styles from "./home.module.css"; + +import SettingsIcon from "../icons/settings.svg"; +import GithubIcon from "../icons/github.svg"; +import ChatGptIcon from "../icons/chatgpt.svg"; +import SendWhiteIcon from "../icons/send-white.svg"; +import BrainIcon from "../icons/brain.svg"; +import ExportIcon from "../icons/export.svg"; +import BotIcon from "../icons/bot.svg"; +import UserIcon from "../icons/user.svg"; +import AddIcon from "../icons/add.svg"; + +export function ChatItem(props: { + onClick?: () => void; + title: string; + count: number; + time: string; + selected: boolean; +}) { + return ( +
+
{props.title}
+
+
{props.count} 条对话
+
{props.time}
+
+
+ ); +} + +export function ChatList() { + const listData = new Array(5).fill({ + title: "这是一个标题", + count: 10, + time: new Date().toLocaleString(), + }); + + const selectedIndex = 0; + + return ( +
+ {listData.map((item, i) => ( + + ))} +
+ ); +} + +export function Chat() { + const messages = [ + { + role: "user", + content: "这是一条消息", + date: new Date().toLocaleString(), + }, + { + role: "bot", + content: "这是一条回复".repeat(10), + date: new Date().toLocaleString(), + }, + ]; + + const title = "这是一个标题"; + const count = 10; + + return ( +
+
+
+
{title}
+
+ 与 ChatGPT 的 {count} 条对话 +
+
+
+
+ } bordered /> +
+
+ } bordered /> +
+
+
+ +
+ {messages.map((message, i) => { + const isUser = message.role === "user"; + + return ( +
+
+
+ {message.role === "user" ? : } +
+
+ {message.content} +
+ {!isUser && ( +
+
+ {message.date} +
+
+ )} +
+
+ ); + })} +
+ +
+
+