From 42811a27f59df99a692d68a1ee9b23f4d66f3ccd Mon Sep 17 00:00:00 2001 From: ProtectCookies <1439080921@qq.com> Date: Thu, 3 Sep 2026 10:31:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Commit=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=8F=8A=E6=9C=AC=E5=9C=B0=E6=8F=90=E4=BA=A4=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CommitSettingsCard 组件,支持选择生成模型与编辑提示词 - 新增 /settings/commit GET/PUT 接口及 CommitSettings 持久化 - 实现 WriteGitCommitMessage RPC 本地生成,空 model_id 时直连转发 - 新增 NetworkService/IsConnected 探针响应,防止流式生成被中断 - 添加 commit prompt 模板及 proto 消息定义 - 补充 zh-CN / en-US 国际化词条 --- .../settings/CommitSettingsCard.module.scss | 53 +++ .../features/settings/CommitSettingsCard.tsx | 153 +++++++ .../src/features/settings/SettingsPage.tsx | 2 + apps/desktop/src/i18n/generated/catalog.json | 219 +++++++--- apps/desktop/src/i18n/locales/en-US.json | 7 + apps/desktop/src/i18n/locales/zh-CN.json | 7 + apps/desktop/src/shared/api.ts | 11 + server/prompt/cursor/commit/prompt.md | 66 +++ server/src/api/cursor/handlers.rs | 27 +- server/src/control/mod.rs | 4 + server/src/control/service.rs | 12 +- server/src/control/settings.rs | 39 +- server/src/cursor/protocol/proto.rs | 35 ++ server/src/cursor/services/commit_message.rs | 406 ++++++++++++++++++ server/src/cursor/services/mod.rs | 1 + server/src/cursor/services/tab.rs | 3 +- server/src/local_app/proxy.rs | 2 + server/src/store/settings.rs | 62 +++ server/tests/commit_message.rs | 246 +++++++++++ 19 files changed, 1292 insertions(+), 63 deletions(-) create mode 100644 apps/desktop/src/features/settings/CommitSettingsCard.module.scss create mode 100644 apps/desktop/src/features/settings/CommitSettingsCard.tsx create mode 100644 server/prompt/cursor/commit/prompt.md create mode 100644 server/src/cursor/services/commit_message.rs create mode 100644 server/tests/commit_message.rs diff --git a/apps/desktop/src/features/settings/CommitSettingsCard.module.scss b/apps/desktop/src/features/settings/CommitSettingsCard.module.scss new file mode 100644 index 0000000..17a8905 --- /dev/null +++ b/apps/desktop/src/features/settings/CommitSettingsCard.module.scss @@ -0,0 +1,53 @@ +@use "../../styles/typography" as type; + +.row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 16px; + + > div:first-child { + display: grid; + gap: 5px; + } + + small { + color: var(--vscode-descriptionForeground); + font-size: type.$font-size-xs; + } +} + +.select { + min-width: 280px; +} + +.textButton { + padding: 4px; + color: var(--vscode-textLink-foreground); + background: transparent; + border: 0; + + &:hover { color: var(--vscode-textLink-activeForeground); } + &:disabled { opacity: 0.5; cursor: not-allowed; } +} + +.promptEditor { + width: 100%; + flex: 1; + min-height: 320px; + box-sizing: border-box; + padding: 10px 12px; + color: var(--vscode-input-foreground); + background: var(--vscode-input-background); + border: 1px solid var(--vscode-input-border, var(--vscode-sideBar-border)); + border-radius: 6px; + font-family: var(--vscode-editor-font-family, monospace); + font-size: type.$font-size-xs; + line-height: 1.6; + resize: none; + + &:focus { + outline: 1px solid var(--vscode-focusBorder); + } +} diff --git a/apps/desktop/src/features/settings/CommitSettingsCard.tsx b/apps/desktop/src/features/settings/CommitSettingsCard.tsx new file mode 100644 index 0000000..2702173 --- /dev/null +++ b/apps/desktop/src/features/settings/CommitSettingsCard.tsx @@ -0,0 +1,153 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { api, type CommitSettingsView } from "../../shared/api"; +import { useAppStore } from "../../shared/store/appStore"; +import { Modal } from "../../shared/ui/Modal"; +import { Select } from "../../shared/ui/Select"; +import { TitledCard } from "../../shared/ui/TitledCard"; +import { useMessage } from "../../shared/ui/message"; +import controls from "../../shared/ui/Controls.module.scss"; +import styles from "./CommitSettingsCard.module.scss"; + +function errorText(cause: unknown) { + return cause instanceof Error ? cause.message : String(cause); +} + +export function CommitSettingsCard() { + const { models } = useAppStore(); + const message = useMessage(); + const [view, setView] = useState(null); + const [savingModel, setSavingModel] = useState(false); + const [promptOpen, setPromptOpen] = useState(false); + const [promptDraft, setPromptDraft] = useState(""); + const [savingPrompt, setSavingPrompt] = useState(false); + + useEffect(() => { + void api.commitSettings().then(setView).catch((cause) => message(errorText(cause))); + }, [message]); + + const modelOptions = useMemo(() => { + const options: Array<{ value: string; label: string }> = [{ value: "", label: t("直连") }]; + const seen = new Set(); + for (const model of models) { + seen.add(model.model_hash); + options.push({ + value: model.model_hash, + label: model.display_name && model.display_name !== model.model_id + ? `${model.display_name}(${model.model_id})` + : model.display_name || model.model_id, + }); + } + if (view?.model_id && !seen.has(view.model_id)) { + options.push({ value: view.model_id, label: view.model_id }); + } + return options; + }, [models, view]); + + const selectedModelId = view?.model_id ?? ""; + + const persist = useCallback( + async (modelId: string, prompt: string) => { + if (!view) return null; + const normalizedPrompt = + prompt.trim() === view.default_prompt.trim() ? "" : prompt.trim(); + return api.setCommitSettings({ model_id: modelId, prompt: normalizedPrompt }); + }, + [view], + ); + + const changeModel = useCallback( + async (modelId: string) => { + if (!view) return; + setSavingModel(true); + try { + const saved = await persist(modelId, view.prompt); + if (saved) setView(saved); + } catch (cause) { + message(errorText(cause)); + } finally { + setSavingModel(false); + } + }, + [view, persist, message], + ); + + const openPrompt = useCallback(() => { + if (!view) return; + setPromptDraft(view.prompt || view.default_prompt); + setPromptOpen(true); + }, [view]); + + const savePrompt = useCallback(async () => { + if (!view) return; + setSavingPrompt(true); + try { + const saved = await persist(view.model_id, promptDraft); + if (saved) setView(saved); + setPromptOpen(false); + message(t("提示词设置已保存")); + } catch (cause) { + message(errorText(cause)); + } finally { + setSavingPrompt(false); + } + }, [view, promptDraft, persist, message]); + + const resetPrompt = useCallback(() => { + if (!view) return; + setPromptDraft(view.default_prompt); + }, [view]); + + return ( + <> + + {t("提示词设置")} + + } + > +
+
+ {t("生成模型")} + {t("直连走 Cursor 原有通道;选择 Cursor 页已配置的模型后由本地生成。")} +
+
+