feat(prompts+i18n): add prompt management and improve prompt editor i18n (#193)

* feat(prompts): add prompt management across Tauri service and React UI

- backend: add commands/prompt.rs, services/prompt.rs, register in commands/mod.rs and lib.rs, refine app_config.rs
- frontend: add PromptPanel, PromptFormModal, PromptListItem, MarkdownEditor, usePromptActions, integrate in App.tsx
- api: add src/lib/api/prompts.ts
- i18n: update src/i18n/locales/{en,zh}.json
- build: update package.json and pnpm-lock.yaml

* feat(i18n): improve i18n for prompts and Markdown editor

- update src/i18n/locales/{en,zh}.json keys and strings
- apply i18n in PromptFormModal, PromptPanel, and MarkdownEditor
- align prompt text with src-tauri/src/services/prompt.rs

* feat(prompts): add enable/disable toggle and simplify panel UI

- Add PromptToggle component and integrate in prompt list items
- Implement toggleEnabled with optimistic update; enable via API, disable via upsert with enabled=false;
  reload after success
- Simplify PromptPanel: remove file import and current-file preview to keep CRUD flow focused
- Tweak header controls style (use mcp variant) and minor copy: rename “Prompt Management” to “Prompts”
- i18n: add disableSuccess/disableFailed messages
- Backend (Tauri): prevent duplicate backups when importing original prompt content

* style: unify code formatting with trailing commas

* feat(prompts): add Gemini filename support to PromptFormModal

Update filename mapping to use Record<AppId, string> pattern, supporting
GEMINI.md alongside CLAUDE.md and AGENTS.md.

* fix(prompts): sync enabled prompt to file when updating

When updating a prompt that is currently enabled, automatically sync
the updated content to the corresponding live file (CLAUDE.md/AGENTS.md/GEMINI.md).

This ensures the active prompt file always reflects the latest content
when editing enabled prompts.
This commit is contained in:
YoVinchen
2025-11-12 16:41:41 +08:00
committed by GitHub
parent 346f916048
commit 155532ea8c
20 changed files with 1302 additions and 1 deletions

View File

@@ -0,0 +1,152 @@
import { useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { promptsApi, type Prompt, type AppId } from "@/lib/api";
export function usePromptActions(appId: AppId) {
const { t } = useTranslation();
const [prompts, setPrompts] = useState<Record<string, Prompt>>({});
const [loading, setLoading] = useState(false);
const [currentFileContent, setCurrentFileContent] = useState<string | null>(
null,
);
const reload = useCallback(async () => {
setLoading(true);
try {
const data = await promptsApi.getPrompts(appId);
setPrompts(data);
// 同时加载当前文件内容
try {
const content = await promptsApi.getCurrentFileContent(appId);
setCurrentFileContent(content);
} catch (error) {
setCurrentFileContent(null);
}
} catch (error) {
toast.error(t("prompts.loadFailed"));
} finally {
setLoading(false);
}
}, [appId, t]);
const savePrompt = useCallback(
async (id: string, prompt: Prompt) => {
try {
await promptsApi.upsertPrompt(appId, id, prompt);
await reload();
toast.success(t("prompts.saveSuccess"));
} catch (error) {
toast.error(t("prompts.saveFailed"));
throw error;
}
},
[appId, reload, t],
);
const deletePrompt = useCallback(
async (id: string) => {
try {
await promptsApi.deletePrompt(appId, id);
await reload();
toast.success(t("prompts.deleteSuccess"));
} catch (error) {
toast.error(t("prompts.deleteFailed"));
throw error;
}
},
[appId, reload, t],
);
const enablePrompt = useCallback(
async (id: string) => {
try {
await promptsApi.enablePrompt(appId, id);
await reload();
toast.success(t("prompts.enableSuccess"));
} catch (error) {
toast.error(t("prompts.enableFailed"));
throw error;
}
},
[appId, reload, t],
);
const toggleEnabled = useCallback(
async (id: string, enabled: boolean) => {
// Optimistic update
const previousPrompts = prompts;
// 如果要启用当前提示词,先禁用其他所有提示词
if (enabled) {
const updatedPrompts = Object.keys(prompts).reduce(
(acc, key) => {
acc[key] = {
...prompts[key],
enabled: key === id,
};
return acc;
},
{} as Record<string, Prompt>,
);
setPrompts(updatedPrompts);
} else {
setPrompts((prev) => ({
...prev,
[id]: {
...prev[id],
enabled: false,
},
}));
}
try {
if (enabled) {
await promptsApi.enablePrompt(appId, id);
toast.success(t("prompts.enableSuccess"));
} else {
// 禁用提示词 - 需要后端支持
await promptsApi.upsertPrompt(appId, id, {
...prompts[id],
enabled: false,
});
toast.success(t("prompts.disableSuccess"));
}
await reload();
} catch (error) {
// Rollback on failure
setPrompts(previousPrompts);
toast.error(
enabled ? t("prompts.enableFailed") : t("prompts.disableFailed"),
);
throw error;
}
},
[appId, prompts, reload, t],
);
const importFromFile = useCallback(async () => {
try {
const id = await promptsApi.importFromFile(appId);
await reload();
toast.success(t("prompts.importSuccess"));
return id;
} catch (error) {
toast.error(t("prompts.importFailed"));
throw error;
}
}, [appId, reload, t]);
return {
prompts,
loading,
currentFileContent,
reload,
savePrompt,
deletePrompt,
enablePrompt,
toggleEnabled,
importFromFile,
};
}