|
@@ -0,0 +1,313 @@
|
|
|
|
+# chat-client-new
|
|
|
|
+
|
|
|
|
+自修改版chat源码
|
|
|
|
+
|
|
|
|
+## 一、构建步骤
|
|
|
|
+### 1、创建测试路由,由于整个项目是需要用到路由的,所以直接先构建路由
|
|
|
|
+#### 1.1 新建路由文件
|
|
|
|
+```tsx
|
|
|
|
+// 创建routes文件夹,并新建index.tsx文件
|
|
|
|
+import {createBrowserRouter} from 'react-router-dom'
|
|
|
|
+
|
|
|
|
+function Test() {
|
|
|
|
+ return (<div>Hello World</div>)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function TestA() {
|
|
|
|
+ return (<div>Hello Hello</div>)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export const router = createBrowserRouter([
|
|
|
|
+ {
|
|
|
|
+ path: '/',
|
|
|
|
+ element: <Test/>
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ path: '/hello',
|
|
|
|
+ element: <TestA/>
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+]);
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+#### 1.2 在App.tsx中使用路由
|
|
|
|
+```tsx
|
|
|
|
+import {RouterProvider} from "react-router-dom";
|
|
|
|
+import {router} from "./routes";
|
|
|
|
+
|
|
|
|
+function App() {
|
|
|
|
+ return (
|
|
|
|
+ <RouterProvider router={router}/>
|
|
|
|
+ )
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export default App
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+### 2、创建测试布局
|
|
|
|
+```tsx
|
|
|
|
+// layouts/Chat.tsx
|
|
|
|
+export default function Chat() {
|
|
|
|
+ return <div>Chat</div>
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// layouts/service.ts.ts
|
|
|
|
+export {default as ChatLayout} from './Chat.tsx'
|
|
|
|
+
|
|
|
|
+// routes/service.ts.tsx
|
|
|
|
+export const router = createBrowserRouter([
|
|
|
|
+ // 新增
|
|
|
|
+ {
|
|
|
|
+ path: 'chat/:conversationId?',
|
|
|
|
+ element: <ChatLayout/>
|
|
|
|
+ }
|
|
|
|
+]);
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+### 3、创建正式聊天布局
|
|
|
|
+#### 3.1 创建正式主布局RootLayout
|
|
|
|
+简单实现布局,左侧菜单,右侧<Outlet>主要读取子布局,这里的话会读取ChatView
|
|
|
|
+```tsx
|
|
|
|
+import {Outlet} from "react-router-dom";
|
|
|
|
+import {Nav} from "../components/Nav";
|
|
|
|
+
|
|
|
|
+export default function Root() {
|
|
|
|
+ return (
|
|
|
|
+ <>
|
|
|
|
+ <div className="flex h-screen">
|
|
|
|
+ <Nav/>
|
|
|
|
+ <div className="flex h-full w-full flex-1 flex-col bg-gray-50">
|
|
|
|
+ <div
|
|
|
|
+ className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden bg-white pt-10 dark:bg-gray-800 md:pt-0">
|
|
|
|
+ <Outlet/>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </>
|
|
|
|
+ )
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+#### 3.2、创建正式的聊天页面ChatView
|
|
|
|
+简单实现聊天页面,暂时只加了信息展示区域
|
|
|
|
+```tsx
|
|
|
|
+import Messages from "../components/Messages/Messages.tsx";
|
|
|
|
+
|
|
|
|
+export default function Chat() {
|
|
|
|
+ return <>
|
|
|
|
+ <Messages/>
|
|
|
|
+ </>
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+#### 3.3 修改路由参数
|
|
|
|
+```tsx
|
|
|
|
+import {createBrowserRouter} from 'react-router-dom'
|
|
|
|
+import {RootLayout} from '../layouts'
|
|
|
|
+import {ChatView} from "../views";
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+export const router = createBrowserRouter([
|
|
|
|
+ {
|
|
|
|
+ path: '/',
|
|
|
|
+ element: <RootLayout/>,
|
|
|
|
+ children: [
|
|
|
|
+ {
|
|
|
|
+ path: 'chat/:conversationId?',
|
|
|
|
+ element: <ChatView/>
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+]);
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+### 4、添加请求功能
|
|
|
|
+#### 4.1 添加根节点
|
|
|
|
+```tsx
|
|
|
|
+import {RouterProvider} from "react-router-dom";
|
|
|
|
+import {router} from "./routes";
|
|
|
|
+import {QueryClient, QueryCache, QueryClientProvider} from "@tanstack/react-query";
|
|
|
|
+
|
|
|
|
+function App() {
|
|
|
|
+ const queryClient = new QueryClient({
|
|
|
|
+ queryCache: new QueryCache({
|
|
|
|
+ onError: (error: any) => {
|
|
|
|
+ if (error?.response?.status === 401) {
|
|
|
|
+ console.error(error)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ return (
|
|
|
|
+ <QueryClientProvider client={queryClient}>
|
|
|
|
+ <RouterProvider router={router}/>
|
|
|
|
+ </QueryClientProvider>
|
|
|
|
+ )
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export default App
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+#### 4.2 添加请求参数
|
|
|
|
+```tsx
|
|
|
|
+import {useQuery, QueryObserverResult} from "@tanstack/react-query";
|
|
|
|
+import * as t from '../types';
|
|
|
|
+import * as service from './service';
|
|
|
|
+
|
|
|
|
+export enum QueryKeys {
|
|
|
|
+ startupConfig = 'startupConfig'
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export const useGetStartupConfig = (): QueryObserverResult<t.TStartupConfig> => {
|
|
|
|
+ return useQuery<t.TStartupConfig>(
|
|
|
|
+ [QueryKeys.startupConfig],
|
|
|
|
+ () => service.getStartupConfig(),
|
|
|
|
+ {
|
|
|
|
+ refetchOnWindowFocus: false,
|
|
|
|
+ refetchOnReconnect: false,
|
|
|
|
+ refetchOnMount: false,
|
|
|
|
+ },
|
|
|
|
+ );
|
|
|
|
+};
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+```tsx
|
|
|
|
+import * as t from '../types';
|
|
|
|
+import request from './request';
|
|
|
|
+import * as endpoints from './endpoints'
|
|
|
|
+
|
|
|
|
+export const getStartupConfig = (): Promise<t.TStartupConfig> => {
|
|
|
|
+ return request.get(endpoints.config())
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+``` tsx
|
|
|
|
+export const config = () => {
|
|
|
|
+ return '/api/config';
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+### 5、添加状态管理器
|
|
|
|
+#### 5.1 设置 recoil 根节点
|
|
|
|
+```tsx
|
|
|
|
+import {RecoilRoot} from 'recoil';
|
|
|
|
+import {RouterProvider} from "react-router-dom";
|
|
|
|
+import {router} from "./routes";
|
|
|
|
+import {QueryClient, QueryCache, QueryClientProvider} from "@tanstack/react-query";
|
|
|
|
+
|
|
|
|
+function App() {
|
|
|
|
+ const queryClient = new QueryClient({
|
|
|
|
+ queryCache: new QueryCache({
|
|
|
|
+ onError: (error: any) => {
|
|
|
|
+ if (error?.response?.status === 401) {
|
|
|
|
+ console.error(error)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ return (
|
|
|
|
+ <QueryClientProvider client={queryClient}>
|
|
|
|
+ <RecoilRoot>
|
|
|
|
+ <RouterProvider router={router}/>
|
|
|
|
+ </RecoilRoot>
|
|
|
|
+ </QueryClientProvider>
|
|
|
|
+ )
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export default App
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+#### 5.2 添加conversation存储
|
|
|
|
+```tsx
|
|
|
|
+import {atom} from 'recoil';
|
|
|
|
+import {TConversation} from "../data-provider/types";
|
|
|
|
+const conversation = atom<TConversation | null>({key: 'conversation', default: null})
|
|
|
|
+
|
|
|
|
+export default {
|
|
|
|
+ conversation,
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+#### 5.3 在页面中调用
|
|
|
|
+```tsx
|
|
|
|
+import {useRecoilState} from "recoil";
|
|
|
|
+
|
|
|
|
+export default function ChatView() {
|
|
|
|
+ const [conversation] = useRecoilState(store.conversation)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+## 二、依赖库
|
|
|
|
+### 1、Tailwind CSS
|
|
|
|
+#### 1.1 安装Tailwind CSS支持
|
|
|
|
+```shell
|
|
|
|
+yarn add tailwindcss postcss autoprefixer
|
|
|
|
+
|
|
|
|
+# 初始化tailwind
|
|
|
|
+npx tailwindcss init
|
|
|
|
+```
|
|
|
|
+#### 1.2 创建postcss配置文件
|
|
|
|
+```javascript
|
|
|
|
+// 创建名为postcss.config.js
|
|
|
|
+module.exports = {
|
|
|
|
+ plugins: [
|
|
|
|
+ require('tailwindcss'),
|
|
|
|
+ require('autoprefixer')
|
|
|
|
+ ]
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+#### 1.3 创建名为styles.css
|
|
|
|
+```css
|
|
|
|
+@tailwind base;
|
|
|
|
+@tailwind components;
|
|
|
|
+@tailwind utilities;
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+### 2、zod
|
|
|
|
+TypeScript的强类型验证库
|
|
|
|
+
|
|
|
|
+## 三、样式说明
|
|
|
|
+### 1、布局
|
|
|
|
+flex : flex布局
|
|
|
|
+flex-col: 子元素垂直布局
|
|
|
|
+flex-1:
|
|
|
|
+### 2、尺寸
|
|
|
|
+h-screen 一屏高 (100vh)
|
|
|
|
+w-full 宽度填满(width:100%)
|
|
|
|
+h-full 高度填满(height:100%)
|
|
|
|
+
|
|
|
|
+## 四、数据说明
|
|
|
|
+### 1、conversation
|
|
|
|
+对话,也就左侧导航菜单栏里,一个对话可以包含很多条消息(message)
|
|
|
|
+``` tsx
|
|
|
|
+export const tConversationSchema = z.object({
|
|
|
|
+ conversationId: z.string().nullable(), // 对话ID,唯一标识
|
|
|
|
+ title: z.string(), // 对话名称
|
|
|
|
+ user: z.string().optional(), // 对话所属用户
|
|
|
|
+ endpoint: eModelEndpointSchema.nullable(),
|
|
|
|
+ createAt: z.string(), // 对话创建时间
|
|
|
|
+ updateAt: z.string() // 对话更新时间
|
|
|
|
+})
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+### 2、endpoint
|
|
|
|
+``` tsx
|
|
|
|
+import {z} from 'zod';
|
|
|
|
+import {openAISchema} from "./openai.ts";
|
|
|
|
+
|
|
|
|
+export enum EModelEndpoint {
|
|
|
|
+ azureOpenAI = 'azureOpenAI',
|
|
|
|
+ openAI = 'openAI',
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export const eModelEndpointSchema = z.nativeEnum(EModelEndpoint)
|
|
|
|
+
|
|
|
|
+type EndpointSchema =
|
|
|
|
+ | typeof openAISchema
|
|
|
|
+
|
|
|
|
+export const endpointSchemas: Record<EModelEndpoint, EndpointSchema> = {
|
|
|
|
+ openAI: openAISchema,
|
|
|
|
+ azureOpenAI: openAISchema,
|
|
|
|
+}
|
|
|
|
+```
|