From b7cfe08251ed18e1beb6abb656dba338ed05486e Mon Sep 17 00:00:00 2001 From: alicesainta Date: Mon, 16 Mar 2026 12:48:23 +0800 Subject: [PATCH] chore(style): enforce formatting and sort imports --- .github/workflows/auto-commit.yml | 2 +- .github/workflows/ci.yml | 2 + .prettierignore | 8 +++ docs/configuration.md | 6 +- docs/page-structure.md | 4 ++ eslint.config.mjs | 16 +++++ package.json | 7 ++ pnpm-lock.yaml | 97 +++++++++++++++++++++++++++ pnpm-workspace.yaml | 4 +- prettier.config.mjs | 10 +++ src/app/api/chat/route.ts | 2 +- src/app/chat/[chat_id]/page.tsx | 34 ++++------ src/app/components/AgentMvpPanel.tsx | 33 +++++---- src/app/components/InputField.tsx | 7 +- src/app/components/MessageList.tsx | 16 ++--- src/app/components/ModelSelector.tsx | 13 +--- src/app/globals.css | 4 +- src/app/layout.tsx | 4 +- src/app/page.tsx | 13 ++-- src/app/perf/page.tsx | 14 ++-- src/components/AppShell.tsx | 3 +- src/components/Navbar.tsx | 55 ++++++++------- src/components/ThemeProvider.tsx | 22 ++---- src/lib/agent/chatPipeline.ts | 4 +- src/lib/agent/createAdapter.test.ts | 3 +- src/lib/agent/createAdapter.ts | 6 +- src/lib/agent/httpAdapter.test.ts | 5 +- src/lib/agent/httpAdapter.ts | 4 +- src/lib/agent/mockAdapter.ts | 2 +- src/lib/agent/runner.test.ts | 1 + src/lib/agent/runner.ts | 9 +-- src/lib/agent/stateMachine.ts | 12 +--- src/lib/agent/types.ts | 15 +---- src/lib/chatMessageStorage.test.ts | 5 +- src/lib/chatMessageStorage.ts | 1 + src/lib/chatMessages.test.ts | 5 +- src/lib/chatMessages.ts | 5 +- src/lib/chatStore.test.ts | 3 +- src/lib/chatStore.ts | 64 +++++++----------- src/lib/model/chatGateway.test.ts | 11 ++- src/lib/model/chatGateway.ts | 16 ++--- src/lib/model/globalModel.ts | 7 +- src/lib/model/models.test.ts | 7 +- src/lib/model/models.ts | 7 +- src/lib/observability/clientEvents.ts | 5 +- src/lib/observability/serverEvents.ts | 5 +- src/lib/perf/chatPerfDataset.ts | 8 +-- src/lib/useHydrated.ts | 3 +- vitest.config.ts | 3 +- 49 files changed, 329 insertions(+), 263 deletions(-) create mode 100644 .prettierignore create mode 100644 prettier.config.mjs diff --git a/.github/workflows/auto-commit.yml b/.github/workflows/auto-commit.yml index 754f0bc..37f2aa6 100644 --- a/.github/workflows/auto-commit.yml +++ b/.github/workflows/auto-commit.yml @@ -1,7 +1,7 @@ name: Auto Commit Demo on: - workflow_dispatch: # 手动触发 + workflow_dispatch: # 手动触发 jobs: auto-commit: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f14ebe9..a4d4266 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,8 @@ jobs: - name: Install run: pnpm install --frozen-lockfile + - name: Format check + run: pnpm run format:check - name: Lint run: pnpm run lint - name: Type check diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..5106455 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +.next +out +build +coverage +node_modules +.pnpm-store +pnpm-lock.yaml +tsconfig.tsbuildinfo diff --git a/docs/configuration.md b/docs/configuration.md index e79e4bc..61424d7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -5,6 +5,7 @@ 当前版本仍使用 Next.js 内置 `/api/chat` 作为服务端代理。 环境变量: + - `DEEPSEEK_API_KEY`:模型 API Key - `BASE_URL`:OpenAI-compatible 接口地址(例如 `https://api.deepseek.com/v1`) - `NEXT_PUBLIC_AGENT_ADAPTER`:Agent adapter 模式(`mock` 或 `http`,默认 `mock`) @@ -12,6 +13,7 @@ - `NEXT_PUBLIC_AGENT_TOOL_NAME`:HTTP 模式下默认工具名(默认 `deepscan.search`) 注意: + - 前端不直接暴露模型 Key。 - 当前不依赖登录即可运行。 - Agent 面板联调前至少配置 `NEXT_PUBLIC_AGENT_ADAPTER=http` 与 `NEXT_PUBLIC_AGENT_API_BASE_URL`。 @@ -35,6 +37,6 @@ ## 迁移建议 -1. 先新增 Gin 服务并对齐接口,再替换前端请求基址。 -2. 会话与消息链路先迁移到 DB,再关闭本地写入。 +1. 先新增 Gin 服务并对齐接口,再替换前端请求基址。 +2. 会话与消息链路先迁移到 DB,再关闭本地写入。 3. Agent 与 MCP 在后端稳定后逐步放量。 diff --git a/docs/page-structure.md b/docs/page-structure.md index c23eaec..34309c5 100644 --- a/docs/page-structure.md +++ b/docs/page-structure.md @@ -16,6 +16,7 @@ flowchart TD ``` 源码定位: + - `src/app/layout.tsx`:全局布局、主题与 Query Provider。 - `src/components/AppShell.tsx`:两栏骨架与侧边栏折叠状态。 - `src/app/page.tsx`:首页输入后跳转 `/chat/new?q=...`。 @@ -47,6 +48,7 @@ sequenceDiagram ``` 源码定位: + - `src/app/components/InputField.tsx`:输入、提交与停止按钮。 - `src/app/chat/[chat_id]/page.tsx`:发送逻辑、流式状态、会话创建与跳转。 - `src/app/components/MessageList.tsx`:助手/用户消息渲染与代码块复制。 @@ -76,6 +78,7 @@ sequenceDiagram ``` 源码定位: + - `src/components/Navbar.tsx`:会话列表操作。 - `src/lib/chatStore.ts`:会话元信息读写与排序。 @@ -87,6 +90,7 @@ sequenceDiagram - `deepscan:sidebar-collapsed`:侧边栏折叠状态。 恢复流程: + 1. 进入 `/chat/[chat_id]` 时读取会话 ID。 2. 从 `deepscan:chat::messages` 拉取历史消息。 3. 渲染消息列表并继续在同一会话上下文发送。 diff --git a/eslint.config.mjs b/eslint.config.mjs index 05e726d..549e6ae 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,10 +1,26 @@ import { defineConfig, globalIgnores } from "eslint/config"; import nextVitals from "eslint-config-next/core-web-vitals"; import nextTs from "eslint-config-next/typescript"; +import prettier from "eslint-config-prettier"; +import simpleImportSort from "eslint-plugin-simple-import-sort"; const eslintConfig = defineConfig([ ...nextVitals, ...nextTs, + { + plugins: { + "simple-import-sort": simpleImportSort, + }, + rules: { + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "@typescript-eslint/consistent-type-imports": [ + "error", + { prefer: "type-imports", fixStyle: "inline-type-imports" }, + ], + }, + }, + prettier, // Override default ignores of eslint-config-next. globalIgnores([ // Default ignores of eslint-config-next: diff --git a/package.json b/package.json index 486b8f6..32be2d2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "build": "next build", "start": "next start", "lint": "eslint", + "lint:fix": "eslint --fix", "type-check": "tsc --noEmit", + "format": "prettier --write .", + "format:check": "prettier --check .", "test": "vitest run" }, "dependencies": { @@ -33,6 +36,10 @@ "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9", "eslint-config-next": "16.1.6", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-simple-import-sort": "^12.1.1", + "prettier": "^3.8.1", + "prettier-plugin-tailwindcss": "^0.7.2", "tailwindcss": "^4", "typescript": "^5", "vitest": "^4.0.18" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e05809..da0db41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,18 @@ importers: eslint-config-next: specifier: 16.1.6 version: 16.1.6(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-simple-import-sort: + specifier: ^12.1.1 + version: 12.1.1(eslint@9.39.2(jiti@2.6.1)) + prettier: + specifier: ^3.8.1 + version: 3.8.1 + prettier-plugin-tailwindcss: + specifier: ^0.7.2 + version: 0.7.2(prettier@3.8.1) tailwindcss: specifier: ^4 version: 4.1.18 @@ -1461,6 +1473,12 @@ packages: typescript: optional: true + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -1526,6 +1544,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-simple-import-sort@12.1.1: + resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==} + peerDependencies: + eslint: '>=5.0.0' + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2372,6 +2395,66 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier-plugin-tailwindcss@0.7.2: + resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + prismjs@1.27.0: resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} engines: {node: '>=6'} @@ -4200,6 +4283,10 @@ snapshots: - eslint-plugin-import-x - supports-color + eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)): + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 @@ -4315,6 +4402,10 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.2(jiti@2.6.1)): + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -5396,6 +5487,12 @@ snapshots: prelude-ls@1.2.1: {} + prettier-plugin-tailwindcss@0.7.2(prettier@3.8.1): + dependencies: + prettier: 3.8.1 + + prettier@3.8.1: {} + prismjs@1.27.0: {} prismjs@1.30.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e56c8b5..ea9dd9a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,7 @@ packages: - . - + ignoredBuiltDependencies: - - '@clerk/shared' + - "@clerk/shared" - sharp - unrs-resolver diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 0000000..1dd0b20 --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,10 @@ +/** @type {import("prettier").Config} */ +const config = { + printWidth: 100, + semi: true, + singleQuote: false, + trailingComma: "all", + plugins: ["prettier-plugin-tailwindcss"], +}; + +export default config; diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index b17162e..ac2d34b 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -25,7 +25,7 @@ export async function POST(req: Request) { } catch (error) { return Response.json( { error: error instanceof Error ? error.message : "Missing model config" }, - { status: 500 } + { status: 500 }, ); } diff --git a/src/app/chat/[chat_id]/page.tsx b/src/app/chat/[chat_id]/page.tsx index 09d1e06..a981293 100644 --- a/src/app/chat/[chat_id]/page.tsx +++ b/src/app/chat/[chat_id]/page.tsx @@ -5,6 +5,7 @@ import { useQueryClient } from "@tanstack/react-query"; import type { UIMessage } from "ai"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; + import AgentMvpPanel from "@/app/components/AgentMvpPanel"; import ErrorDisplay from "@/app/components/ErrorDisplay"; import InputField from "@/app/components/InputField"; @@ -14,10 +15,10 @@ import ModelSelector from "@/app/components/ModelSelector"; import { IconFlask, IconMoon, IconSun } from "@/components/icons"; import { useTheme } from "@/components/ThemeProvider"; import { runAgentPipelineForChat } from "@/lib/agent/chatPipeline"; -import { createLocalChat, getChatScope, updateLocalChat } from "@/lib/chatStore"; +import type { AgentErrorCode, AgentRunStatus } from "@/lib/agent/types"; import { getFirstUserMessageText, getMessageText } from "@/lib/chatMessages"; import { readStoredMessages, writeStoredMessages } from "@/lib/chatMessageStorage"; -import type { AgentErrorCode, AgentRunStatus } from "@/lib/agent/types"; +import { createLocalChat, getChatScope, updateLocalChat } from "@/lib/chatStore"; import { setGlobalChatModel, useGlobalChatModel } from "@/lib/model/globalModel"; import { recordClientEvent } from "@/lib/observability/clientEvents"; import { useHydrated } from "@/lib/useHydrated"; @@ -117,7 +118,7 @@ function ChatSession({ const isLoading = status === "streaming" || status === "submitted"; const firstUserTitle = useMemo( () => getFirstUserMessageText(safeMessages).slice(0, 40) || "新对话", - [safeMessages] + [safeMessages], ); const latestAssistantTextLength = useMemo(() => { for (let index = safeMessages.length - 1; index >= 0; index -= 1) { @@ -199,7 +200,7 @@ function ChatSession({ const query = new URLSearchParams({ q: text }).toString(); router.replace(`/chat/${created.id}?${query}`); }, - [chatScope, globalModel, queryClient, router] + [chatScope, globalModel, queryClient, router], ); const sendWithPipeline = useCallback( @@ -208,7 +209,7 @@ function ChatSession({ options?: { mode: "send" | "regenerate"; assistantMessageId?: string; - } + }, ) => { const requestId = `req_${Date.now()}_${Math.random().toString(16).slice(2)}`; const previousPipeline = activePipelineRef.current; @@ -303,7 +304,7 @@ function ChatSession({ sendMessage({ text }, { body: requestBody }); }, - [globalModel, regenerate, sendMessage, sessionId] + [globalModel, regenerate, sendMessage, sessionId], ); useEffect(() => { @@ -313,9 +314,7 @@ function ChatSession({ if (autoSend && !hasAutoSentRef.current) { hasAutoSentRef.current = true; void createChatAndRedirect(initialPrompt.trim()).catch((submitError) => { - setLocalActionError( - submitError instanceof Error ? submitError.message : "创建会话失败" - ); + setLocalActionError(submitError instanceof Error ? submitError.message : "创建会话失败"); }); } }, [autoSend, createChatAndRedirect, initialPrompt, isDraftSession]); @@ -332,14 +331,7 @@ function ChatSession({ void sendWithPipeline(prompt); }); router.replace(`/chat/${routeChatId}`); - }, [ - initialPrompt, - isDraftSession, - messageCount, - routeChatId, - router, - sendWithPipeline, - ]); + }, [initialPrompt, isDraftSession, messageCount, routeChatId, router, sendWithPipeline]); useEffect(() => { if (isDraftSession) return; @@ -374,9 +366,7 @@ function ChatSession({ if (isDraftSession) { void createChatAndRedirect(text).catch((submitError) => { - setLocalActionError( - submitError instanceof Error ? submitError.message : "创建会话失败" - ); + setLocalActionError(submitError instanceof Error ? submitError.message : "创建会话失败"); }); return; } @@ -405,7 +395,7 @@ function ChatSession({ return (
-
+

