mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
feat(pi): expose explicit prompt library reconciliation
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FileText } from "lucide-react";
|
||||
import { AlertTriangle, FileText, RefreshCw } from "lucide-react";
|
||||
import { type AppId } from "@/lib/api";
|
||||
import { usePromptActions } from "@/hooks/usePromptActions";
|
||||
import { useTauriEvent } from "@/hooks/useTauriEvent";
|
||||
@@ -8,6 +8,7 @@ import PromptListItem from "./PromptListItem";
|
||||
import PromptFormPanel from "./PromptFormPanel";
|
||||
import { PiNativePromptResources } from "./PiNativePromptResources";
|
||||
import { ConfirmDialog } from "../ConfirmDialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface PromptPanelProps {
|
||||
open: boolean;
|
||||
@@ -35,10 +36,12 @@ const PromptPanel = React.forwardRef<PromptPanelHandle, PromptPanelProps>(
|
||||
const {
|
||||
prompts,
|
||||
loading,
|
||||
piLibraryStatus,
|
||||
reload,
|
||||
savePrompt,
|
||||
deletePrompt,
|
||||
toggleEnabled,
|
||||
reconcilePiLibrary,
|
||||
} = usePromptActions(appId);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -123,6 +126,43 @@ const PromptPanel = React.forwardRef<PromptPanelHandle, PromptPanelProps>(
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{appId === "pi" && piLibraryStatus?.needsReconciliation && (
|
||||
<div
|
||||
className="mb-4 flex items-start justify-between gap-3 rounded-lg border border-amber-500/30 bg-amber-500/10 p-3"
|
||||
role="status"
|
||||
>
|
||||
<div className="flex min-w-0 gap-2">
|
||||
<AlertTriangle
|
||||
className="mt-0.5 h-4 w-4 shrink-0 text-amber-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div>
|
||||
<p className="text-sm font-medium">
|
||||
{t("pi.prompts.libraryDriftTitle")}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{t(
|
||||
piLibraryStatus.nativeExists
|
||||
? "pi.prompts.libraryDriftNative"
|
||||
: "pi.prompts.libraryDriftMissing",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="shrink-0"
|
||||
onClick={() => {
|
||||
void reconcilePiLibrary().catch(() => undefined);
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="mr-2 h-3.5 w-3.5" aria-hidden="true" />
|
||||
{t("pi.prompts.reconcileLibrary")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{loading ? (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
{t("prompts.loading")}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { promptsApi, type Prompt, type AppId } from "@/lib/api";
|
||||
import {
|
||||
promptsApi,
|
||||
type Prompt,
|
||||
type AppId,
|
||||
type PiPromptLibraryStatus,
|
||||
} from "@/lib/api";
|
||||
|
||||
export function usePromptActions(appId: AppId) {
|
||||
const { t } = useTranslation();
|
||||
@@ -10,12 +15,19 @@ export function usePromptActions(appId: AppId) {
|
||||
const [currentFileContent, setCurrentFileContent] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [piLibraryStatus, setPiLibraryStatus] =
|
||||
useState<PiPromptLibraryStatus | null>(null);
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await promptsApi.getPrompts(appId);
|
||||
setPrompts(data);
|
||||
if (appId === "pi") {
|
||||
setPiLibraryStatus(await promptsApi.getPiPromptLibraryStatus());
|
||||
} else {
|
||||
setPiLibraryStatus(null);
|
||||
}
|
||||
|
||||
// 同时加载当前文件内容
|
||||
try {
|
||||
@@ -138,15 +150,28 @@ export function usePromptActions(appId: AppId) {
|
||||
}
|
||||
}, [appId, reload, t]);
|
||||
|
||||
const reconcilePiLibrary = useCallback(async () => {
|
||||
try {
|
||||
await promptsApi.reconcilePiPromptLibrary();
|
||||
await reload();
|
||||
toast.success(t("pi.prompts.libraryReconciled"), { closeButton: true });
|
||||
} catch (error) {
|
||||
toast.error(t("pi.prompts.libraryReconcileFailed"));
|
||||
throw error;
|
||||
}
|
||||
}, [reload, t]);
|
||||
|
||||
return {
|
||||
prompts,
|
||||
loading,
|
||||
currentFileContent,
|
||||
piLibraryStatus,
|
||||
reload,
|
||||
savePrompt,
|
||||
deletePrompt,
|
||||
enablePrompt,
|
||||
toggleEnabled,
|
||||
importFromFile,
|
||||
reconcilePiLibrary,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -962,6 +962,12 @@
|
||||
"nativeDescription": "File presence is activation. SYSTEM.md replaces Pi's system prompt, APPEND_SYSTEM.md appends to it, and /template-name expands files from the prompts directory.",
|
||||
"agentsLibrary": "AGENTS.md library",
|
||||
"agentsLibraryDescription": "The enabled library entry is projected to AGENTS.md. External edits are detected and must be imported before switching.",
|
||||
"libraryDriftTitle": "AGENTS.md and the library differ",
|
||||
"libraryDriftNative": "Pi is using native AGENTS.md content that is not the library's saved selection. Reconcile to import and select the exact live content.",
|
||||
"libraryDriftMissing": "AGENTS.md is absent, so Pi is not using a global prompt even though the library still has an enabled selection.",
|
||||
"reconcileLibrary": "Reconcile",
|
||||
"libraryReconciled": "AGENTS.md library reconciled with native Pi state",
|
||||
"libraryReconcileFailed": "Failed to reconcile the AGENTS.md library",
|
||||
"systemOverride": "System override",
|
||||
"systemOverrideDescription": "SYSTEM.md completely replaces Pi's built-in system prompt while the file exists.",
|
||||
"systemAppend": "System append",
|
||||
|
||||
@@ -962,6 +962,12 @@
|
||||
"nativeDescription": "文件存在即启用。SYSTEM.md 替换 Pi 系统提示,APPEND_SYSTEM.md 追加系统提示,/模板名 会展开 prompts 目录中的文件。",
|
||||
"agentsLibrary": "AGENTS.md 提示库",
|
||||
"agentsLibraryDescription": "已启用的提示库条目会投影到 AGENTS.md;检测到外部修改时,必须先导入才能切换。",
|
||||
"libraryDriftTitle": "AGENTS.md 与提示库不一致",
|
||||
"libraryDriftNative": "Pi 正在使用的原生 AGENTS.md 内容不是提示库中保存的选中项。协调后会导入并选中完全一致的实时内容。",
|
||||
"libraryDriftMissing": "AGENTS.md 不存在,因此 Pi 当前没有使用全局提示词,但提示库仍保留了启用项。",
|
||||
"reconcileLibrary": "协调",
|
||||
"libraryReconciled": "AGENTS.md 提示库已与 Pi 原生状态协调",
|
||||
"libraryReconcileFailed": "协调 AGENTS.md 提示库失败",
|
||||
"systemOverride": "系统提示替换",
|
||||
"systemOverrideDescription": "SYSTEM.md 存在期间会完整替换 Pi 内置系统提示。",
|
||||
"systemAppend": "系统提示追加",
|
||||
|
||||
@@ -23,7 +23,7 @@ export * as configApi from "./config";
|
||||
export * as authApi from "./auth";
|
||||
export * as copilotApi from "./copilot";
|
||||
export type { ProviderSwitchEvent } from "./providers";
|
||||
export type { Prompt } from "./prompts";
|
||||
export type { PiPromptLibraryStatus, Prompt } from "./prompts";
|
||||
export type { Profile, ProfilePayload, ProfilesResponse } from "./profiles";
|
||||
export type {
|
||||
CopilotDeviceCodeResponse,
|
||||
|
||||
@@ -30,6 +30,13 @@ export interface PiPromptTemplate {
|
||||
revision: string;
|
||||
}
|
||||
|
||||
export interface PiPromptLibraryStatus {
|
||||
nativeExists: boolean;
|
||||
nativeRevision: string;
|
||||
matchedPromptId: string | null;
|
||||
needsReconciliation: boolean;
|
||||
}
|
||||
|
||||
export const promptsApi = {
|
||||
async getPrompts(app: AppId): Promise<Record<string, Prompt>> {
|
||||
return await invoke("get_prompts", { app });
|
||||
@@ -55,6 +62,14 @@ export const promptsApi = {
|
||||
return await invoke("get_current_prompt_file_content", { app });
|
||||
},
|
||||
|
||||
async getPiPromptLibraryStatus(): Promise<PiPromptLibraryStatus> {
|
||||
return await invoke("get_pi_prompt_library_status");
|
||||
},
|
||||
|
||||
async reconcilePiPromptLibrary(): Promise<void> {
|
||||
return await invoke("reconcile_pi_prompt_library");
|
||||
},
|
||||
|
||||
async getPiPromptFile(kind: PiPromptFileKind): Promise<PiPromptFileSnapshot> {
|
||||
return await invoke("get_pi_prompt_file", { kind });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user