From 9b2d14b7991eab0a3fc2b3ed4d1294a9b9d56991 Mon Sep 17 00:00:00 2001 From: quan0715 Date: Sat, 7 Feb 2026 08:47:51 +0800 Subject: [PATCH] Rebrand to Reforge with shadcn Lyra/Stone/Blue theme and ScrollArea - Rebrand from "smo" to "Reforge" across all pages and components - Apply shadcn Stone base + Blue theme (HSL CSS variables) - Lyra style: JetBrains Mono font, radius 0, boxy/sharp look - Add shadcn ScrollArea component, replace native overflow scrollbars - Add sidebar/chart CSS variables for future use - Add ReforgeLogo component and SVG logo - Remove duplicate docker-compose.dev.yml, keep docker-compose.yml - Update README with new branding and correct compose references - Enable SummarizationMiddleware in agent for long conversations - Remove hardcoded VERTEX_AI_MODEL from deploy workflow Co-Authored-By: Claude Opus 4.6 --- .github/workflows/deploy-gce.yml | 2 +- README.md | 110 ++++++-------- agent/deep_agent.py | 13 +- devops/docker-compose.dev.yml | 94 ------------ frontend/components.json | 6 +- frontend/index.html | 7 +- frontend/package-lock.json | 60 +++++++- frontend/package.json | 3 +- frontend/public/reforge-logo.svg | 15 ++ .../src/components/agent/AgentRunPanel.tsx | 71 ++++----- frontend/src/components/agent/TaskList.tsx | 30 ++-- frontend/src/components/brand/ReforgeLogo.tsx | 20 +++ frontend/src/components/chat/ChatPanel.tsx | 106 ++++++++++---- .../src/components/chat/ChatSessionList.tsx | 55 +++---- .../src/components/common/ModelSelector.tsx | 17 +-- frontend/src/components/file/FileTree.tsx | 41 +++--- frontend/src/components/file/FileViewer.tsx | 16 +-- .../src/components/layout/PanelHeader.tsx | 4 +- .../project/ProjectSettingsModal.tsx | 43 +++--- frontend/src/components/ui/EmptyState.tsx | 4 +- frontend/src/components/ui/ErrorState.tsx | 4 +- frontend/src/components/ui/badge.tsx | 8 +- frontend/src/components/ui/button.tsx | 10 +- frontend/src/components/ui/card.tsx | 4 +- frontend/src/components/ui/dialog.tsx | 6 +- frontend/src/components/ui/input.tsx | 2 +- frontend/src/components/ui/scroll-area.tsx | 46 ++++++ frontend/src/components/ui/textarea.tsx | 2 +- frontend/src/components/ui/toast.tsx | 8 +- frontend/src/index.css | 135 ++++++++++++------ frontend/src/pages/CreateProjectPage.tsx | 50 +++---- frontend/src/pages/LoginPage.tsx | 34 +++-- frontend/src/pages/ProjectDetailPage.tsx | 59 ++++---- frontend/src/pages/ProjectsPage.tsx | 29 ++-- frontend/src/pages/RegisterPage.tsx | 42 +++--- frontend/tailwind.config.js | 47 ++++++ 36 files changed, 687 insertions(+), 516 deletions(-) delete mode 100644 devops/docker-compose.dev.yml create mode 100644 frontend/public/reforge-logo.svg create mode 100644 frontend/src/components/brand/ReforgeLogo.tsx create mode 100644 frontend/src/components/ui/scroll-area.tsx diff --git a/.github/workflows/deploy-gce.yml b/.github/workflows/deploy-gce.yml index 5e4fe94..f3c8c52 100644 --- a/.github/workflows/deploy-gce.yml +++ b/.github/workflows/deploy-gce.yml @@ -195,7 +195,7 @@ jobs: # Optional: Vertex AI (works best when the VM service account has Vertex permissions) GCP_PROJECT_ID=${{ env.GCP_PROJECT_ID }} GCP_LOCATION=us-central1 - VERTEX_AI_MODEL=gemini-2.5-pro + # NOTE: 模型選擇現在由前端透過 API 參數傳遞,不再需要 VERTEX_AI_MODEL 環境變數 LOG_LEVEL=INFO EOF diff --git a/README.md b/README.md index 9d67f3d..906c816 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# AI 舊程式碼智能重構系統 +# Reforge + +AI Refactoring. Measured. Continuous. 基於 LangChain Deep Agents 的智能程式碼分析與重構服務,提供隔離的 Docker 容器環境進行安全的程式碼重構。 @@ -41,27 +43,27 @@ Dockerfile 位置: ### 啟動服務 -**使用 Docker Compose(推薦)** +**Docker Compose(開發/測試)** ```bash -# 啟動所有服務(MongoDB + Backend API + Frontend) -docker-compose -f devops/docker-compose.dev.yml up -d +# 啟動所有服務(PostgreSQL + MongoDB + Backend API + Frontend) +docker-compose -f devops/docker-compose.yml up -d # 查看服務狀態 -docker-compose -f devops/docker-compose.dev.yml ps +docker-compose -f devops/docker-compose.yml ps # 查看日誌 -docker-compose -f devops/docker-compose.dev.yml logs -f api +docker-compose -f devops/docker-compose.yml logs -f api # 停止服務 -docker-compose -f devops/docker-compose.dev.yml down +docker-compose -f devops/docker-compose.yml down ``` **GCE 單機(正式環境)** -- 先將 `refactor-base` / `refactor-api` / `refactor-frontend` 映像推送到 Artifact Registry -- 設定 `REGISTRY_HOST` / `GCP_PROJECT_ID` / `GAR_REPOSITORY` / `IMAGE_TAG` 後,用 `devops/docker-compose.prod.yml` 啟動(會從 Artifact Registry 拉取映像) -- 也可直接使用 `./scripts/deploy-prod.sh`(會先 pull + 啟動) +- 使用 `devops/docker-compose.prod.yml` +- 設定 `REGISTRY_HOST` / `GCP_PROJECT_ID` / `GAR_REPOSITORY` / `IMAGE_TAG` +- 映像從 Artifact Registry 拉取 服務端點: - Frontend: http://localhost:5173 @@ -106,7 +108,6 @@ curl -X POST http://localhost:8000/api/v1/auth/login \ ### 2. 建立專案 ```bash -# 建立專案 curl -X POST http://localhost:8000/api/v1/projects \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ @@ -120,7 +121,6 @@ curl -X POST http://localhost:8000/api/v1/projects \ ### 3. Provision 專案 ```bash -# Provision 專案(建立隔離容器並 clone repository) curl -X POST http://localhost:8000/api/v1/projects/{project_id}/provision \ -H "Authorization: Bearer YOUR_TOKEN" ``` @@ -143,16 +143,10 @@ curl -N http://localhost:8000/api/v1/projects/{project_id}/agent/runs/{run_id}/s ## 測試 -### E2E 測試 - ```bash # 執行完整 E2E 測試 ./test_cloud_run_e2e_v2.sh -``` - -### Base Image 測試 -```bash # 測試 base image 建置 export ANTHROPIC_API_KEY=your-api-key ./test_base_image.sh @@ -162,65 +156,44 @@ export ANTHROPIC_API_KEY=your-api-key ``` ┌─────────────┐ HTTP ┌──────────────────┐ -│ Frontend │ ◄─────────────► │ Backend API │ -│ (React/Vite)│ │ (FastAPI) │ -└─────────────┘ └────────┬─────────┘ - │ - ┌────────────────┼────────────────┐ - │ │ │ - ▼ ▼ ▼ - ┌──────────┐ ┌──────────────┐ ┌──────────┐ - │ MongoDB │ │Docker Network│ │ Project │ - │ │ │ │ │Container │ - └──────────┘ └──────┬───────┘ └────┬─────┘ - │ │ - │ HTTP │ - └───────────────┤ - │ - ┌─────▼──────┐ - │ AI Server │ - │ (FastAPI) │ - │ │ - │ Deep Agent │ - └────────────┘ +│ Frontend │ <────────────> │ Backend API │ +│ (React/Vite)│ │ (FastAPI) │ +└─────────────┘ └────────┬─────────┘ + │ + ┌────────────────┼────────────────┐ + │ │ │ + v v v + ┌──────────┐ ┌──────────────┐ ┌──────────┐ + │ MongoDB │ │Docker Network│ │ Project │ + │ │ │ │ │Container │ + └──────────┘ └──────┬───────┘ └────┬─────┘ + │ │ + │ HTTP │ + └───────────────┤ + │ + ┌─────v──────┐ + │ AI Server │ + │ (FastAPI) │ + │ │ + │ Deep Agent │ + └────────────┘ ``` -### 核心特性 - -- **隔離環境**: 每個專案在獨立的 Docker 容器中執行 -- **AI Server**: 容器內建 FastAPI HTTP Server,提供 Agent 執行介面 -- **異步任務**: 支援長時間執行的 Agent 任務(無 timeout 限制) -- **實時日誌**: SSE 串流提供 Agent 執行過程的實時日誌 -- **JWT 認證**: 安全的使用者認證機制 - ## 技術棧 -- **Backend**: FastAPI, Python 3.11, MongoDB +- **Backend**: FastAPI, Python 3.11, MongoDB, PostgreSQL - **Frontend**: React 18, Vite, TypeScript, Tailwind CSS, shadcn/ui - **AI/ML**: LangChain, Deep Agents, Anthropic Claude - **容器**: Docker, Docker Compose - **認證**: JWT (JSON Web Tokens) -## 📚 文件 +## 文件 -### 完整文件導覽 - -請參閱 **[docs/](./docs/)** 資料夾: - -- **[docs/API.md](./docs/API.md)** - REST API 完整規格(詳細的 Request/Response) -- **[docs/BACKEND.md](./docs/BACKEND.md)** - 後端技術文件(架構、服務層、部署) -- **[docs/guides/](./docs/guides/)** - 使用指南(CLI 工具等) +- **[docs/API.md](./docs/API.md)** - REST API 完整規格 +- **[docs/BACKEND.md](./docs/BACKEND.md)** - 後端技術文件 +- **[docs/guides/](./docs/guides/)** - 使用指南 - **[docs/testing/](./docs/testing/)** - 測試文件 - -### 開發指引 - -- [CLAUDE.md](./CLAUDE.md) - Claude Code 專案指引 -- [docs/README.md](./docs/README.md) - 文件索引 - -### API 文件 - -- **Swagger UI**: http://localhost:8000/docs(互動式 API 文件) -- **詳細規格**: [docs/API.md](./docs/API.md)(完整的 Request/Response 範例) +- **[CLAUDE.md](./CLAUDE.md)** - Claude Code 專案指引 ## 常見問題 @@ -235,14 +208,13 @@ docker images | grep refactor-base 1. 檢查容器內 AI Server 的 LLM API Key 設定 2. 查看容器日誌:`docker logs refactor-project-{project_id}` -3. 檢查 API 日誌:`docker-compose -f devops/docker-compose.dev.yml logs -f api` -4. 查看 Agent 執行日誌:使用 SSE stream 端點 +3. 檢查 API 日誌:`docker-compose -f devops/docker-compose.yml logs -f api` ### 如何清理測試資料? ```bash # 停止並移除所有容器和資料 -docker-compose -f devops/docker-compose.dev.yml down -v +docker-compose -f devops/docker-compose.yml down -v # 清理專案容器 docker ps -a | grep refactor-project | awk '{print $1}' | xargs docker rm -f diff --git a/agent/deep_agent.py b/agent/deep_agent.py index 40d13c4..c6e1a66 100644 --- a/agent/deep_agent.py +++ b/agent/deep_agent.py @@ -149,9 +149,16 @@ def _agent_init(self): ) # 準備 middleware 列表 - # 注意:當 checkpointer 啟用時,SummarizationMiddleware 可能已被自動添加 - # 暫時不手動添加 middleware,避免重複 - middleware = [] + # 啟用 SummarizationMiddleware 來自動壓縮過長的對話歷史 + middleware = [ + SummarizationMiddleware( + model=self.model, + # 當訊息數量超過 50 條時觸發壓縮 + trigger=("messages", 50), + # 壓縮後保留最近的 20 條訊息 + keep=("messages", 20), + ) + ] self.agent = create_deep_agent( model=self.model, diff --git a/devops/docker-compose.dev.yml b/devops/docker-compose.dev.yml deleted file mode 100644 index d1d5e9f..0000000 --- a/devops/docker-compose.dev.yml +++ /dev/null @@ -1,94 +0,0 @@ -services: - # PostgreSQL for LangGraph persistence - postgres: - image: postgres:16 - container_name: refactor-postgres - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - environment: - POSTGRES_USER: langgraph - POSTGRES_PASSWORD: langgraph_secret - POSTGRES_DB: langgraph - networks: - - refactor-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U langgraph"] - interval: 10s - timeout: 5s - retries: 5 - - # MongoDB 資料庫 - mongodb: - image: mongo:7 - container_name: refactor-mongodb - ports: - - "27017:27017" - volumes: - - mongodb_data:/data/db - environment: - MONGO_INITDB_DATABASE: refactor_agent - networks: - - refactor-network - healthcheck: - test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] - interval: 10s - timeout: 5s - retries: 5 - - # FastAPI 後端服務 - api: - build: - context: .. - dockerfile: backend/Dockerfile - container_name: refactor-api - ports: - - "8000:8000" - volumes: - - ../backend:/app - - /var/run/docker.sock:/var/run/docker.sock - - /tmp/refactor-workspaces:/tmp/refactor-workspaces - - ${HOME}/.config/gcloud/application_default_credentials.json:/root/.config/gcloud/application_default_credentials.json:ro - env_file: - - ../backend/.env - environment: - - MONGODB_URL=mongodb://mongodb:27017 - - MONGODB_DATABASE=refactor_agent - - POSTGRES_URL=postgresql://langgraph:langgraph_secret@postgres:5432/langgraph - - DEBUG=true - depends_on: - mongodb: - condition: service_healthy - postgres: - condition: service_healthy - networks: - - refactor-network - command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload - - # React 前端服務 - frontend: - build: - context: .. - dockerfile: frontend/Dockerfile - container_name: refactor-frontend - ports: - - "5173:5173" - volumes: - - ../frontend:/app - - /app/node_modules # 避免覆蓋 node_modules - environment: - - VITE_API_BASE_URL=http://localhost:8000 - depends_on: - - api - networks: - - refactor-network - -volumes: - mongodb_data: - postgres_data: - -networks: - refactor-network: - name: refactor-network - driver: bridge diff --git a/frontend/components.json b/frontend/components.json index afb0587..adf4f7a 100644 --- a/frontend/components.json +++ b/frontend/components.json @@ -1,13 +1,13 @@ { "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", + "style": "new-york", "rsc": false, "tsx": true, "tailwind": { "config": "tailwind.config.js", "css": "src/index.css", - "baseColor": "neutral", - "cssVariables": false, + "baseColor": "stone", + "cssVariables": true, "prefix": "" }, "aliases": { diff --git a/frontend/index.html b/frontend/index.html index dce9be6..9374b40 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,12 @@ - + - AI 舊程式碼智能重構系統 + Reforge + + +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 03cc6ac..40ab023 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "auto-refactor-frontend", + "name": "reforge-frontend", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "auto-refactor-frontend", + "name": "reforge-frontend", "version": "0.1.0", "dependencies": { "@fortawesome/fontawesome-svg-core": "^7.1.0", @@ -13,6 +13,7 @@ "@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/react-fontawesome": "^3.1.1", "@hookform/resolvers": "^3.3.4", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.0.2", "@tailwindcss/typography": "^0.5.19", @@ -1640,6 +1641,30 @@ } } }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-primitive": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", @@ -1681,6 +1706,37 @@ } } }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index e30ca85..bb7d222 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "auto-refactor-frontend", + "name": "reforge-frontend", "private": true, "version": "0.1.0", "type": "module", @@ -17,6 +17,7 @@ "@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/react-fontawesome": "^3.1.1", "@hookform/resolvers": "^3.3.4", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.0.2", "@tailwindcss/typography": "^0.5.19", diff --git a/frontend/public/reforge-logo.svg b/frontend/public/reforge-logo.svg new file mode 100644 index 0000000..97aa19c --- /dev/null +++ b/frontend/public/reforge-logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/frontend/src/components/agent/AgentRunPanel.tsx b/frontend/src/components/agent/AgentRunPanel.tsx index 938a4c4..1ee7d94 100644 --- a/frontend/src/components/agent/AgentRunPanel.tsx +++ b/frontend/src/components/agent/AgentRunPanel.tsx @@ -4,6 +4,7 @@ import { useAgentRunStream } from '@/hooks/useAgentRunStream' import { Loader2, AlertCircle, CheckCircle, Square, Play } from 'lucide-react' import ReactMarkdown from 'react-markdown' import { TaskList, type Task } from './TaskList' +import { ScrollArea } from '@/components/ui/scroll-area' interface Props { projectId: string @@ -68,7 +69,7 @@ export function AgentRunPanel({ projectId, currentRun, onTasksUpdate, onReconnec if (!currentRun) { return ( -
+