@@ -505,7 +495,7 @@ function ChatSession({

-
+
getAgentApiBaseUrl(), []); const configuredToolName = useMemo(() => getAgentToolName(), []); const [activeScenario, setActiveScenario] = useState( - adapterMode === "http" ? "remote_call" : "success" + adapterMode === "http" ? "remote_call" : "success", ); const [isRunning, setIsRunning] = useState(false); const [runState, setRunState] = useState(null); @@ -102,7 +103,7 @@ export default function AgentMvpPanel({ mockMode: "fail", }, }, - [adapterMode] + [adapterMode], ); const runScenario = async (scenario: AgentScenario) => { @@ -161,7 +162,7 @@ export default function AgentMvpPanel({ const stepState = runState?.steps[0] ?? null; const scenarioList = useMemo( () => Object.entries(scenarioConfig) as Array<[AgentScenario, ScenarioConfig]>, - [scenarioConfig] + [scenarioConfig], ); const isHttpMode = adapterMode === "http"; const isHttpConfigured = !isHttpMode || Boolean(configuredApiBaseUrl); @@ -170,7 +171,9 @@ export default function AgentMvpPanel({
-

Agent MVP 演示面板

+

+ Agent MVP 演示面板 +

{isHttpMode ? "当前为 HTTP 联调模式:将调用后端 MCP 工具接口。" @@ -246,7 +249,9 @@ export default function AgentMvpPanel({

{config.label}

{config.description} @@ -264,14 +269,16 @@ export default function AgentMvpPanel({

-

Run 状态

+

+ Run 状态 +

- {isRunning ? "running" : runState?.status ?? "idle"} + {isRunning ? "running" : (runState?.status ?? "idle")} events:{runState?.events.length ?? 0} @@ -286,16 +293,16 @@ export default function AgentMvpPanel({
-

+

Step 状态

- {isRunning ? "running" : stepState?.status ?? "pending"} + {isRunning ? "running" : (stepState?.status ?? "pending")} attempts:{stepState?.attempt ?? 0} @@ -314,7 +321,7 @@ export default function AgentMvpPanel({
-

+

事件轨迹(最近一次执行)

{runState?.events.length ? ( diff --git a/src/app/components/InputField.tsx b/src/app/components/InputField.tsx index acb8c1d..a12569d 100644 --- a/src/app/components/InputField.tsx +++ b/src/app/components/InputField.tsx @@ -1,6 +1,7 @@ "use client"; -import { FormEvent } from "react"; +import { type FormEvent } from "react"; + import { IconArrowRight, IconStop } from "@/components/icons"; type InputFieldProps = { @@ -46,9 +47,7 @@ export default function InputField({ className="w-full resize-none bg-transparent px-1 text-sm leading-6 text-slate-800 outline-none placeholder:text-slate-400 dark:text-slate-100 dark:placeholder:text-slate-500" />
-

- Enter 发送,Shift + Enter 换行 -

+

Enter 发送,Shift + Enter 换行

{isLoading && onStop ? ( @@ -170,7 +168,7 @@ const MessageCard = memo(function MessageCard({
{isAssistant ? ( -
+
) : ( -

{content}

+

{content}

)}
diff --git a/src/app/components/ModelSelector.tsx b/src/app/components/ModelSelector.tsx index 4b89d00..6ca22ac 100644 --- a/src/app/components/ModelSelector.tsx +++ b/src/app/components/ModelSelector.tsx @@ -1,9 +1,6 @@ "use client"; -import { - SUPPORTED_CHAT_MODELS, - type SupportedChatModel, -} from "@/lib/model/models"; +import { SUPPORTED_CHAT_MODELS, type SupportedChatModel } from "@/lib/model/models"; type ModelSelectorProps = { value: SupportedChatModel; @@ -16,11 +13,7 @@ const MODEL_LABELS: Record = { "deepseek-r1": "DeepSeek R1", }; -export default function ModelSelector({ - value, - onChange, - disabled = false, -}: ModelSelectorProps) { +export default function ModelSelector({ value, onChange, disabled = false }: ModelSelectorProps) { return (