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 页已配置的模型后由本地生成。")} +
+
+