feat(pi): expose explicit prompt library reconciliation

This commit is contained in:
SaladDay
2026-08-03 03:23:57 +00:00
parent 2ba603fb77
commit 914fc99b4c
10 changed files with 341 additions and 4 deletions
+41 -1
View File
@@ -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")}
+26 -1
View File
@@ -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,
};
}
+6
View File
@@ -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",
+6
View File
@@ -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": "系统提示追加",
+1 -1
View File
@@ -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,
+15
View File
@@ -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 });
},