From 20765efe62f54927fee9a4e2606cca7a0ad648da Mon Sep 17 00:00:00 2001 From: kurihada Date: Fri, 12 Dec 2025 13:56:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E6=B7=BB=E5=8A=A0=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E5=BC=8F=E5=B8=83=E5=B1=80=E5=92=8C=20PWA=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现移动端响应式适配:抽屉式侧边栏、触摸优化、Safe Area 支持 - 添加 PWA 支持:vite-plugin-pwa、manifest、Service Worker 缓存 - 优化移动端输入体验:防缩放、最小触摸目标、键盘适配 --- packages/web/index.html | 18 +- packages/web/package.json | 9 +- packages/web/public/icon.svg | 22 + packages/web/public/manifest.json | 19 + packages/web/src/App.tsx | 74 +- packages/web/src/components/ChatInput.tsx | 26 +- packages/web/src/components/ConfigPanel.tsx | 53 +- packages/web/src/components/Sidebar.tsx | 168 +- packages/web/src/styles/index.css | 21 + packages/web/vite.config.ts | 48 +- pnpm-lock.yaml | 2798 ++++++++++++++++++- 11 files changed, 3141 insertions(+), 115 deletions(-) create mode 100644 packages/web/public/icon.svg create mode 100644 packages/web/public/manifest.json diff --git a/packages/web/index.html b/packages/web/index.html index 5eb2d77..856be3a 100644 --- a/packages/web/index.html +++ b/packages/web/index.html @@ -2,8 +2,22 @@ - - + + + + + + + + + + + + + + + + AI Assistant diff --git a/packages/web/package.json b/packages/web/package.json index 4be017e..1cf6fff 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -10,12 +10,12 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "clsx": "^2.1.0", + "lucide-react": "^0.344.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.22.0", - "zustand": "^4.5.0", - "lucide-react": "^0.344.0", - "clsx": "^2.1.0" + "zustand": "^4.5.0" }, "devDependencies": { "@types/react": "^18.3.0", @@ -25,6 +25,7 @@ "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "typescript": "^5.3.3", - "vite": "^5.1.0" + "vite": "^5.1.0", + "vite-plugin-pwa": "^1.2.0" } } diff --git a/packages/web/public/icon.svg b/packages/web/public/icon.svg new file mode 100644 index 0000000..04eef54 --- /dev/null +++ b/packages/web/public/icon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/public/manifest.json b/packages/web/public/manifest.json new file mode 100644 index 0000000..186003f --- /dev/null +++ b/packages/web/public/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "AI Terminal Assistant", + "short_name": "AI Assistant", + "description": "Your intelligent coding companion", + "theme_color": "#111827", + "background_color": "#111827", + "display": "standalone", + "orientation": "portrait-primary", + "scope": "/", + "start_url": "/", + "icons": [ + { + "src": "/icon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any maskable" + } + ] +} diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index 6530e4c..935b0c3 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -1,5 +1,7 @@ /** * App Component + * + * 响应式布局:支持桌面端和移动端 */ import { useState, useEffect } from 'react'; @@ -66,12 +68,12 @@ export function App() { onCreateSession={handleCreateSession} /> - {/* 工具栏按钮 */} -
+ {/* 工具栏按钮 - 移动端右移避开菜单按钮 */} +
{/* 配置按钮 */} - {/* 文件浏览器切换按钮 */} + {/* 文件浏览器切换按钮 - 移动端隐藏 */}
-
+ {/* 主内容区域 */} +
{/* 聊天区域 */} -
+
{currentSessionId ? ( ) : ( @@ -121,20 +124,61 @@ export function App() { )}
- {/* 文件浏览器 */} + {/* 文件浏览器 - 桌面端侧边栏,移动端全屏覆盖 */} {showFileBrowser && ( -
- { - console.log('Selected file:', path); - }} - /> -
+ <> + {/* 移动端: 全屏覆盖 */} +
+
+ Files + +
+
+ { + console.log('Selected file:', path); + }} + /> +
+
+ + {/* 桌面端: 侧边栏 */} +
+ { + console.log('Selected file:', path); + }} + /> +
+ )}
{/* 配置面板 */} {showConfig && setShowConfig(false)} />} + + {/* 移动端底部文件按钮 */} +
); } diff --git a/packages/web/src/components/ChatInput.tsx b/packages/web/src/components/ChatInput.tsx index af2bf1e..1189974 100644 --- a/packages/web/src/components/ChatInput.tsx +++ b/packages/web/src/components/ChatInput.tsx @@ -1,5 +1,7 @@ /** * Chat Input Component + * + * 响应式输入框:适配移动端键盘和触摸操作 */ import { useState, useRef, useEffect } from 'react'; @@ -22,7 +24,9 @@ export function ChatInput({ onSend, onCancel, isLoading, disabled }: ChatInputPr const textarea = textareaRef.current; if (textarea) { textarea.style.height = 'auto'; - textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`; + // 移动端最大高度稍小 + const maxHeight = window.innerWidth < 768 ? 120 : 200; + textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`; } }, [input]); @@ -40,6 +44,7 @@ export function ChatInput({ onSend, onCancel, isLoading, disabled }: ChatInputPr }; const handleKeyDown = (e: React.KeyboardEvent) => { + // 移动端 Enter 直接发送,桌面端 Shift+Enter 换行 if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(); @@ -47,7 +52,7 @@ export function ChatInput({ onSend, onCancel, isLoading, disabled }: ChatInputPr }; return ( -
+