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:
152
src/hooks/usePromptActions.ts
Normal file
152
src/hooks/usePromptActions.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user