尚未啟動 Agent Run

點擊「開始重構」按鈕來啟動

@@ -77,28 +78,28 @@ export function AgentRunPanel({ projectId, currentRun, onTasksUpdate, onReconnec } const statusConfig = { - RUNNING: { icon: Loader2, color: 'text-purple-400', spin: true, label: '執行中' }, + RUNNING: { icon: Loader2, color: 'text-brand-blue-400', spin: true, label: '執行中' }, DONE: { icon: CheckCircle, color: 'text-green-400', spin: false, label: '完成' }, FAILED: { icon: AlertCircle, color: 'text-red-400', spin: false, label: '失敗' }, - STOPPED: { icon: Square, color: 'text-gray-400', spin: false, label: '已停止' }, + STOPPED: { icon: Square, color: 'text-muted-foreground', spin: false, label: '已停止' }, } const status = statusConfig[currentRun.status] || statusConfig.STOPPED const StatusIcon = status.icon return ( -
+
{/* Header */} -
+
{status.label} {isReconnecting && ( - 重新連線中... + 重新連線中... )}
-
+
Run #{currentRun.id.slice(0, 8)}
@@ -111,31 +112,33 @@ export function AgentRunPanel({ projectId, currentRun, onTasksUpdate, onReconnec {/* Tasks */} {tasks.length > 0 && ( -
+ -
+ )} {/* Logs */} -
- {logs.length === 0 && isRunning && ( -
- -

等待日誌...

-
- )} - - {logs.map((log, index) => ( - - ))} - -
-
+ +
+ {logs.length === 0 && isRunning && ( +
+ +

等待日誌...

+
+ )} + + {logs.map((log, index) => ( + + ))} + +
+
+ {/* Footer */} {isStreaming && ( -
-
+
+
即時串流中
@@ -150,7 +153,7 @@ function LogEntry({ log }: { log: LogEntry }) { switch (log.type) { case 'ai_content': return ( -
+
{log.content.text || ''}
) @@ -161,7 +164,7 @@ function LogEntry({ log }: { log: LogEntry }) {
🔧 呼叫工具: {log.content.tool_name || log.content.name || '未知'} {log.content.args && ( -
+              
                 {JSON.stringify(log.content.args, null, 2)}
               
)} @@ -173,7 +176,7 @@ function LogEntry({ log }: { log: LogEntry }) {
✓ 工具結果 {log.content.output && ( -
+              
                 {typeof log.content.output === 'string'
                   ? log.content.output
                   : JSON.stringify(log.content.output, null, 2)}
@@ -191,28 +194,28 @@ function LogEntry({ log }: { log: LogEntry }) {
 
       case 'token_usage':
         return (
-          
+
📊 Token 使用: 輸入 {log.content.input_tokens || 0} / 輸出 {log.content.output_tokens || 0}
) case 'status': return ( -
+
📌 狀態: {log.content.status || '未知'}
) case 'log': return ( -
+
{log.content.message || JSON.stringify(log.content)}
) default: return ( -
+
{JSON.stringify(log.content)}
) @@ -220,8 +223,8 @@ function LogEntry({ log }: { log: LogEntry }) { } return ( -
-
+
+
[{new Date(log.timestamp).toLocaleTimeString()}] {log.type}
{renderContent()} diff --git a/frontend/src/components/agent/TaskList.tsx b/frontend/src/components/agent/TaskList.tsx index 5c9ccc8..6916f8c 100644 --- a/frontend/src/components/agent/TaskList.tsx +++ b/frontend/src/components/agent/TaskList.tsx @@ -29,25 +29,25 @@ export function TaskList({ tasks, compact = false }: TaskListProps) { } return ( -
+
{/* Header */} -
-
+
+
Tasks
{inProgressCount > 0 && ( - {inProgressCount} running + {inProgressCount} running )} - + {completedCount}/{tasks.length}
{/* Task List */} -
+
{tasks.map((task, index) => ( ))} @@ -59,17 +59,17 @@ export function TaskList({ tasks, compact = false }: TaskListProps) { function TaskItem({ task }: { task: Task }) { return (
{task.content} @@ -83,10 +83,10 @@ function CompactTaskItem({ task }: { task: Task }) { {task.content} @@ -101,8 +101,8 @@ function StatusIcon({ status, size = 'md' }: { status: Task['status']; size?: 's case 'completed': return case 'in_progress': - return + return case 'pending': - return + return } } diff --git a/frontend/src/components/brand/ReforgeLogo.tsx b/frontend/src/components/brand/ReforgeLogo.tsx new file mode 100644 index 0000000..70f410f --- /dev/null +++ b/frontend/src/components/brand/ReforgeLogo.tsx @@ -0,0 +1,20 @@ +interface ReforgeLogoProps { + size?: 'sm' | 'md' | 'lg' + className?: string +} + +const sizes = { + sm: 'w-8 h-8', + md: 'w-12 h-12', + lg: 'w-16 h-16', +} + +export function ReforgeLogo({ size = 'md', className = '' }: ReforgeLogoProps) { + return ( + Reforge + ) +} diff --git a/frontend/src/components/chat/ChatPanel.tsx b/frontend/src/components/chat/ChatPanel.tsx index d69f95f..673a69c 100644 --- a/frontend/src/components/chat/ChatPanel.tsx +++ b/frontend/src/components/chat/ChatPanel.tsx @@ -21,6 +21,9 @@ import { EmptyState } from "@/components/ui/EmptyState"; import { apiErrorMessage } from "@/utils/apiError"; import { useAgentRunStream } from "@/hooks/useAgentRunStream"; import { ModelSelector } from "@/components/common/ModelSelector"; +import { useToast } from "@/hooks/useToast"; +import { Toast } from "@/components/ui/toast"; +import { ScrollArea } from "@/components/ui/scroll-area"; interface Props { projectId: string; @@ -65,6 +68,10 @@ export function ChatPanel({ const messagesEndRef = useRef(null); const textareaRef = useRef(null); const cancelStreamRef = useRef<(() => void) | null>(null); + const warningShownRef = useRef<{ [key: number]: boolean }>({}); + + // Toast for warnings + const toast = useToast(); // Agent Run 串流整合 const isAgentRunning = currentRun?.status === 'RUNNING'; @@ -130,6 +137,34 @@ export function ChatPanel({ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); + // 監控會話長度,顯示警告 + useEffect(() => { + const count = messages.length; + + // 第一次警告: 50 條訊息 + if (count >= 50 && count < 100 && !warningShownRef.current[50]) { + warningShownRef.current[50] = true; + toast.info( + "💡 對話已累積 50+ 條訊息,建議開啟新會話以獲得更好的效能", + 8000 + ); + } + + // 第二次警告: 100 條訊息(更嚴重) + if (count >= 100 && !warningShownRef.current[100]) { + warningShownRef.current[100] = true; + toast.info( + "⚠️ 對話已累積 100+ 條訊息,強烈建議開啟新會話避免效能問題", + 10000 + ); + } + }, [messages.length, toast]); + + // 當 threadId 改變時,重置警告狀態 + useEffect(() => { + warningShownRef.current = {}; + }, [threadId]); + useEffect(() => { onStreamingChange?.(isAnyStreaming); }, [isAnyStreaming, onStreamingChange]); @@ -477,26 +512,28 @@ export function ChatPanel({ }; return ( -
+
{/* Messages */} -
- {loadingHistory && ( -
- - Loading history... -
- )} - {!loadingHistory && messages.length === 0 ? ( - } /> - ) : ( - messages.map((msg) => ) - )} -
-
+ +
+ {loadingHistory && ( +
+ + Loading history... +
+ )} + {!loadingHistory && messages.length === 0 ? ( + } /> + ) : ( + messages.map((msg) => ) + )} +
+
+ {/* Input */}
-
+
{/* Textarea */}