From bed535219dea0d1e5ef9fd75a420c0d05020dcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E6=AC=A3=E6=84=89?= Date: Thu, 18 Jul 2024 01:29:21 +0800 Subject: [PATCH] feat: Integrated Alibaba Cloud's BaiLian model (#67) --- .env.development | 4 + .gitignore | 6 +- next.config.mjs | 30 +++ package.json | 1 + pnpm-lock.yaml | 66 ++++++ public/qwen.png | Bin 0 -> 7493 bytes src/app/edit/(main)/(router)/ai/page.tsx | 8 +- src/app/edit/layout.tsx | 1 + src/components/ai/chat/chat-bottombar.tsx | 85 ++++++++ src/components/ai/chat/chat-layout.tsx | 12 ++ src/components/ai/chat/chat-list.tsx | 76 +++++++ src/components/ai/chat/chat-topbar.tsx | 31 +++ src/components/ai/chat/chat.tsx | 73 +++++++ src/components/ai/chat/data.tsx | 39 ++++ src/components/ai/chat/textarea.tsx | 23 +++ src/components/ai/chat/utils.ts | 6 + src/components/file/pendingFileItem/index.tsx | 40 ++++ .../modals/create-project-modal/index.tsx | 81 ++++++++ .../provider/modal-provider/index.tsx | 23 +++ src/components/ui/dialog/index.tsx | 104 ++++++++++ src/components/ui/input/index.tsx | 24 +++ src/hooks/useModal.ts | 23 +++ src/store/uploadFileDataStore.tsx | 195 ++++++++++++++++++ src/utils/getLocalDirectory.ts | 112 ++++++++++ 24 files changed, 1061 insertions(+), 2 deletions(-) create mode 100644 .env.development create mode 100644 public/qwen.png create mode 100644 src/components/ai/chat/chat-bottombar.tsx create mode 100644 src/components/ai/chat/chat-layout.tsx create mode 100644 src/components/ai/chat/chat-list.tsx create mode 100644 src/components/ai/chat/chat-topbar.tsx create mode 100644 src/components/ai/chat/chat.tsx create mode 100644 src/components/ai/chat/data.tsx create mode 100644 src/components/ai/chat/textarea.tsx create mode 100644 src/components/ai/chat/utils.ts create mode 100644 src/components/file/pendingFileItem/index.tsx create mode 100644 src/components/modals/create-project-modal/index.tsx create mode 100644 src/components/provider/modal-provider/index.tsx create mode 100644 src/components/ui/dialog/index.tsx create mode 100644 src/components/ui/input/index.tsx create mode 100644 src/hooks/useModal.ts create mode 100644 src/store/uploadFileDataStore.tsx create mode 100644 src/utils/getLocalDirectory.ts diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..a9139d8 --- /dev/null +++ b/.env.development @@ -0,0 +1,4 @@ +MODEL_API_BASE_URL=https://dashscope.aliyuncs.com/api/v1/apps/ + +QWEN_APP_ID= +QWEN_AUTH= diff --git a/.gitignore b/.gitignore index a112a9b..cc9c517 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,8 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -*storybook.log \ No newline at end of file +*storybook.log + +/.idea/ + +/.vscode/ diff --git a/next.config.mjs b/next.config.mjs index 3cb2d9b..d4f818b 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -5,6 +5,36 @@ const nextConfig = { eslint: { ignoreDuringBuilds: true, }, + compress: false, // 禁用gzip压缩 + async rewrites() { + return [ + { + source: '/ai/:path*', + destination: `${process.env.MODEL_API_BASE_URL}${process.env.QWEN_APP_ID}/:path*`, // 你想要代理的外部 API + }, + ]; + }, + async headers() { + return [ + { + source: '/ai/:path*', + headers: [ + { + key: 'Content-Type', + value: 'application/json', + }, + { + key: 'Authorization', + value: `Bearer ${process.env.QWEN_AUTH}`, + }, + { + key: 'Accept', + value: `text/event-stream`, + }, + ], + }, + ] + }, }; export default withNextDevtools(nextConfig); diff --git a/package.json b/package.json index 9f2fdbb..30436bf 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@giscus/react": "^3.0.0", + "@microsoft/fetch-event-source": "^2.0.1", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-avatar": "^1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fca135..7d615d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@giscus/react': specifier: ^3.0.0 version: 3.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@microsoft/fetch-event-source': + specifier: ^2.0.1 + version: 2.0.1 '@monaco-editor/react': specifier: ^4.6.0 version: 4.6.0(monaco-editor@0.50.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -38,6 +41,9 @@ importers: '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-tooltip': + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -1526,6 +1532,9 @@ packages: '@types/react': '>=16' react: '>=16' + '@microsoft/fetch-event-source@2.0.1': + resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==} + '@monaco-editor/loader@1.4.0': resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} peerDependencies: @@ -1942,6 +1951,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-tooltip@1.1.2': + resolution: {integrity: sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -1996,6 +2018,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-visually-hidden@1.1.0': + resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} @@ -8479,6 +8514,8 @@ snapshots: '@types/react': 18.3.3 react: 18.3.1 + '@microsoft/fetch-event-source@2.0.1': {} + '@monaco-editor/loader@1.4.0(monaco-editor@0.50.0)': dependencies: monaco-editor: 0.50.0 @@ -8893,6 +8930,26 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-tooltip@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.3.1)': dependencies: react: 18.3.1 @@ -8933,6 +8990,15 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/rect@1.1.0': {} '@react-dev-inspector/babel-plugin@2.0.1': diff --git a/public/qwen.png b/public/qwen.png new file mode 100644 index 0000000000000000000000000000000000000000..fb655a94fecfaead8a4be401773f798140848f3c GIT binary patch literal 7493 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 P00000NkvXXu0mjf87WtO literal 0 HcmV?d00001 diff --git a/src/app/edit/(main)/(router)/ai/page.tsx b/src/app/edit/(main)/(router)/ai/page.tsx index 8393456..992bcff 100644 --- a/src/app/edit/(main)/(router)/ai/page.tsx +++ b/src/app/edit/(main)/(router)/ai/page.tsx @@ -1,7 +1,13 @@ import React from 'react'; +import { ChatLayout } from '@/components/ai/chat/chat-layout'; + const AI = () => { - return
AI
; + return ( +
+ +
+ ); }; export default AI; diff --git a/src/app/edit/layout.tsx b/src/app/edit/layout.tsx index 7218c30..8a7c3e4 100644 --- a/src/app/edit/layout.tsx +++ b/src/app/edit/layout.tsx @@ -154,6 +154,7 @@ const Page: React.FC<{ children: React.ReactNode }> = ({ children }) => { void; +} + +export const BottombarIcons = [{ icon: FileImage }, { icon: Paperclip }]; + +export default function ChatBottombar({ sendMessage }: ChatBottombarProps) { + const [message, setMessage] = useState(''); + const inputRef = useRef(null); + + const handleInputChange = (event: React.ChangeEvent) => { + setMessage(event.target.value); + }; + + const handleSend = () => { + if (message.trim()) { + const newMessage: Message = { + id: Date.now(), + name: loggedInUserData.name, + avatar: loggedInUserData.avatar, + sessionId: loggedInUserData.sessionId, + message: message.trim(), + }; + sendMessage(newMessage); + setMessage(''); + + if (inputRef.current) { + inputRef.current.focus(); + } + } + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + handleSend(); + } + + if (event.key === 'Enter' && event.shiftKey) { + event.preventDefault(); + setMessage((prev) => prev + '\n'); + } + }; + + return ( +
+ + + + + + +
+ ); +} diff --git a/src/components/ai/chat/chat-layout.tsx b/src/components/ai/chat/chat-layout.tsx new file mode 100644 index 0000000..f1dfe90 --- /dev/null +++ b/src/components/ai/chat/chat-layout.tsx @@ -0,0 +1,12 @@ +'use client'; + +import React from 'react'; + +import { userData } from './data'; +import { Chat } from './chat'; + +export function ChatLayout() { + const [selectedUser] = React.useState(userData[0]); + + return ; +} diff --git a/src/components/ai/chat/chat-list.tsx b/src/components/ai/chat/chat-list.tsx new file mode 100644 index 0000000..a9ee113 --- /dev/null +++ b/src/components/ai/chat/chat-list.tsx @@ -0,0 +1,76 @@ +import React, { useRef } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; + +import { Message, UserData } from './data'; +import { cn } from './utils'; +import ChatBottombar from './chat-bottombar'; + +import { Avatar, AvatarImage } from '@/components/ui/avatar/index'; + +interface ChatListProps { + messages?: Message[]; + selectedUser: UserData; + sendMessage: (newMessage: Message) => void; +} + +export function ChatList({ messages, selectedUser, sendMessage }: ChatListProps) { + const messagesContainerRef = useRef(null); + + React.useEffect(() => { + if (messagesContainerRef.current) { + messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight; + } + }, [messages]); + + return ( +
+
+ + {messages?.map((message, index) => ( + +
+ {message.name !== selectedUser.name && ( + + + + )} + {message.message} + {message.name === selectedUser.name && ( + + + + )} +
+
+ ))} +
+
+ +
+ ); +} diff --git a/src/components/ai/chat/chat-topbar.tsx b/src/components/ai/chat/chat-topbar.tsx new file mode 100644 index 0000000..ba8bb5c --- /dev/null +++ b/src/components/ai/chat/chat-topbar.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import { UserData } from './data'; + +import { Avatar, AvatarImage } from '@/components/ui/avatar/index'; + +interface ChatTopbarProps { + selectedUser: UserData; +} + +export default function ChatTopbar({ selectedUser }: ChatTopbarProps) { + return ( +
+
+ + + +
+ {selectedUser.name} + https://bailian.console.aliyun.com +
+
+
+ ); +} diff --git a/src/components/ai/chat/chat.tsx b/src/components/ai/chat/chat.tsx new file mode 100644 index 0000000..86c7eaa --- /dev/null +++ b/src/components/ai/chat/chat.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { fetchEventSource } from '@microsoft/fetch-event-source'; + +import { Message, type UserData } from './data'; +import ChatTopbar from './chat-topbar'; +import { ChatList } from './chat-list'; + +interface ChatProps { + messages?: Message[]; + selectedUser: UserData; +} + +export function Chat({ messages, selectedUser }: ChatProps) { + const [messagesState, setMessages] = React.useState(messages ?? []); + + const sendMessage = (newMessage: Message) => { + setMessages((prevMessages) => [...prevMessages, newMessage]); + }; + + const updateMessage = (id: number, updatedFields: Partial) => { + setMessages((prevMessages) => + prevMessages.map((message) => + message.id === id ? { ...message, ...updatedFields } : message, + ), + ); + }; + + const fetchAnswer = (prompt: Message) => { + sendMessage(prompt); + + console.log('prompt'); + console.log(JSON.stringify(prompt)); + + const answerMessage: Message = { + id: Date.now() + 1, + name: 'GPT', + avatar: '/qwen.png', + sessionId: prompt.sessionId, + message: '', + }; + console.log('messageNew'); + console.log(JSON.stringify(answerMessage)); + sendMessage(answerMessage); + + const ctrl = new AbortController(); + fetchEventSource(`/ai/completion`, { + method: 'POST', + body: JSON.stringify({ + input: { + prompt: prompt.message, + session_id: prompt.sessionId, + }, + }), + signal: ctrl.signal, + onmessage(event) { + console.log(event); + + const data = JSON.parse(event.data); + const updatedMessage = data.output.text; + console.log(updatedMessage); + updateMessage(answerMessage.id, { message: updatedMessage }); + }, + }); + }; + + return ( +
+ + + +
+ ); +} diff --git a/src/components/ai/chat/data.tsx b/src/components/ai/chat/data.tsx new file mode 100644 index 0000000..9b59122 --- /dev/null +++ b/src/components/ai/chat/data.tsx @@ -0,0 +1,39 @@ +export const userData: UserData[] = [ + { + id: 1, + avatar: 'https://github.com/shadcn.png', + messages: [], + name: '通义千问', + }, +]; + +export type UserData = { + id: number; + avatar: string; + messages: Message[]; + name: string; +}; + +export const loggedInUserData = { + id: 5, + avatar: 'https://github.com/shadcn.png', + name: '通义千问', + sessionId: Date.now(), +}; + +export type LoggedInUserData = typeof loggedInUserData; + +export interface Message { + id: number; + avatar: string; + name: string; + message: string; + sessionId: number; +} + +export interface User { + id: number; + avatar: string; + messages: Message[]; + name: string; +} diff --git a/src/components/ai/chat/textarea.tsx b/src/components/ai/chat/textarea.tsx new file mode 100644 index 0000000..6caf454 --- /dev/null +++ b/src/components/ai/chat/textarea.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; + +import { cn } from './utils'; + +export interface TextareaProps extends React.TextareaHTMLAttributes {} + +const Textarea = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +