@dyyz1993/create-agent 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/src/__tests__/cli.test.ts +2 -0
- package/src/__tests__/copy.test.ts +110 -122
- package/src/__tests__/create-args.test.ts +113 -0
- package/src/__tests__/templates.test.ts +29 -3
- package/src/__tests__/update.test.ts +129 -0
- package/src/cli.ts +30 -32
- package/src/commands/create.ts +83 -24
- package/src/commands/update.ts +174 -0
- package/src/commands/workspace.ts +26 -6
- package/src/lib/copy.ts +127 -91
- package/src/lib/templates.ts +12 -0
- package/templates/agent/.husky/commit-msg +4 -0
- package/templates/agent/.husky/pre-commit +11 -0
- package/templates/agent/.husky/pre-push +6 -0
- package/templates/agent/CHANGELOG.md +15 -0
- package/templates/agent/package.json +3 -3
- package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +53 -39
- package/templates/agent/src/mainview/App.tsx +14 -12
- package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +194 -190
- package/templates/agent/src/mainview/components/bash/BashPanel.tsx +210 -189
- package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +45 -20
- package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +41 -21
- package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +181 -114
- package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +80 -80
- package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +61 -58
- package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +43 -41
- package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +69 -50
- package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +31 -18
- package/templates/agent/src/mainview/components/git/GitPanel.tsx +731 -490
- package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +157 -139
- package/templates/agent/src/mainview/components/layout/AppLayout.tsx +210 -161
- package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +130 -122
- package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +119 -109
- package/templates/agent/src/mainview/components/search/SearchPanel.tsx +123 -117
- package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +64 -44
- package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +38 -38
- package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +40 -38
- package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +72 -72
- package/templates/agent/src/mainview/lib/i18n/locales/en.json +175 -155
- package/templates/agent/src/mainview/lib/i18n/locales/zh.json +173 -155
- package/templates/agent/src/mainview/stores/use-app-store.ts +96 -86
- package/templates/agent/src/mainview/stores/use-explorer-store.ts +292 -275
- package/templates/agent/src/mainview/types/index.ts +23 -22
- package/templates/agent/src/mainview/utils/file-icon.tsx +17 -21
- package/templates/agent/src/shared/handlers/chat.ts +53 -53
- package/templates/agent/src/shared/handlers/debug.ts +13 -3
- package/templates/browser-agent/.env.example +19 -0
- package/templates/browser-agent/.husky/commit-msg +4 -0
- package/templates/browser-agent/.husky/pre-commit +11 -0
- package/templates/browser-agent/.husky/pre-push +6 -0
- package/templates/browser-agent/.prettierignore +6 -0
- package/templates/browser-agent/.prettierrc +9 -0
- package/templates/browser-agent/AGENTS.md +396 -0
- package/templates/browser-agent/CHANGELOG.md +15 -0
- package/templates/browser-agent/LICENSE +21 -0
- package/templates/browser-agent/README.md +103 -0
- package/templates/browser-agent/commitlint.config.js +8 -0
- package/templates/browser-agent/electrobun.config.ts +27 -0
- package/templates/browser-agent/electron/main.js +46 -0
- package/templates/browser-agent/electron/preload.js +5 -0
- package/templates/browser-agent/electron-builder.json +40 -0
- package/templates/browser-agent/eslint.config.mjs +79 -0
- package/templates/browser-agent/llms.txt +24 -0
- package/templates/browser-agent/package.json +140 -0
- package/templates/browser-agent/postcss.config.js +6 -0
- package/templates/browser-agent/scripts/dev.ts +138 -0
- package/templates/browser-agent/src/__tests__/hybrid-mode.test.ts +126 -0
- package/templates/browser-agent/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/browser-agent/src/__tests__/server-config.test.ts +59 -0
- package/templates/browser-agent/src/bun/index.ts +130 -0
- package/templates/browser-agent/src/bun/three.d.ts +1 -0
- package/templates/browser-agent/src/gateway/http-routes.ts +1 -0
- package/templates/browser-agent/src/gateway/ipc-transport.ts +68 -0
- package/templates/browser-agent/src/gateway/sse-transport.ts +221 -0
- package/templates/browser-agent/src/mainview/App.tsx +45 -0
- package/templates/browser-agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
- package/templates/browser-agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
- package/templates/browser-agent/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
- package/templates/browser-agent/src/mainview/__tests__/setup.ts +40 -0
- package/templates/browser-agent/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
- package/templates/browser-agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
- package/templates/browser-agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/browser-agent/src/mainview/components/assets/AssetsPanel.tsx +139 -0
- package/templates/browser-agent/src/mainview/components/chat/ChatPanel.tsx +219 -0
- package/templates/browser-agent/src/mainview/components/chat/CommandBar.tsx +184 -0
- package/templates/browser-agent/src/mainview/components/chat/MessageBubble.tsx +249 -0
- package/templates/browser-agent/src/mainview/components/chat/ToolPicker.tsx +115 -0
- package/templates/browser-agent/src/mainview/components/common/ErrorBoundary.tsx +1 -0
- package/templates/browser-agent/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
- package/templates/browser-agent/src/mainview/components/common/ThemeToggle.tsx +45 -0
- package/templates/browser-agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
- package/templates/browser-agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
- package/templates/browser-agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
- package/templates/browser-agent/src/mainview/components/dev/NetworkPanel.tsx +155 -0
- package/templates/browser-agent/src/mainview/components/layout/AppLayout.tsx +311 -0
- package/templates/browser-agent/src/mainview/components/onboarding/SetupWizard.tsx +310 -0
- package/templates/browser-agent/src/mainview/components/process/ProcessPanel.tsx +329 -0
- package/templates/browser-agent/src/mainview/components/sidebar/SessionSidebar.tsx +122 -0
- package/templates/browser-agent/src/mainview/components/sidebar/SkillSidebar.tsx +115 -0
- package/templates/browser-agent/src/mainview/components/topbar/TopBar.tsx +350 -0
- package/templates/browser-agent/src/mainview/global.d.ts +13 -0
- package/templates/browser-agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +151 -0
- package/templates/browser-agent/src/mainview/hooks/use-agent-chat.ts +157 -0
- package/templates/browser-agent/src/mainview/hooks/use-breakpoint.ts +68 -0
- package/templates/browser-agent/src/mainview/hooks/use-rpc-init.ts +71 -0
- package/templates/browser-agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
- package/templates/browser-agent/src/mainview/index.css +71 -0
- package/templates/browser-agent/src/mainview/index.html +12 -0
- package/templates/browser-agent/src/mainview/lib/api-client.ts +397 -0
- package/templates/browser-agent/src/mainview/lib/i18n/index.ts +30 -0
- package/templates/browser-agent/src/mainview/lib/i18n/locales/en.json +130 -0
- package/templates/browser-agent/src/mainview/lib/i18n/locales/zh.json +128 -0
- package/templates/browser-agent/src/mainview/lib/network-bus.ts +92 -0
- package/templates/browser-agent/src/mainview/lib/rpc-cache.ts +94 -0
- package/templates/browser-agent/src/mainview/main.tsx +18 -0
- package/templates/browser-agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
- package/templates/browser-agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
- package/templates/browser-agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
- package/templates/browser-agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
- package/templates/browser-agent/src/mainview/stores/message-batcher.ts +25 -0
- package/templates/browser-agent/src/mainview/stores/use-asset-store.ts +65 -0
- package/templates/browser-agent/src/mainview/stores/use-chat-store.ts +200 -0
- package/templates/browser-agent/src/mainview/stores/use-connection-store.ts +173 -0
- package/templates/browser-agent/src/mainview/stores/use-locale-store.ts +27 -0
- package/templates/browser-agent/src/mainview/stores/use-log-store.ts +16 -0
- package/templates/browser-agent/src/mainview/stores/use-network-panel-store.ts +18 -0
- package/templates/browser-agent/src/mainview/stores/use-record-store.ts +147 -0
- package/templates/browser-agent/src/mainview/stores/use-session-store.ts +151 -0
- package/templates/browser-agent/src/mainview/stores/use-sidebar-store.ts +161 -0
- package/templates/browser-agent/src/mainview/stores/use-theme-store.ts +46 -0
- package/templates/browser-agent/src/mainview/stores/use-view-store.ts +20 -0
- package/templates/browser-agent/src/mainview/types/index.ts +6 -0
- package/templates/browser-agent/src/server-config.ts +45 -0
- package/templates/browser-agent/src/server.ts +78 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
- package/templates/browser-agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
- package/templates/browser-agent/src/shared/handlers/browser.ts +596 -0
- package/templates/browser-agent/src/shared/handlers/chat.ts +213 -0
- package/templates/browser-agent/src/shared/handlers/file.ts +99 -0
- package/templates/browser-agent/src/shared/handlers/index.ts +7 -0
- package/templates/browser-agent/src/shared/handlers/session.ts +167 -0
- package/templates/browser-agent/src/shared/handlers/system.ts +27 -0
- package/templates/browser-agent/src/shared/handlers/timer.ts +34 -0
- package/templates/browser-agent/src/shared/http-routes.ts +437 -0
- package/templates/browser-agent/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/browser-agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
- package/templates/browser-agent/src/shared/lib/__tests__/web-server.test.ts +203 -0
- package/templates/browser-agent/src/shared/lib/agent.ts +384 -0
- package/templates/browser-agent/src/shared/lib/cdp.ts +448 -0
- package/templates/browser-agent/src/shared/lib/generate.ts +111 -0
- package/templates/browser-agent/src/shared/lib/logger.ts +152 -0
- package/templates/browser-agent/src/shared/lib/mock-stream.ts +147 -0
- package/templates/browser-agent/src/shared/lib/path-security.ts +38 -0
- package/templates/browser-agent/src/shared/lib/port-registry.ts +103 -0
- package/templates/browser-agent/src/shared/lib/web-server.ts +127 -0
- package/templates/browser-agent/src/shared/lib/xhs-extract.ts +71 -0
- package/templates/browser-agent/src/shared/modules/browser.ts +86 -0
- package/templates/browser-agent/src/shared/modules/chat.ts +20 -0
- package/templates/browser-agent/src/shared/modules/file.ts +40 -0
- package/templates/browser-agent/src/shared/modules/session.ts +55 -0
- package/templates/browser-agent/src/shared/modules/system.ts +8 -0
- package/templates/browser-agent/src/shared/modules/timer.ts +11 -0
- package/templates/browser-agent/src/shared/register-all-handlers.ts +36 -0
- package/templates/browser-agent/src/shared/rpc-schema.ts +34 -0
- package/templates/browser-agent/tailwind.config.js +15 -0
- package/templates/browser-agent/tsconfig.ipc.json +14 -0
- package/templates/browser-agent/tsconfig.json +36 -0
- package/templates/browser-agent/vite.config.ts +3 -0
- package/templates/browser-agent/vitest.config.ts +3 -0
- package/templates/chat/.husky/commit-msg +4 -0
- package/templates/chat/.husky/pre-commit +11 -0
- package/templates/chat/.husky/pre-push +6 -0
- package/templates/chat/CHANGELOG.md +15 -0
- package/templates/chat/commitlint.config.js +8 -0
- package/templates/chat/package.json +19 -4
- package/templates/chat/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
- package/templates/chat/src/mainview/__tests__/setup.ts +32 -29
- package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +80 -55
- package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +40 -20
- package/templates/chat/src/mainview/lib/i18n/locales/en.json +175 -155
- package/templates/chat/src/mainview/lib/i18n/locales/zh.json +173 -155
- package/templates/chat/src/shared/handlers/chat.ts +17 -19
- package/templates/chat/src/shared/handlers/debug.ts +12 -2
- package/templates/cowork/.env.example +8 -0
- package/templates/cowork/.prettierignore +6 -0
- package/templates/cowork/.prettierrc +9 -0
- package/templates/cowork/AGENTS.md +236 -0
- package/templates/cowork/CHANGELOG.md +15 -0
- package/templates/cowork/LICENSE +21 -0
- package/templates/cowork/README.md +103 -0
- package/templates/cowork/commitlint.config.js +8 -0
- package/templates/cowork/docs/CODE-VIEW-PLAN.md +637 -0
- package/templates/cowork/docs/CODE-VIEW-V2.md +196 -0
- package/templates/cowork/docs/CODE-VIEW-V4.md +150 -0
- package/templates/cowork/electrobun.config.ts +27 -0
- package/templates/cowork/eslint.config.mjs +79 -0
- package/templates/cowork/llms.txt +24 -0
- package/templates/cowork/package.json +87 -0
- package/templates/cowork/postcss.config.js +6 -0
- package/templates/cowork/scripts/dev.ts +138 -0
- package/templates/cowork/src/__tests__/hybrid-mode.test.ts +126 -0
- package/templates/cowork/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/cowork/src/__tests__/server-config.test.ts +59 -0
- package/templates/cowork/src/bun/index.ts +130 -0
- package/templates/cowork/src/bun/three.d.ts +1 -0
- package/templates/cowork/src/gateway/http-routes.ts +1 -0
- package/templates/cowork/src/gateway/ipc-transport.ts +68 -0
- package/templates/cowork/src/gateway/sse-transport.ts +231 -0
- package/templates/cowork/src/mainview/App.tsx +45 -0
- package/templates/cowork/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
- package/templates/cowork/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
- package/templates/cowork/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
- package/templates/cowork/src/mainview/__tests__/setup.ts +40 -0
- package/templates/cowork/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
- package/templates/cowork/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
- package/templates/cowork/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/cowork/src/mainview/components/chat/TaskChat.tsx +311 -0
- package/templates/cowork/src/mainview/components/chat/__tests__/localhost-links.test.ts +76 -0
- package/templates/cowork/src/mainview/components/common/ErrorBoundary.tsx +1 -0
- package/templates/cowork/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
- package/templates/cowork/src/mainview/components/common/ThemeToggle.tsx +45 -0
- package/templates/cowork/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
- package/templates/cowork/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
- package/templates/cowork/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
- package/templates/cowork/src/mainview/components/dev/NetworkPanel.tsx +155 -0
- package/templates/cowork/src/mainview/components/layout/AppLayout.tsx +216 -0
- package/templates/cowork/src/mainview/components/right/ArtifactsPanel.tsx +54 -0
- package/templates/cowork/src/mainview/components/right/ContextPanel.tsx +133 -0
- package/templates/cowork/src/mainview/components/right/ElementPicker.tsx +206 -0
- package/templates/cowork/src/mainview/components/right/PreviewBlock.tsx +155 -0
- package/templates/cowork/src/mainview/components/right/ProgressPanel.tsx +85 -0
- package/templates/cowork/src/mainview/components/sidebar/TaskSidebar.tsx +211 -0
- package/templates/cowork/src/mainview/components/topbar/TopBar.tsx +86 -0
- package/templates/cowork/src/mainview/global.d.ts +13 -0
- package/templates/cowork/src/mainview/hooks/__tests__/use-breakpoint.test.ts +123 -0
- package/templates/cowork/src/mainview/hooks/use-breakpoint.ts +53 -0
- package/templates/cowork/src/mainview/hooks/use-rpc-init.ts +26 -0
- package/templates/cowork/src/mainview/hooks/use-sidebar-resize.ts +40 -0
- package/templates/cowork/src/mainview/index.css +89 -0
- package/templates/cowork/src/mainview/index.html +12 -0
- package/templates/cowork/src/mainview/lib/api-client.ts +404 -0
- package/templates/cowork/src/mainview/lib/i18n/index.ts +30 -0
- package/templates/cowork/src/mainview/lib/i18n/locales/en.json +130 -0
- package/templates/cowork/src/mainview/lib/i18n/locales/zh.json +128 -0
- package/templates/cowork/src/mainview/lib/network-bus.ts +92 -0
- package/templates/cowork/src/mainview/lib/rpc-cache.ts +94 -0
- package/templates/cowork/src/mainview/main.tsx +18 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-preview-store.test.ts +193 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-sidebar-store.test.ts +81 -0
- package/templates/cowork/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
- package/templates/cowork/src/mainview/stores/message-batcher.ts +25 -0
- package/templates/cowork/src/mainview/stores/use-chat-store.ts +198 -0
- package/templates/cowork/src/mainview/stores/use-connection-store.ts +168 -0
- package/templates/cowork/src/mainview/stores/use-context-store.ts +68 -0
- package/templates/cowork/src/mainview/stores/use-locale-store.ts +27 -0
- package/templates/cowork/src/mainview/stores/use-log-store.ts +16 -0
- package/templates/cowork/src/mainview/stores/use-network-panel-store.ts +18 -0
- package/templates/cowork/src/mainview/stores/use-output-store.ts +47 -0
- package/templates/cowork/src/mainview/stores/use-preview-store.ts +97 -0
- package/templates/cowork/src/mainview/stores/use-sidebar-store.ts +272 -0
- package/templates/cowork/src/mainview/stores/use-task-store.ts +86 -0
- package/templates/cowork/src/mainview/stores/use-theme-store.ts +46 -0
- package/templates/cowork/src/mainview/stores/use-view-store.ts +29 -0
- package/templates/cowork/src/mainview/types/index.ts +6 -0
- package/templates/cowork/src/server-config.ts +44 -0
- package/templates/cowork/src/server.ts +78 -0
- package/templates/cowork/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
- package/templates/cowork/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
- package/templates/cowork/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
- package/templates/cowork/src/shared/handlers/__tests__/preview-handler.test.ts +172 -0
- package/templates/cowork/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
- package/templates/cowork/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
- package/templates/cowork/src/shared/handlers/chat.ts +213 -0
- package/templates/cowork/src/shared/handlers/context.ts +72 -0
- package/templates/cowork/src/shared/handlers/file.ts +99 -0
- package/templates/cowork/src/shared/handlers/index.ts +9 -0
- package/templates/cowork/src/shared/handlers/output.ts +59 -0
- package/templates/cowork/src/shared/handlers/preview.ts +81 -0
- package/templates/cowork/src/shared/handlers/system.ts +27 -0
- package/templates/cowork/src/shared/handlers/task.ts +101 -0
- package/templates/cowork/src/shared/handlers/timer.ts +34 -0
- package/templates/cowork/src/shared/http-routes.ts +444 -0
- package/templates/cowork/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/cowork/src/shared/lib/__tests__/path-security.test.ts +77 -0
- package/templates/cowork/src/shared/lib/__tests__/web-server.test.ts +203 -0
- package/templates/cowork/src/shared/lib/logger.ts +152 -0
- package/templates/cowork/src/shared/lib/path-security.ts +38 -0
- package/templates/cowork/src/shared/lib/port-registry.ts +103 -0
- package/templates/cowork/src/shared/lib/web-server.ts +127 -0
- package/templates/cowork/src/shared/modules/chat.ts +20 -0
- package/templates/cowork/src/shared/modules/context.ts +32 -0
- package/templates/cowork/src/shared/modules/file.ts +40 -0
- package/templates/cowork/src/shared/modules/output.ts +20 -0
- package/templates/cowork/src/shared/modules/preview.ts +44 -0
- package/templates/cowork/src/shared/modules/system.ts +8 -0
- package/templates/cowork/src/shared/modules/task.ts +31 -0
- package/templates/cowork/src/shared/modules/timer.ts +11 -0
- package/templates/cowork/src/shared/register-all-handlers.ts +36 -0
- package/templates/cowork/src/shared/rpc-schema.ts +38 -0
- package/templates/cowork/tailwind.config.js +15 -0
- package/templates/cowork/test-upload.txt +0 -0
- package/templates/cowork/tsconfig.ipc.json +14 -0
- package/templates/cowork/tsconfig.json +36 -0
- package/templates/cowork/vite.config.ts +3 -0
- package/templates/cowork/vitest.config.ts +3 -0
- package/templates/general/.husky/commit-msg +4 -0
- package/templates/general/.husky/pre-commit +11 -0
- package/templates/general/.husky/pre-push +6 -0
- package/templates/general/CHANGELOG.md +15 -0
- package/templates/general/commitlint.config.js +8 -0
- package/templates/general/package.json +19 -4
- package/templates/general/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
- package/templates/general/src/mainview/__tests__/setup.ts +32 -29
- package/templates/general/src/mainview/components/chat/ChatPanel.tsx +80 -55
- package/templates/general/src/mainview/components/common/ThemeToggle.tsx +40 -20
- package/templates/general/src/mainview/components/debug/DebugPanel.tsx +170 -156
- package/templates/general/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +169 -0
- package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +42 -42
- package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +72 -67
- package/templates/general/src/mainview/components/feed/FeedPanel.tsx +7 -5
- package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +64 -45
- package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +24 -7
- package/templates/general/src/mainview/components/git/GitPanel.tsx +700 -473
- package/templates/general/src/mainview/components/layout/AppLayout.tsx +123 -114
- package/templates/general/src/mainview/components/search/SearchPanel.tsx +15 -19
- package/templates/general/src/mainview/lib/i18n/locales/en.json +175 -155
- package/templates/general/src/mainview/lib/i18n/locales/zh.json +173 -155
- package/templates/general/src/mainview/stores/__tests__/use-explorer-store.test.ts +259 -0
- package/templates/general/src/mainview/stores/__tests__/use-feed-store.test.ts +160 -0
- package/templates/general/src/mainview/stores/__tests__/use-git-store.test.ts +313 -0
- package/templates/general/src/mainview/stores/__tests__/use-notification-store.test.ts +115 -0
- package/templates/general/src/mainview/stores/__tests__/use-sidebar-store.test.ts +120 -0
- package/templates/general/src/mainview/stores/use-explorer-store.ts +277 -260
- package/templates/general/src/mainview/types/index.ts +21 -20
- package/templates/general/src/shared/handlers/chat.ts +1 -1
- package/templates/general/src/shared/handlers/debug.ts +12 -2
- package/templates/shared/components/ErrorBoundary.tsx +0 -1
- package/templates/shared/vite-base.config.ts +8 -7
- /package/templates/{shared → browser-agent}/test-upload.txt +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { useChatStore } from "../../stores/use-chat-store";
|
|
3
|
+
import { act } from "@testing-library/react";
|
|
4
|
+
|
|
5
|
+
describe("useChatStore", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
act(() => {
|
|
8
|
+
useChatStore.setState({
|
|
9
|
+
messages: [],
|
|
10
|
+
_onEventCallback: null,
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("initial state", () => {
|
|
16
|
+
const state = useChatStore.getState();
|
|
17
|
+
expect(state.messages).toEqual([]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("addUserMessage appends a user message", () => {
|
|
21
|
+
act(() => {
|
|
22
|
+
useChatStore.getState().addUserMessage("hello");
|
|
23
|
+
});
|
|
24
|
+
const msgs = useChatStore.getState().messages;
|
|
25
|
+
expect(msgs).toHaveLength(1);
|
|
26
|
+
expect(msgs[0].role).toBe("user");
|
|
27
|
+
expect((msgs[0] as { text: string }).text).toBe("hello");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("addAgentPlaceholder appends an agent message", () => {
|
|
31
|
+
act(() => {
|
|
32
|
+
useChatStore.getState().addAgentPlaceholder("agent-1");
|
|
33
|
+
});
|
|
34
|
+
const msgs = useChatStore.getState().messages;
|
|
35
|
+
expect(msgs).toHaveLength(1);
|
|
36
|
+
expect(msgs[0].role).toBe("agent");
|
|
37
|
+
expect(msgs[0].id).toBe("agent-1");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("setMessages replaces all messages", () => {
|
|
41
|
+
const msgs = [
|
|
42
|
+
{ id: "1", role: "user" as const, text: "a", at: 1000 },
|
|
43
|
+
{ id: "2", role: "agent" as const, text: "b", turns: [], thinking: "", at: 2000 },
|
|
44
|
+
];
|
|
45
|
+
act(() => {
|
|
46
|
+
useChatStore.getState().setMessages(msgs);
|
|
47
|
+
});
|
|
48
|
+
expect(useChatStore.getState().messages).toHaveLength(2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("addUserMessage then addAgentPlaceholder preserves order", () => {
|
|
52
|
+
act(() => {
|
|
53
|
+
useChatStore.getState().addUserMessage("question");
|
|
54
|
+
useChatStore.getState().addAgentPlaceholder("agent-1");
|
|
55
|
+
});
|
|
56
|
+
const msgs = useChatStore.getState().messages;
|
|
57
|
+
expect(msgs).toHaveLength(2);
|
|
58
|
+
expect(msgs[0].role).toBe("user");
|
|
59
|
+
expect(msgs[1].role).toBe("agent");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("patchLastAgent updates the last agent message", () => {
|
|
63
|
+
act(() => {
|
|
64
|
+
useChatStore.getState().addAgentPlaceholder("agent-1");
|
|
65
|
+
});
|
|
66
|
+
act(() => {
|
|
67
|
+
useChatStore.getState().patchLastAgent({ thinking: "reasoning..." });
|
|
68
|
+
});
|
|
69
|
+
const msgs = useChatStore.getState().messages;
|
|
70
|
+
const agentMsg = msgs[0] as { thinking: string };
|
|
71
|
+
expect(agentMsg.thinking).toBe("reasoning...");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { useSidebarStore, SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH } from "../../stores/use-sidebar-store";
|
|
3
|
+
import { act } from "@testing-library/react";
|
|
4
|
+
|
|
5
|
+
describe("useSidebarStore", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
act(() => {
|
|
8
|
+
useSidebarStore.setState({
|
|
9
|
+
isPinned: true,
|
|
10
|
+
drawerOpen: false,
|
|
11
|
+
sidebarWidth: 240,
|
|
12
|
+
breakpoint: "desktop",
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("setPinned", () => {
|
|
18
|
+
act(() => {
|
|
19
|
+
useSidebarStore.getState().setPinned(false);
|
|
20
|
+
});
|
|
21
|
+
expect(useSidebarStore.getState().isPinned).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("setDrawerOpen", () => {
|
|
25
|
+
act(() => {
|
|
26
|
+
useSidebarStore.getState().setDrawerOpen(true);
|
|
27
|
+
});
|
|
28
|
+
expect(useSidebarStore.getState().drawerOpen).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("setDrawerOpen false closes drawer", () => {
|
|
32
|
+
act(() => {
|
|
33
|
+
useSidebarStore.setState({ isPinned: false, drawerOpen: true });
|
|
34
|
+
});
|
|
35
|
+
act(() => {
|
|
36
|
+
useSidebarStore.getState().setDrawerOpen(false);
|
|
37
|
+
});
|
|
38
|
+
expect(useSidebarStore.getState().drawerOpen).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("setSidebarWidth clamps to min", () => {
|
|
42
|
+
act(() => {
|
|
43
|
+
useSidebarStore.getState().setSidebarWidth(50);
|
|
44
|
+
});
|
|
45
|
+
expect(useSidebarStore.getState().sidebarWidth).toBe(SIDEBAR_MIN_WIDTH);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("setSidebarWidth clamps to max", () => {
|
|
49
|
+
act(() => {
|
|
50
|
+
useSidebarStore.getState().setSidebarWidth(9999);
|
|
51
|
+
});
|
|
52
|
+
expect(useSidebarStore.getState().sidebarWidth).toBe(SIDEBAR_MAX_WIDTH);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("_setBreakpoint", () => {
|
|
56
|
+
act(() => {
|
|
57
|
+
useSidebarStore.getState()._setBreakpoint("mobile");
|
|
58
|
+
});
|
|
59
|
+
expect(useSidebarStore.getState().breakpoint).toBe("mobile");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { resolve, dirname } from "path";
|
|
4
|
+
|
|
5
|
+
function extractVarNames(css: string, selector: string): string[] {
|
|
6
|
+
const regex = new RegExp(`${selector.replace(".", "\\.")}\\s*\\{([^}]+)\\}`, "s");
|
|
7
|
+
const match = css.match(regex);
|
|
8
|
+
if (!match) return [];
|
|
9
|
+
const vars = match[1].match(/--[\w-]+/g);
|
|
10
|
+
return vars ? [...new Set(vars)].sort() : [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("CSS Variables", () => {
|
|
14
|
+
const cssPath = resolve(dirname(import.meta.url.replace("file://", "")), "../index.css");
|
|
15
|
+
const css = readFileSync(cssPath, "utf-8");
|
|
16
|
+
|
|
17
|
+
it("should have matching variables in :root and .dark", () => {
|
|
18
|
+
const rootVars = extractVarNames(css, ":root");
|
|
19
|
+
const darkVars = extractVarNames(css, ".dark");
|
|
20
|
+
|
|
21
|
+
expect(rootVars.length).toBeGreaterThan(0);
|
|
22
|
+
expect(darkVars.length).toBeGreaterThan(0);
|
|
23
|
+
expect(rootVars).toEqual(darkVars);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should have all required variable categories", () => {
|
|
27
|
+
const allVars = css.match(/--[\w-]+/g) || [];
|
|
28
|
+
const uniqueVars = [...new Set(allVars)];
|
|
29
|
+
|
|
30
|
+
const requiredPrefixes = ["bg-", "text-", "border-", "accent-"];
|
|
31
|
+
for (const prefix of requiredPrefixes) {
|
|
32
|
+
const hasPrefix = uniqueVars.some((v) => v.includes(prefix));
|
|
33
|
+
expect(hasPrefix, `Missing CSS variables with prefix: ${prefix}`).toBe(true);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 任务对话区 — 中间内容面板
|
|
3
|
+
*
|
|
4
|
+
* 显示任务标题 + 用户指令卡片 + AI 回复 + 命令执行卡片 + 回复输入框
|
|
5
|
+
* MVP 阶段使用 mock 数据流
|
|
6
|
+
*
|
|
7
|
+
* V4:聊天消息中出现的 localhost 链接会渲染为可点击的 🌐 标签,
|
|
8
|
+
* 点击后右侧面板自动展开并加载浏览器预览。
|
|
9
|
+
*/
|
|
10
|
+
import { useState, useEffect, useRef } from 'react';
|
|
11
|
+
import { ChevronDown, ChevronUp, Plus, ArrowUp, Globe, ExternalLink } from 'lucide-react';
|
|
12
|
+
import { useTaskStore } from '../../stores/use-task-store';
|
|
13
|
+
import { usePreviewStore } from '../../stores/use-preview-store';
|
|
14
|
+
import { useRightPanelStore } from '../../stores/use-sidebar-store';
|
|
15
|
+
|
|
16
|
+
// ── localhost 链接解析 ──
|
|
17
|
+
|
|
18
|
+
/** 匹配 localhost / 127.0.0.1 链接(可选协议、必带端口、可选路径) */
|
|
19
|
+
const LOCALHOST_RE = /(https?:\/\/)?(localhost|127\.0\.0\.1)(:\d+)([^\s,。、))】]*)?/g;
|
|
20
|
+
|
|
21
|
+
interface LinkSegment {
|
|
22
|
+
type: 'text' | 'link';
|
|
23
|
+
value: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 将文本拆分为「纯文本 / localhost 链接」片段。
|
|
28
|
+
* 仅高亮 localhost / 127.0.0.1 的本地预览地址。
|
|
29
|
+
*/
|
|
30
|
+
export function splitLocalhostLinks(text: string): LinkSegment[] {
|
|
31
|
+
const segments: LinkSegment[] = [];
|
|
32
|
+
let last = 0;
|
|
33
|
+
// 重置 lastIndex(全局正则复用)
|
|
34
|
+
LOCALHOST_RE.lastIndex = 0;
|
|
35
|
+
let match: RegExpExecArray | null;
|
|
36
|
+
while ((match = LOCALHOST_RE.exec(text)) !== null) {
|
|
37
|
+
const start = match.index;
|
|
38
|
+
if (start > last) segments.push({ type: 'text', value: text.slice(last, start) });
|
|
39
|
+
segments.push({ type: 'link', value: match[0] });
|
|
40
|
+
last = start + match[0].length;
|
|
41
|
+
}
|
|
42
|
+
if (last < text.length) segments.push({ type: 'text', value: text.slice(last) });
|
|
43
|
+
return segments;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Mock 对话数据 ──
|
|
47
|
+
interface ChatMessage {
|
|
48
|
+
id: string;
|
|
49
|
+
role: 'user' | 'assistant';
|
|
50
|
+
text?: string;
|
|
51
|
+
command?: { command: string; description: string };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const MOCK_MESSAGES: ChatMessage[] = [
|
|
55
|
+
{
|
|
56
|
+
id: 'msg-1',
|
|
57
|
+
role: 'user',
|
|
58
|
+
text: "Look at my drafts that were started within the last three months and then check that I didn't publish them on simonwillison.net using a search against content on that site and then suggest the ones that are most close to being ready",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'msg-2',
|
|
62
|
+
role: 'assistant',
|
|
63
|
+
text: "I'll help you find drafts from the last three months and check if they've been published. Let me start by looking at your drafts folder.",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'msg-3',
|
|
67
|
+
role: 'assistant',
|
|
68
|
+
command: {
|
|
69
|
+
command:
|
|
70
|
+
'find /sessions/zealous-bold-ramanujan/mnt/blog-drafts -type f \\( -name "*.md" -o -name "*.txt" -o -name "*.html" \\) -mtime -90 -exec ls -la {} \\;',
|
|
71
|
+
description: 'Find draft files modified in the last 90 days',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'msg-4',
|
|
76
|
+
role: 'assistant',
|
|
77
|
+
text: "Found 46 draft files. Now let me read the content of each to get their titles/topics, then search simonwillison.net for any matches to check if they've already been published.",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'msg-5',
|
|
81
|
+
role: 'assistant',
|
|
82
|
+
text: '本地预览服务已启动,访问 http://localhost:7300 即可查看草稿管理界面。',
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// ── localhost 链接标签 ──
|
|
87
|
+
function LocalhostLinkChip({ url }: { url: string }) {
|
|
88
|
+
const openUrl = usePreviewStore((s) => s.openUrl);
|
|
89
|
+
const openForPreview = useRightPanelStore((s) => s.openForPreview);
|
|
90
|
+
|
|
91
|
+
const handleClick = () => {
|
|
92
|
+
openForPreview();
|
|
93
|
+
openUrl(url);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<button
|
|
98
|
+
onClick={handleClick}
|
|
99
|
+
className="inline-flex items-center gap-1 px-2 py-0.5 mx-0.5 rounded-md bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50 transition-colors text-[13px] font-mono align-middle"
|
|
100
|
+
title={`在右侧打开预览:${url}`}
|
|
101
|
+
>
|
|
102
|
+
<Globe className="w-3 h-3 flex-shrink-0" />
|
|
103
|
+
<span className="truncate max-w-[220px]">{url.replace(/^https?:\/\//, '')}</span>
|
|
104
|
+
<ExternalLink className="w-2.5 h-2.5 flex-shrink-0 opacity-60" />
|
|
105
|
+
</button>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 渲染可能包含 localhost 链接的文本 */
|
|
110
|
+
function RichText({ text }: { text: string }) {
|
|
111
|
+
const segments = splitLocalhostLinks(text);
|
|
112
|
+
const only = segments.length === 1 ? segments[0] : undefined;
|
|
113
|
+
if (only && only.type === 'text') {
|
|
114
|
+
return <>{only.value}</>;
|
|
115
|
+
}
|
|
116
|
+
return (
|
|
117
|
+
<>
|
|
118
|
+
{segments.map((seg, i) =>
|
|
119
|
+
seg.type === 'link' ? (
|
|
120
|
+
<LocalhostLinkChip key={i} url={seg.value} />
|
|
121
|
+
) : (
|
|
122
|
+
<span key={i}>{seg.value}</span>
|
|
123
|
+
),
|
|
124
|
+
)}
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── 命令执行卡片 ──
|
|
130
|
+
function CommandCard({ command, description }: { command: string; description: string }) {
|
|
131
|
+
const [expanded, setExpanded] = useState(true);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
className="rounded-xl overflow-hidden my-3 border border-[var(--color-border-primary)]"
|
|
136
|
+
style={{ boxShadow: 'var(--shadow-card)' }}
|
|
137
|
+
>
|
|
138
|
+
<button
|
|
139
|
+
onClick={() => setExpanded(!expanded)}
|
|
140
|
+
className="w-full flex items-center gap-2.5 px-4 py-2.5 bg-[var(--color-bg-secondary)] hover:bg-[var(--color-bg-hover)] transition-colors text-left"
|
|
141
|
+
>
|
|
142
|
+
{/* 终端图标 */}
|
|
143
|
+
<div className="w-6 h-6 rounded-md bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center flex-shrink-0">
|
|
144
|
+
<span className="text-xs">⚙️</span>
|
|
145
|
+
</div>
|
|
146
|
+
<span className="text-sm font-medium text-[var(--color-text-primary)]">
|
|
147
|
+
Running command
|
|
148
|
+
</span>
|
|
149
|
+
<span className="flex-1" />
|
|
150
|
+
{expanded ? (
|
|
151
|
+
<ChevronUp className="w-4 h-4 text-[var(--color-text-tertiary)]" />
|
|
152
|
+
) : (
|
|
153
|
+
<ChevronDown className="w-4 h-4 text-[var(--color-text-tertiary)]" />
|
|
154
|
+
)}
|
|
155
|
+
</button>
|
|
156
|
+
{expanded && (
|
|
157
|
+
<div className="px-4 py-3 bg-[var(--color-bg-primary)] border-t border-[var(--color-border-secondary)]">
|
|
158
|
+
<div className="text-[11px] uppercase tracking-wide text-[var(--color-text-tertiary)] mb-2 font-semibold">
|
|
159
|
+
Request
|
|
160
|
+
</div>
|
|
161
|
+
<pre className="text-[13px] font-mono text-[var(--color-text-secondary)] whitespace-pre-wrap break-all leading-relaxed">
|
|
162
|
+
{JSON.stringify({ command, description }, null, 2)}
|
|
163
|
+
</pre>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── 单条消息 ──
|
|
171
|
+
function MessageBubble({ message }: { message: ChatMessage }) {
|
|
172
|
+
if (message.role === 'user') {
|
|
173
|
+
// 用户指令卡片:左对齐,米色背景,无边框
|
|
174
|
+
return (
|
|
175
|
+
<div className="mb-6">
|
|
176
|
+
<div
|
|
177
|
+
className="max-w-[90%] px-4 py-3.5 rounded-2xl rounded-tl-md text-[14px] leading-relaxed"
|
|
178
|
+
style={{
|
|
179
|
+
backgroundColor: 'var(--color-cowork-instruction-bg)',
|
|
180
|
+
color: 'var(--color-text-primary)',
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
{message.text}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// AI 回复:纯文本,左对齐,无背景
|
|
190
|
+
return (
|
|
191
|
+
<div className="mb-5">
|
|
192
|
+
<div className="max-w-[90%]">
|
|
193
|
+
{message.text && (
|
|
194
|
+
<div className="text-sm leading-relaxed text-[var(--color-text-primary)]">
|
|
195
|
+
<RichText text={message.text} />
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
{message.command && (
|
|
199
|
+
<CommandCard
|
|
200
|
+
command={message.command.command}
|
|
201
|
+
description={message.command.description}
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── 主组件 ──
|
|
210
|
+
export function TaskChat() {
|
|
211
|
+
const currentTaskId = useTaskStore((s) => s.currentTaskId);
|
|
212
|
+
const tasks = useTaskStore((s) => s.tasks);
|
|
213
|
+
const currentTask = tasks.find((t) => t.id === currentTaskId);
|
|
214
|
+
|
|
215
|
+
const [input, setInput] = useState('');
|
|
216
|
+
const [messages, setMessages] = useState<ChatMessage[]>(MOCK_MESSAGES);
|
|
217
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
218
|
+
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
221
|
+
}, [messages]);
|
|
222
|
+
|
|
223
|
+
const handleSend = () => {
|
|
224
|
+
if (!input.trim()) return;
|
|
225
|
+
setMessages((prev) => [
|
|
226
|
+
...prev,
|
|
227
|
+
{ id: `msg-${Date.now()}`, role: 'user', text: input },
|
|
228
|
+
{
|
|
229
|
+
id: `msg-${Date.now() + 1}`,
|
|
230
|
+
role: 'assistant',
|
|
231
|
+
text: `收到指令:"${input}"。正在分析并执行任务...`,
|
|
232
|
+
},
|
|
233
|
+
]);
|
|
234
|
+
setInput('');
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (!currentTask) {
|
|
238
|
+
return (
|
|
239
|
+
<div className="flex-1 flex items-center justify-center text-[var(--color-text-tertiary)]">
|
|
240
|
+
<div className="text-center">
|
|
241
|
+
<div className="w-16 h-16 rounded-full bg-[var(--color-bg-secondary)] flex items-center justify-center mx-auto mb-4">
|
|
242
|
+
<Plus className="w-7 h-7 text-[var(--color-text-tertiary)]" />
|
|
243
|
+
</div>
|
|
244
|
+
<p className="text-base font-medium text-[var(--color-text-secondary)] mb-1">
|
|
245
|
+
开始一个新任务
|
|
246
|
+
</p>
|
|
247
|
+
<p className="text-sm text-[var(--color-text-tertiary)]">
|
|
248
|
+
在左侧点击 "New task" 或切换 Tab
|
|
249
|
+
</p>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<div className="flex-1 flex flex-col overflow-hidden bg-[var(--color-bg-primary)]">
|
|
257
|
+
{/* 任务标题 */}
|
|
258
|
+
<div className="px-6 py-4 border-b border-[var(--color-border-primary)]">
|
|
259
|
+
<h2 className="text-base font-semibold text-[var(--color-text-primary)] truncate">
|
|
260
|
+
{currentTask.title}
|
|
261
|
+
</h2>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
{/* 消息流 */}
|
|
265
|
+
<div className="flex-1 overflow-auto px-6 py-5">
|
|
266
|
+
{messages.map((msg) => (
|
|
267
|
+
<MessageBubble key={msg.id} message={msg} />
|
|
268
|
+
))}
|
|
269
|
+
<div ref={messagesEndRef} />
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
{/* 底部输入框 */}
|
|
273
|
+
<div className="px-6 pb-4 pt-2">
|
|
274
|
+
<div className="flex items-end gap-2 bg-[var(--color-bg-primary)] rounded-2xl px-3 py-2.5 border border-[var(--color-border-primary)] focus-within:border-[var(--color-text-accent)] transition-colors shadow-sm">
|
|
275
|
+
<button className="p-1.5 text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-hover)] rounded-lg transition-all">
|
|
276
|
+
<Plus className="w-5 h-5" />
|
|
277
|
+
</button>
|
|
278
|
+
<textarea
|
|
279
|
+
value={input}
|
|
280
|
+
onChange={(e) => setInput(e.target.value)}
|
|
281
|
+
onKeyDown={(e) => {
|
|
282
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
283
|
+
e.preventDefault();
|
|
284
|
+
handleSend();
|
|
285
|
+
}
|
|
286
|
+
}}
|
|
287
|
+
placeholder="Reply..."
|
|
288
|
+
className="flex-1 bg-transparent text-[14px] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-placeholder)] resize-none outline-none min-h-[24px] max-h-[120px] py-1"
|
|
289
|
+
rows={1}
|
|
290
|
+
/>
|
|
291
|
+
<div className="flex items-center gap-2 pl-1">
|
|
292
|
+
<button className="text-xs text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] transition-colors px-2 py-1">
|
|
293
|
+
Opus 4.5
|
|
294
|
+
</button>
|
|
295
|
+
{/* 发送按钮:圆形,Claude 风格的橙红色 */}
|
|
296
|
+
<button
|
|
297
|
+
onClick={handleSend}
|
|
298
|
+
disabled={!input.trim()}
|
|
299
|
+
className="w-8 h-8 rounded-full bg-rose-400 text-white disabled:bg-[var(--color-bg-tertiary)] disabled:text-[var(--color-text-tertiary)] hover:bg-rose-500 transition-colors flex items-center justify-center"
|
|
300
|
+
>
|
|
301
|
+
<ArrowUp className="w-4 h-4" strokeWidth={2.5} />
|
|
302
|
+
</button>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
<p className="text-[11px] text-[var(--color-text-tertiary)] text-center mt-2.5">
|
|
306
|
+
Claude is AI and can make mistakes. Please double-check responses.
|
|
307
|
+
</p>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// 纯函数测试:只验证链接解析逻辑,不依赖 React/Store
|
|
4
|
+
// 通过动态导入避免拉起整个组件树
|
|
5
|
+
async function loadSplitter() {
|
|
6
|
+
const mod = await import('../TaskChat');
|
|
7
|
+
return mod.splitLocalhostLinks;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('splitLocalhostLinks', () => {
|
|
11
|
+
it('returns single text segment when no localhost link present', async () => {
|
|
12
|
+
const split = await loadSplitter();
|
|
13
|
+
const result = split('hello world, nothing to link here');
|
|
14
|
+
expect(result).toEqual([{ type: 'text', value: 'hello world, nothing to link here' }]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('detects a bare localhost:port link', async () => {
|
|
18
|
+
const split = await loadSplitter();
|
|
19
|
+
const result = split('visit localhost:7300 now');
|
|
20
|
+
expect(result).toEqual([
|
|
21
|
+
{ type: 'text', value: 'visit ' },
|
|
22
|
+
{ type: 'link', value: 'localhost:7300' },
|
|
23
|
+
{ type: 'text', value: ' now' },
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('detects an http://localhost link with path', async () => {
|
|
28
|
+
const split = await loadSplitter();
|
|
29
|
+
const result = split('see http://localhost:5173/drafts');
|
|
30
|
+
expect(result).toEqual([
|
|
31
|
+
{ type: 'text', value: 'see ' },
|
|
32
|
+
{ type: 'link', value: 'http://localhost:5173/drafts' },
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('detects 127.0.0.1 variant', async () => {
|
|
37
|
+
const split = await loadSplitter();
|
|
38
|
+
const result = split('open 127.0.0.1:3000');
|
|
39
|
+
expect(result).toEqual([
|
|
40
|
+
{ type: 'text', value: 'open ' },
|
|
41
|
+
{ type: 'link', value: '127.0.0.1:3000' },
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('detects multiple links in one message', async () => {
|
|
46
|
+
const split = await loadSplitter();
|
|
47
|
+
const result = split('a localhost:3000 and localhost:3001 here');
|
|
48
|
+
const links = result.filter((s) => s.type === 'link');
|
|
49
|
+
expect(links.map((s) => s.value)).toEqual(['localhost:3000', 'localhost:3001']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('does not match localhost without a port', async () => {
|
|
53
|
+
const split = await loadSplitter();
|
|
54
|
+
const result = split('just localhost here');
|
|
55
|
+
expect(result.every((s) => s.type === 'text')).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does not match a remote URL', async () => {
|
|
59
|
+
const split = await loadSplitter();
|
|
60
|
+
const result = split('check https://example.com/page');
|
|
61
|
+
expect(result.every((s) => s.type === 'text')).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('handles leading link with no surrounding text', async () => {
|
|
65
|
+
const split = await loadSplitter();
|
|
66
|
+
const result = split('localhost:8080');
|
|
67
|
+
expect(result).toEqual([{ type: 'link', value: 'localhost:8080' }]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('stops at CJK punctuation (does not swallow trailing fullwidth chars)', async () => {
|
|
71
|
+
const split = await loadSplitter();
|
|
72
|
+
const result = split('预览在 localhost:7300,点击查看。');
|
|
73
|
+
const linkSeg = result.find((s) => s.type === 'link');
|
|
74
|
+
expect(linkSeg?.value).toBe('localhost:7300');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ErrorBoundary } from "@shared/components/ErrorBoundary";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useLocaleStore } from "../../stores/use-locale-store";
|
|
2
|
+
|
|
3
|
+
export function LanguageSwitcher() {
|
|
4
|
+
const { locale, setLocale } = useLocaleStore();
|
|
5
|
+
return (
|
|
6
|
+
<button
|
|
7
|
+
data-testid="language-switcher"
|
|
8
|
+
onClick={() => setLocale(locale === "en" ? "zh" : "en")}
|
|
9
|
+
className="px-1.5 py-0.5 rounded text-xs hover:bg-[var(--color-bg-hover)] transition-colors text-[var(--color-text-secondary)]"
|
|
10
|
+
title={locale === "en" ? "切换到中文" : "Switch to English"}
|
|
11
|
+
>
|
|
12
|
+
{locale === "en" ? "中" : "EN"}
|
|
13
|
+
</button>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useThemeStore } from '../../stores/use-theme-store';
|
|
2
|
+
|
|
3
|
+
export function ThemeToggle() {
|
|
4
|
+
const theme = useThemeStore((s) => s.theme);
|
|
5
|
+
const toggleTheme = useThemeStore((s) => s.toggleTheme);
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<button
|
|
9
|
+
data-testid="theme-toggle"
|
|
10
|
+
onClick={toggleTheme}
|
|
11
|
+
className="p-1.5 rounded-md hover:bg-[var(--color-bg-hover)] transition-colors"
|
|
12
|
+
title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
|
|
13
|
+
>
|
|
14
|
+
{theme === 'dark' ? (
|
|
15
|
+
<svg
|
|
16
|
+
className="w-4 h-4 text-yellow-400"
|
|
17
|
+
fill="none"
|
|
18
|
+
viewBox="0 0 24 24"
|
|
19
|
+
stroke="currentColor"
|
|
20
|
+
strokeWidth={2}
|
|
21
|
+
>
|
|
22
|
+
<path
|
|
23
|
+
strokeLinecap="round"
|
|
24
|
+
strokeLinejoin="round"
|
|
25
|
+
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
|
26
|
+
/>
|
|
27
|
+
</svg>
|
|
28
|
+
) : (
|
|
29
|
+
<svg
|
|
30
|
+
className="w-4 h-4 text-[var(--color-text-tertiary)]"
|
|
31
|
+
fill="none"
|
|
32
|
+
viewBox="0 0 24 24"
|
|
33
|
+
stroke="currentColor"
|
|
34
|
+
strokeWidth={2}
|
|
35
|
+
>
|
|
36
|
+
<path
|
|
37
|
+
strokeLinecap="round"
|
|
38
|
+
strokeLinejoin="round"
|
|
39
|
+
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
|
|
40
|
+
/>
|
|
41
|
+
</svg>
|
|
42
|
+
)}
|
|
43
|
+
</button>
|
|
44
|
+
);
|
|
45
|
+
}
|