@rexvane/piweb 0.3.11 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +6 -5
- package/.next/build-manifest.json +6 -6
- package/.next/prerender-manifest.json +7 -7
- package/.next/react-loadable-manifest.json +6 -6
- package/.next/routes-manifest.json +6 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/git/route.js +2 -2
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/prompts/route.js +1 -0
- package/.next/server/app/api/prompts/route.js.nft.json +1 -0
- package/.next/server/app/api/prompts/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/security/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/web-auth/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js +1 -1
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +6 -5
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/686.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/2kt2UiWjv_viXpNlfjvBT/_buildManifest.js +1 -0
- package/.next/static/chunks/2155.2f27fe7098987699.js +1 -0
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +1 -0
- package/.next/static/chunks/2716.a9e808190a4e2424.js +1 -0
- package/.next/static/chunks/{794-d1a40dcf24d651d9.js → 3794-3679f1c05160977c.js} +1 -1
- package/.next/static/chunks/4127.dff5c3b33c6bfd1b.js +1 -0
- package/.next/static/chunks/4673.d3d0a7f2599080fb.js +1 -0
- package/.next/static/chunks/4686-5bbee099126af797.js +1 -0
- package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +1 -0
- package/.next/static/chunks/app/_global-error/page-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/_not-found/page-2d40d1d7d916666a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/config/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/files/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/git/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/growth/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/health/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/models/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/security/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/skills/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/update/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/version/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-961c055baebb3872.js +1 -0
- package/.next/static/chunks/app/layout-50d06e2078d6ce76.js +1 -0
- package/.next/static/chunks/app/login/page-52ef4841cbd199a8.js +1 -0
- package/.next/static/chunks/app/page-00c8ebf40ec5eaca.js +1 -0
- package/.next/static/chunks/framework-c4e651fe89edfa91.js +1 -0
- package/.next/static/chunks/{main-a315d7c58b599962.js → main-6159b22a8cb0b7c5.js} +2 -2
- package/.next/static/chunks/main-app-e9b53f0e1c6591b2.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-961c055baebb3872.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-961c055baebb3872.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-a7e260f8cc2582d7.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-961c055baebb3872.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-961c055baebb3872.js +1 -0
- package/.next/static/chunks/webpack-05a9e85529a18b93.js +1 -0
- package/.next/types/app/api/prompts/route.ts +353 -0
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/package.json +1 -1
- package/src/app/api/prompts/route.ts +51 -0
- package/src/components/AppShell.tsx +42 -19
- package/src/components/ChatWindow.tsx +20 -35
- package/src/components/PromptPanel.tsx +257 -0
- package/src/components/SettingsPanel.tsx +59 -56
- package/src/i18n.tsx +38 -4
- package/src/lib/agent-manager.ts +35 -8
- package/src/lib/clipboard.ts +27 -0
- package/src/lib/command-validation.ts +1 -0
- package/src/lib/prompt-sources.ts +201 -0
- package/src/lib/types.ts +1 -0
- package/.next/static/222KLUIvefiGf7M0Ov-Qz/_buildManifest.js +0 -1
- package/.next/static/chunks/127.c31e688e156605e3.js +0 -1
- package/.next/static/chunks/155.a3c40ae481da1358.js +0 -1
- package/.next/static/chunks/4bd1b696-92152b0f5947070d.js +0 -1
- package/.next/static/chunks/608.5b8aae42a9998ee7.js +0 -1
- package/.next/static/chunks/673.93445c8600bc1dd9.js +0 -1
- package/.next/static/chunks/686-9f15e4e370d26815.js +0 -1
- package/.next/static/chunks/716.5266fe10b5578591.js +0 -1
- package/.next/static/chunks/app/_global-error/page-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/_not-found/page-9de30f6f614fce02.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/config/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/files/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/git/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/growth/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/health/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/models/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/security/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/skills/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/update/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/version/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/layout-eb62bcd99abe79f9.js +0 -1
- package/.next/static/chunks/app/login/page-d2308b48d013d1a6.js +0 -1
- package/.next/static/chunks/app/page-708cd3ee4cfe8af5.js +0 -1
- package/.next/static/chunks/framework-0f597d6e68ffafed.js +0 -1
- package/.next/static/chunks/main-app-1a92e20ec190a32b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-7bb5c4afcbc1772b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/webpack-9d84b382d4dec27a.js +0 -1
- /package/.next/static/{222KLUIvefiGf7M0Ov-Qz → 2kt2UiWjv_viXpNlfjvBT}/_ssgManifest.js +0 -0
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "@/components/icons";
|
|
29
29
|
import { DiffView, type DiffLine, parseUnifiedDiff } from "@/components/DiffView";
|
|
30
30
|
import { languageForPath } from "@/lib/highlight";
|
|
31
|
+
import { copyText } from "@/lib/clipboard";
|
|
31
32
|
import { OutlineRail } from "@/components/OutlineRail";
|
|
32
33
|
import { useI18n } from "@/i18n";
|
|
33
34
|
import type { ToolCardState } from "@/hooks/usePiWeb";
|
|
@@ -53,29 +54,6 @@ import {
|
|
|
53
54
|
type Dict = Record<string, string>;
|
|
54
55
|
type ToolCallContent = Extract<WebContent, { type: "toolCall" }>;
|
|
55
56
|
|
|
56
|
-
async function copyText(text: string): Promise<boolean> {
|
|
57
|
-
try {
|
|
58
|
-
if (navigator.clipboard?.writeText) {
|
|
59
|
-
await navigator.clipboard.writeText(text);
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
} catch {
|
|
63
|
-
// Non-secure local-network contexts need the legacy fallback below.
|
|
64
|
-
}
|
|
65
|
-
const field = document.createElement("textarea");
|
|
66
|
-
field.value = text;
|
|
67
|
-
field.style.position = "fixed";
|
|
68
|
-
field.style.opacity = "0";
|
|
69
|
-
document.body.appendChild(field);
|
|
70
|
-
field.select();
|
|
71
|
-
try {
|
|
72
|
-
return document.execCommand("copy");
|
|
73
|
-
} catch {
|
|
74
|
-
return false;
|
|
75
|
-
} finally {
|
|
76
|
-
field.remove();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
57
|
|
|
80
58
|
/** dsh message-chrome formatRunDuration:整秒,分钟档秒数补零 */
|
|
81
59
|
function formatRunDuration(ms: number, t: Dict): string {
|
|
@@ -112,17 +90,24 @@ const Markdown = memo(function Markdown({ text }: { text: string }) {
|
|
|
112
90
|
);
|
|
113
91
|
});
|
|
114
92
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 流式正文:边流边按 Markdown 渲染(标题/列表/代码块在生成过程中就成形)。
|
|
96
|
+
*
|
|
97
|
+
* 不是每个 delta 都重解析整段——那样会随回答变长线性变慢;这里按 ~100ms 节流,
|
|
98
|
+
* 只在最新文本稳定下来时才交给 Markdown,回合结束时外层会切到精确的最终渲染。
|
|
99
|
+
*/
|
|
100
|
+
const STREAM_MARKDOWN_THROTTLE_MS = 100;
|
|
101
|
+
|
|
102
|
+
const StreamingMarkdown = memo(function StreamingMarkdown({ text }: { text: string }) {
|
|
103
|
+
const [shown, setShown] = useState(text);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (text === shown) return;
|
|
106
|
+
const timer = setTimeout(() => setShown(text), STREAM_MARKDOWN_THROTTLE_MS);
|
|
107
|
+
return () => clearTimeout(timer);
|
|
108
|
+
}, [text, shown]);
|
|
109
|
+
return <Markdown text={shown} />;
|
|
110
|
+
});
|
|
126
111
|
|
|
127
112
|
/** dsh TurnTimePanel 的简化版:耗时胶囊常驻,点击弹层看本回合 token 用量 */
|
|
128
113
|
function TurnMetaPill({ durationMs, usage, t }: { durationMs: number; usage?: TrajTokens & { cost?: { total?: number } }; t: Dict }) {
|
|
@@ -642,7 +627,7 @@ const ThinkStep = memo(function ThinkStep({ text, live, startedAt }: { text: str
|
|
|
642
627
|
const NarrationBlock = memo(function NarrationBlock({ text, live, error }: { text: string; live?: boolean; error?: boolean }) {
|
|
643
628
|
return (
|
|
644
629
|
<div className="pw-narr" data-error={error || undefined}>
|
|
645
|
-
{live ? <
|
|
630
|
+
{live ? <StreamingMarkdown text={text} /> : <Markdown text={text} />}
|
|
646
631
|
</div>
|
|
647
632
|
);
|
|
648
633
|
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* 提示词面板:把"发给模型的提示词从哪来"集中列出来,点某一项查看原文。
|
|
4
|
+
*
|
|
5
|
+
* 只读:文件类来源的路径来自 Pi 资源加载器(项目记忆 / 系统提示词覆盖 / 追加提示词 /
|
|
6
|
+
* 技能 / 提示模板,含扩展与 npm 包提供的那些);热会话额外给出"组装后的系统提示词"
|
|
7
|
+
* 与每个工具的定义,它们不是文件、没有路径,直接在本面板内展开查看。
|
|
8
|
+
*/
|
|
9
|
+
import { useCallback, useEffect, useState } from "react";
|
|
10
|
+
import { useI18n } from "@/i18n";
|
|
11
|
+
import { IconCopyOutline16, IconCheckOutline14, IconRefreshOutline14, IconCloseOutline14 } from "@/components/icons";
|
|
12
|
+
import { copyText } from "@/lib/clipboard";
|
|
13
|
+
|
|
14
|
+
export type PromptSourceKind = "system" | "append" | "agents" | "skill" | "template" | "tool" | "assembled";
|
|
15
|
+
|
|
16
|
+
export interface PromptSourceOrigin {
|
|
17
|
+
scope: "project" | "agent" | "package" | "extension";
|
|
18
|
+
name?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PromptSource {
|
|
22
|
+
kind: PromptSourceKind;
|
|
23
|
+
name: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
origin: PromptSourceOrigin;
|
|
26
|
+
bytes?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PromptToolView {
|
|
30
|
+
name: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
parameters?: unknown;
|
|
33
|
+
promptGuidelines?: string[];
|
|
34
|
+
source: "builtin" | "extension";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PromptSourcesData {
|
|
38
|
+
sources: PromptSource[];
|
|
39
|
+
assembledSystemPrompt?: string;
|
|
40
|
+
tools?: PromptToolView[];
|
|
41
|
+
sessionReady: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const GROUP_KINDS: PromptSourceKind[] = ["system", "append", "agents", "skill", "template", "tool", "assembled"];
|
|
45
|
+
|
|
46
|
+
function formatBytes(bytes: number | undefined): string {
|
|
47
|
+
if (!bytes) return "";
|
|
48
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
49
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
50
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function PromptPanel({
|
|
54
|
+
cwd,
|
|
55
|
+
sessionId,
|
|
56
|
+
refreshKey,
|
|
57
|
+
onOpenContent,
|
|
58
|
+
onClose,
|
|
59
|
+
}: {
|
|
60
|
+
cwd: string;
|
|
61
|
+
sessionId: string | null;
|
|
62
|
+
/** 变化即重新拉取(例如切换会话) */
|
|
63
|
+
refreshKey?: string | number | null;
|
|
64
|
+
/** 打开查看器显示内容(复用应用现有的文件查看器) */
|
|
65
|
+
onOpenContent?: (path: string, content: string) => void;
|
|
66
|
+
onClose: () => void;
|
|
67
|
+
}) {
|
|
68
|
+
const { t } = useI18n();
|
|
69
|
+
const [data, setData] = useState<PromptSourcesData | null>(null);
|
|
70
|
+
const [error, setError] = useState<string | null>(null);
|
|
71
|
+
const [busy, setBusy] = useState(false);
|
|
72
|
+
const [opening, setOpening] = useState<string | null>(null);
|
|
73
|
+
const [inline, setInline] = useState<{ title: string; content: string } | null>(null);
|
|
74
|
+
const [copied, setCopied] = useState(false);
|
|
75
|
+
|
|
76
|
+
const load = useCallback(async () => {
|
|
77
|
+
setBusy(true);
|
|
78
|
+
setError(null);
|
|
79
|
+
try {
|
|
80
|
+
const query = new URLSearchParams({ cwd });
|
|
81
|
+
if (sessionId) query.set("session", sessionId);
|
|
82
|
+
const response = await fetch(`/api/prompts?${query.toString()}`);
|
|
83
|
+
const body = await response.json();
|
|
84
|
+
if (!response.ok || !body.success) throw new Error(body.error || `request failed (${response.status})`);
|
|
85
|
+
setData(body.data as PromptSourcesData);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
88
|
+
} finally {
|
|
89
|
+
setBusy(false);
|
|
90
|
+
}
|
|
91
|
+
}, [cwd, sessionId]);
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
void load();
|
|
95
|
+
}, [load, refreshKey]);
|
|
96
|
+
|
|
97
|
+
const originLabel = (origin: PromptSourceOrigin): string => {
|
|
98
|
+
if (origin.scope === "project") return t.promptOriginProject;
|
|
99
|
+
if (origin.scope === "agent") return t.promptOriginAgent;
|
|
100
|
+
if (origin.scope === "package") return `${t.promptOriginPackage} ${origin.name ?? ""}`.trim();
|
|
101
|
+
return `${t.promptOriginExtension} ${origin.name ?? ""}`.trim();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const kindLabel = (kind: PromptSourceKind): string => {
|
|
105
|
+
switch (kind) {
|
|
106
|
+
case "system": return t.promptKindSystem;
|
|
107
|
+
case "append": return t.promptKindAppend;
|
|
108
|
+
case "agents": return t.promptKindAgents;
|
|
109
|
+
case "skill": return t.promptKindSkill;
|
|
110
|
+
case "template": return t.promptKindTemplate;
|
|
111
|
+
case "tool": return t.promptKindTool;
|
|
112
|
+
case "assembled": return t.promptKindAssembled;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** 文件类:按需读内容,交给外部查看器 */
|
|
117
|
+
const openFile = async (source: PromptSource) => {
|
|
118
|
+
if (!source.path || !onOpenContent) return;
|
|
119
|
+
setOpening(source.path);
|
|
120
|
+
setError(null);
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch("/api/prompts", {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: { "Content-Type": "application/json" },
|
|
125
|
+
body: JSON.stringify({ action: "read", filePath: source.path, cwd }),
|
|
126
|
+
});
|
|
127
|
+
const body = await response.json();
|
|
128
|
+
if (!response.ok || !body.success) throw new Error(body.error || "read failed");
|
|
129
|
+
onOpenContent(source.path, body.data.content as string);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
132
|
+
} finally {
|
|
133
|
+
setOpening(null);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const groups = GROUP_KINDS.map((kind) => ({ kind, items: (data?.sources ?? []).filter((source) => source.kind === kind) })).filter(
|
|
138
|
+
(group) => group.items.length > 0,
|
|
139
|
+
);
|
|
140
|
+
const hasTools = (data?.tools?.length ?? 0) > 0;
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
144
|
+
<div className="hairline-b flex items-center gap-2 px-4 py-2.5">
|
|
145
|
+
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 13.5, fontWeight: 600 }}>{t.promptPanel}</span>
|
|
146
|
+
<button type="button" className="icon-btn" style={{ width: 26, height: 26 }} title={t.promptRefresh} aria-label={t.promptRefresh} onClick={() => void load()}>
|
|
147
|
+
<span className={busy ? "piweb-spin" : undefined} style={{ display: "inline-flex" }}>
|
|
148
|
+
<IconRefreshOutline14 size={14} />
|
|
149
|
+
</span>
|
|
150
|
+
</button>
|
|
151
|
+
<button type="button" className="icon-btn" style={{ width: 26, height: 26 }} title={t.close} aria-label={t.close} onClick={onClose}>
|
|
152
|
+
<IconCloseOutline14 size={14} />
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
<div className="min-h-0 flex-1 overflow-auto px-3 py-3" style={{ fontSize: 12.5 }}>
|
|
156
|
+
{error && (
|
|
157
|
+
<div className="mb-2 rounded-lg px-2 py-1.5" style={{ background: "var(--dsw-hover)", color: "var(--dsw-danger)" }} role="alert">
|
|
158
|
+
{error}
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
{!data && busy && <div style={{ color: "var(--dsw-label-tertiary)" }}>{t.promptLoading}</div>}
|
|
162
|
+
{data && !data.sessionReady && (
|
|
163
|
+
<div className="mb-2 rounded-lg px-2 py-1.5" style={{ background: "var(--dsw-hover)", color: "var(--dsw-label-caption)" }} role="status">
|
|
164
|
+
{t.promptColdHint}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
{groups.map((group) => (
|
|
169
|
+
<section key={group.kind} className="mb-3">
|
|
170
|
+
<div className="mb-1 px-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{kindLabel(group.kind)}</div>
|
|
171
|
+
<div className="flex flex-col gap-1">
|
|
172
|
+
{group.items.map((source) => (
|
|
173
|
+
<button
|
|
174
|
+
key={`${source.kind}:${source.path ?? source.name}`}
|
|
175
|
+
type="button"
|
|
176
|
+
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left"
|
|
177
|
+
style={{ background: opening === source.path ? "var(--dsw-active)" : "transparent", border: "0.5px solid var(--dsw-border-l2)" }}
|
|
178
|
+
title={source.path ?? source.name}
|
|
179
|
+
onClick={() => void openFile(source)}
|
|
180
|
+
>
|
|
181
|
+
<span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-primary)" }}>{source.name}</span>
|
|
182
|
+
<span className="flex-none" style={{ fontSize: 10.5, color: "var(--dsw-label-tertiary)" }}>{formatBytes(source.bytes)}</span>
|
|
183
|
+
<span className="flex-none rounded-md px-1.5 py-0.5" style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}>
|
|
184
|
+
{originLabel(source.origin)}
|
|
185
|
+
</span>
|
|
186
|
+
</button>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
</section>
|
|
190
|
+
))}
|
|
191
|
+
|
|
192
|
+
{hasTools && (
|
|
193
|
+
<section className="mb-3">
|
|
194
|
+
<div className="mb-1 px-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{t.promptKindTool}</div>
|
|
195
|
+
<div className="flex flex-col gap-1">
|
|
196
|
+
{data!.tools!.map((tool) => (
|
|
197
|
+
<button
|
|
198
|
+
key={`tool:${tool.name}`}
|
|
199
|
+
type="button"
|
|
200
|
+
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left"
|
|
201
|
+
style={{ border: "0.5px solid var(--dsw-border-l2)" }}
|
|
202
|
+
onClick={() => setInline({ title: `${tool.name}`, content: [tool.description ?? "", tool.promptGuidelines?.join("\n") ?? "", tool.parameters ? JSON.stringify(tool.parameters, null, 2) : ""].filter(Boolean).join("\n\n") })}
|
|
203
|
+
>
|
|
204
|
+
<span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-primary)" }}>{tool.name}</span>
|
|
205
|
+
<span className="flex-none rounded-md px-1.5 py-0.5" style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}>
|
|
206
|
+
{tool.source === "builtin" ? t.promptToolBuiltin : t.promptToolExtension}
|
|
207
|
+
</span>
|
|
208
|
+
</button>
|
|
209
|
+
))}
|
|
210
|
+
</div>
|
|
211
|
+
</section>
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
{data?.assembledSystemPrompt && (
|
|
215
|
+
<section className="mb-3">
|
|
216
|
+
<div className="mb-1 px-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{t.promptKindAssembled}</div>
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
className="w-full rounded-lg px-2 py-1.5 text-left"
|
|
220
|
+
style={{ border: "0.5px solid var(--dsw-border-l2)" }}
|
|
221
|
+
onClick={() => setInline({ title: t.promptKindAssembled, content: data.assembledSystemPrompt ?? "" })}
|
|
222
|
+
>
|
|
223
|
+
<span className="truncate" style={{ color: "var(--dsw-label-primary)" }}>{t.promptAssembledKey}</span>
|
|
224
|
+
</button>
|
|
225
|
+
</section>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
{inline && (
|
|
229
|
+
<div className="mt-2">
|
|
230
|
+
<div className="mb-1 flex items-center gap-2">
|
|
231
|
+
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{inline.title}</span>
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
className="icon-btn"
|
|
235
|
+
style={{ width: 24, height: 24 }}
|
|
236
|
+
title={copied ? t.copySuccess : t.copy}
|
|
237
|
+
aria-label={t.copy}
|
|
238
|
+
onClick={() => {
|
|
239
|
+
void copyText(inline.content).then((ok) => {
|
|
240
|
+
setCopied(ok);
|
|
241
|
+
setTimeout(() => setCopied(false), 1200);
|
|
242
|
+
});
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
{copied ? <IconCheckOutline14 size={13} /> : <IconCopyOutline16 size={13} />}
|
|
246
|
+
</button>
|
|
247
|
+
<button type="button" className="icon-btn" style={{ width: 24, height: 24 }} title={t.close} aria-label={t.close} onClick={() => setInline(null)}>
|
|
248
|
+
<IconCloseOutline14 size={13} />
|
|
249
|
+
</button>
|
|
250
|
+
</div>
|
|
251
|
+
<pre className="pw-output" style={{ maxHeight: 420, overflow: "auto", margin: 0 }}>{inline.content}</pre>
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
@@ -978,12 +978,12 @@ function ModelsSection() {
|
|
|
978
978
|
return (
|
|
979
979
|
<>
|
|
980
980
|
<div className="flex items-center gap-2">
|
|
981
|
-
{!usage && <span>{failed ? t.usageProbeFailed : busy ? t.queryingQuota :
|
|
981
|
+
{!usage && <span>{failed ? t.usageProbeFailed : busy ? t.queryingQuota : t.usageReadOnlyDisclosure}</span>}
|
|
982
982
|
{usage?.kind === "quota" && usage.planType && <span>{t.usagePlan} {titleCase(usage.planType)}</span>}
|
|
983
983
|
{usage?.kind === "balance" && usage.primary && (
|
|
984
984
|
<span>{usage.primaryKey === "used" ? t.usageBalanceUsed : t.usageQuotaRemaining} <span style={{ color: "var(--dsw-label-secondary)" }}>{fmtAmount(usage.primary)}</span></span>
|
|
985
985
|
)}
|
|
986
|
-
<button type="button" className="pw-chip" disabled={busy} title={
|
|
986
|
+
<button type="button" className="pw-chip" disabled={busy} title={t.usageReadOnlyDisclosure} onClick={() => void fetchProviderUsage(providerId, true)}>
|
|
987
987
|
{busy ? t.usageProbing : usage ? t.usageRefresh : t.usageProbe}
|
|
988
988
|
</button>
|
|
989
989
|
</div>
|
|
@@ -1230,69 +1230,71 @@ function ModelsSection() {
|
|
|
1230
1230
|
const editingThis = editing === p.id;
|
|
1231
1231
|
return (
|
|
1232
1232
|
<div key={p.id}>
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
<div className="
|
|
1236
|
-
<
|
|
1237
|
-
<div className="
|
|
1238
|
-
{
|
|
1233
|
+
{/* 卡片:身份/操作行在上,额度显示放在同一个框内的第二段(分隔线区分) */}
|
|
1234
|
+
<div className="flex flex-col rounded-2xl px-4 py-3" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
|
|
1235
|
+
<div className="flex items-center gap-3">
|
|
1236
|
+
<ProviderBrand id={p.id} name={providerName(p.id)} size={34} />
|
|
1237
|
+
<div className="min-w-0 flex-1">
|
|
1238
|
+
<div className="truncate" style={{ fontSize: 14, fontWeight: 500 }}>{providerName(p.id)}</div>
|
|
1239
|
+
<div className="truncate" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
1240
|
+
{p.id}{p.baseUrl ? ` · ${p.baseUrl}` : ""}
|
|
1241
|
+
</div>
|
|
1239
1242
|
</div>
|
|
1240
|
-
|
|
1241
|
-
|
|
1243
|
+
{isCustom && (
|
|
1244
|
+
<span
|
|
1245
|
+
className="rounded-md px-1.5 py-0.5"
|
|
1246
|
+
style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}
|
|
1247
|
+
>
|
|
1248
|
+
{t.customBadge}
|
|
1249
|
+
</span>
|
|
1250
|
+
)}
|
|
1251
|
+
<span
|
|
1252
|
+
className="inline-block h-2 w-2 rounded-full"
|
|
1253
|
+
style={{ background: p.authReady ? "var(--dsw-success)" : "var(--dsw-danger)" }}
|
|
1254
|
+
title={p.authError || (p.authReady ? t.providerReady : t.authUnavailable)}
|
|
1255
|
+
/>
|
|
1242
1256
|
<span
|
|
1243
1257
|
className="rounded-md px-1.5 py-0.5"
|
|
1244
1258
|
style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}
|
|
1245
1259
|
>
|
|
1246
|
-
{t.
|
|
1260
|
+
{t.credentialSource.replace("{source}", credentialSource(p))}
|
|
1247
1261
|
</span>
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
className="inline-block h-2 w-2 rounded-full"
|
|
1251
|
-
style={{ background: p.authReady ? "var(--dsw-success)" : "var(--dsw-danger)" }}
|
|
1252
|
-
title={p.authError || (p.authReady ? t.providerReady : t.authUnavailable)}
|
|
1253
|
-
/>
|
|
1254
|
-
<span
|
|
1255
|
-
className="rounded-md px-1.5 py-0.5"
|
|
1256
|
-
style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}
|
|
1257
|
-
>
|
|
1258
|
-
{t.credentialSource.replace("{source}", credentialSource(p))}
|
|
1259
|
-
</span>
|
|
1260
|
-
<button className="btn-outline" style={{ height: 28, padding: "0 12px", fontSize: 12 }} onClick={() => startEdit(p.id)}>
|
|
1261
|
-
{t.edit}
|
|
1262
|
-
</button>
|
|
1263
|
-
{p.storedAuthType === "oauth" && (
|
|
1264
|
-
<button
|
|
1265
|
-
style={{ height: 28, padding: "0 12px", fontSize: 12, color: "var(--dsw-danger)" }}
|
|
1266
|
-
onClick={async () => {
|
|
1267
|
-
if (!window.confirm(t.confirmRemoveOAuth.replace("{name}", providerName(p.id)))) return;
|
|
1268
|
-
await call({ action: "removeKey", providerId: p.id });
|
|
1269
|
-
}}
|
|
1270
|
-
>
|
|
1271
|
-
{t.removeOAuth}
|
|
1262
|
+
<button className="btn-outline" style={{ height: 28, padding: "0 12px", fontSize: 12 }} onClick={() => startEdit(p.id)}>
|
|
1263
|
+
{t.edit}
|
|
1272
1264
|
</button>
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1265
|
+
{p.storedAuthType === "oauth" && (
|
|
1266
|
+
<button
|
|
1267
|
+
style={{ height: 28, padding: "0 12px", fontSize: 12, color: "var(--dsw-danger)" }}
|
|
1268
|
+
onClick={async () => {
|
|
1269
|
+
if (!window.confirm(t.confirmRemoveOAuth.replace("{name}", providerName(p.id)))) return;
|
|
1270
|
+
await call({ action: "removeKey", providerId: p.id });
|
|
1271
|
+
}}
|
|
1272
|
+
>
|
|
1273
|
+
{t.removeOAuth}
|
|
1274
|
+
</button>
|
|
1275
|
+
)}
|
|
1276
|
+
{p.authTypes?.includes("oauth") && (
|
|
1277
|
+
<button
|
|
1278
|
+
className="btn-outline"
|
|
1279
|
+
style={{ height: 28, padding: "0 12px", fontSize: 12 }}
|
|
1280
|
+
onClick={() => {
|
|
1281
|
+
setOauthDone(null);
|
|
1282
|
+
void call({ action: "loginStart", providerId: p.id });
|
|
1283
|
+
setOauthProvider(p.id);
|
|
1284
|
+
}}
|
|
1285
|
+
>
|
|
1286
|
+
{t.oauthLogin}
|
|
1287
|
+
</button>
|
|
1288
|
+
)}
|
|
1289
|
+
{customIds.has(p.id) && (
|
|
1290
|
+
<button style={{ fontSize: 12.5, color: "var(--dsw-danger)" }} onClick={() => deleteCustom(p.id)}>
|
|
1291
|
+
{t.delete}
|
|
1292
|
+
</button>
|
|
1293
|
+
)}
|
|
1292
1294
|
</div>
|
|
1293
1295
|
|
|
1294
1296
|
{p.id === "xai" && p.authReady && (
|
|
1295
|
-
<div className="mt-
|
|
1297
|
+
<div className="mt-3 flex items-center gap-2 pt-3" style={{ borderTop: "0.5px solid var(--dsw-border-l2)", fontSize: 11.5, color: "var(--dsw-label-tertiary)" }}>
|
|
1296
1298
|
{xaiUsage ? (
|
|
1297
1299
|
<span>{t.usageQuotaLabel} · {xaiUsage.model} · {t.usageRequests} {xaiUsage.requestRemaining.toLocaleString()}/{xaiUsage.requestLimit.toLocaleString()} · Tokens {fmtQuotaTokens(xaiUsage.tokenRemaining)}/{fmtQuotaTokens(xaiUsage.tokenLimit)}</span>
|
|
1298
1300
|
) : <span>{usageError ? t.usageProbeFailed : t.usageProbeDisclosure}</span>}
|
|
@@ -1303,10 +1305,11 @@ function ModelsSection() {
|
|
|
1303
1305
|
)}
|
|
1304
1306
|
|
|
1305
1307
|
{supportsUsageProbe(p.id) && p.id !== "xai" && p.authReady && (
|
|
1306
|
-
<div className="mt-
|
|
1308
|
+
<div className="mt-3 flex flex-col gap-1.5 pt-3" style={{ borderTop: "0.5px solid var(--dsw-border-l2)", fontSize: 11.5, color: "var(--dsw-label-tertiary)" }}>
|
|
1307
1309
|
{renderProviderUsage(p.id)}
|
|
1308
1310
|
</div>
|
|
1309
1311
|
)}
|
|
1312
|
+
</div>
|
|
1310
1313
|
|
|
1311
1314
|
{editingThis && (
|
|
1312
1315
|
<div className="mt-2 rounded-2xl p-4" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
|
package/src/i18n.tsx
CHANGED
|
@@ -21,7 +21,6 @@ const zh = {
|
|
|
21
21
|
copyFailed: "复制失败",
|
|
22
22
|
editMessage: "编辑这条消息",
|
|
23
23
|
sendEdit: "重新发送",
|
|
24
|
-
editWhileRunning: "会话正在运行,先停止再编辑",
|
|
25
24
|
editMessageFailed: "编辑重发失败",
|
|
26
25
|
export: "导出",
|
|
27
26
|
settings: "设置",
|
|
@@ -214,7 +213,6 @@ const zh = {
|
|
|
214
213
|
usageQuotaRemaining: "额度剩余",
|
|
215
214
|
queryingQuota: "正在查询额度…",
|
|
216
215
|
usageReadOnlyDisclosure: "读取提供商的只读用量端点,不消耗额度。",
|
|
217
|
-
usageXaiDisclosure: "会向 xAI 发出一次 1 token 的请求以读取限流头,会计入用量。",
|
|
218
216
|
usageReset: "刷新时间",
|
|
219
217
|
usageWindowWeekly: "周限",
|
|
220
218
|
usageWindowMonthly: "月限",
|
|
@@ -286,6 +284,25 @@ const zh = {
|
|
|
286
284
|
contextSystem: "系统提示词",
|
|
287
285
|
contextMessages: "对话消息",
|
|
288
286
|
contextTools: "工具",
|
|
287
|
+
promptPanel: "提示词来源",
|
|
288
|
+
promptRefresh: "刷新",
|
|
289
|
+
promptLoading: "加载中…",
|
|
290
|
+
promptColdHint: "会话尚未启动:先列出磁盘上的提示词文件;打开会话后才会出现「组装后的系统提示词」与工具定义。",
|
|
291
|
+
promptOriginProject: "项目",
|
|
292
|
+
promptOriginAgent: "个人",
|
|
293
|
+
promptOriginPackage: "包",
|
|
294
|
+
promptOriginExtension: "扩展",
|
|
295
|
+
promptKindSystem: "系统提示词文件",
|
|
296
|
+
promptKindAppend: "追加提示词",
|
|
297
|
+
promptKindAgents: "项目记忆(AGENTS.md)",
|
|
298
|
+
promptKindSkill: "技能提示词",
|
|
299
|
+
promptKindTemplate: "提示模板",
|
|
300
|
+
promptKindTool: "工具提示词",
|
|
301
|
+
promptKindAssembled: "组装后的系统提示词",
|
|
302
|
+
promptAssembledKey: "当前生效的完整系统提示词",
|
|
303
|
+
promptToolBuiltin: "内置",
|
|
304
|
+
promptToolExtension: "扩展",
|
|
305
|
+
copySuccess: "已复制",
|
|
289
306
|
contextSystemPrompt: "System Prompt",
|
|
290
307
|
contextSystemTools: "System Tools",
|
|
291
308
|
contextCustomTools: "Custom Tools",
|
|
@@ -579,7 +596,6 @@ const en: Dict = {
|
|
|
579
596
|
copyFailed: "Copy failed",
|
|
580
597
|
editMessage: "Edit this message",
|
|
581
598
|
sendEdit: "Send again",
|
|
582
|
-
editWhileRunning: "This session is running; stop it before editing",
|
|
583
599
|
editMessageFailed: "Edit and resend failed",
|
|
584
600
|
export: "Export",
|
|
585
601
|
settings: "Settings",
|
|
@@ -771,7 +787,6 @@ const en: Dict = {
|
|
|
771
787
|
usageQuotaRemaining: "Remaining quota",
|
|
772
788
|
queryingQuota: "Querying quota…",
|
|
773
789
|
usageReadOnlyDisclosure: "Reads the provider's read-only usage endpoint; consumes no quota.",
|
|
774
|
-
usageXaiDisclosure: "Sends one 1-token request to xAI to read the rate-limit headers; counts against your usage.",
|
|
775
790
|
usageReset: "Resets",
|
|
776
791
|
usageWindowWeekly: "Weekly",
|
|
777
792
|
usageWindowMonthly: "Monthly",
|
|
@@ -841,6 +856,25 @@ const en: Dict = {
|
|
|
841
856
|
contextSystem: "System prompt",
|
|
842
857
|
contextMessages: "Messages",
|
|
843
858
|
contextTools: "Tools",
|
|
859
|
+
promptPanel: "Prompt sources",
|
|
860
|
+
promptRefresh: "Refresh",
|
|
861
|
+
promptLoading: "Loading…",
|
|
862
|
+
promptColdHint: "Session not started yet: disk files are listed now; the assembled system prompt and tool definitions appear once the session is running.",
|
|
863
|
+
promptOriginProject: "project",
|
|
864
|
+
promptOriginAgent: "user",
|
|
865
|
+
promptOriginPackage: "package",
|
|
866
|
+
promptOriginExtension: "extension",
|
|
867
|
+
promptKindSystem: "System prompt file",
|
|
868
|
+
promptKindAppend: "Appended prompt",
|
|
869
|
+
promptKindAgents: "Project memory (AGENTS.md)",
|
|
870
|
+
promptKindSkill: "Skill prompts",
|
|
871
|
+
promptKindTemplate: "Prompt templates",
|
|
872
|
+
promptKindTool: "Tool prompts",
|
|
873
|
+
promptKindAssembled: "Assembled system prompt",
|
|
874
|
+
promptAssembledKey: "Effective system prompt for this session",
|
|
875
|
+
promptToolBuiltin: "builtin",
|
|
876
|
+
promptToolExtension: "extension",
|
|
877
|
+
copySuccess: "Copied",
|
|
844
878
|
contextSystemPrompt: "System Prompt",
|
|
845
879
|
contextSystemTools: "System Tools",
|
|
846
880
|
contextCustomTools: "Custom Tools",
|
package/src/lib/agent-manager.ts
CHANGED
|
@@ -684,17 +684,15 @@ async function buildContextBreakdown(
|
|
|
684
684
|
// Memory:AGENTS.md 等注入文件(contextResources 的 project 部分)
|
|
685
685
|
const memoryText = resources.filter((r) => r.source === "project").map((r) => r.content).join("\n\n");
|
|
686
686
|
breakdown.memory = estimateTokensOf(memoryText);
|
|
687
|
-
// Skills:从资源加载器取技能清单,按
|
|
687
|
+
// Skills:从资源加载器取技能清单,按 SDK 的 formatSkillsForPrompt 段落定位。
|
|
688
|
+
// 该段落没有 Markdown 标题(原文以 "The following skills provide specialized instructions…" 开头,
|
|
689
|
+
// 结尾是 </available_skills>),以前按 "## Skills" 找永远找不到,技能段一直是 0。
|
|
688
690
|
try {
|
|
689
691
|
const { skills } = session.resourceLoader.getSkills();
|
|
690
692
|
if (skills.length && prompt) {
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const next = prompt.slice(header + 1).search(/^## /m);
|
|
695
|
-
const section = next >= 0 ? prompt.slice(header, header + 1 + next) : prompt.slice(header);
|
|
696
|
-
breakdown.skills = estimateTokensOf(section);
|
|
697
|
-
}
|
|
693
|
+
const start = prompt.indexOf("The following skills provide specialized instructions");
|
|
694
|
+
const end = prompt.indexOf("</available_skills>");
|
|
695
|
+
if (start >= 0 && end > start) breakdown.skills = estimateTokensOf(prompt.slice(start, end + "</available_skills>".length));
|
|
698
696
|
}
|
|
699
697
|
} catch {
|
|
700
698
|
/* loader 不可用则跳过 */
|
|
@@ -1442,6 +1440,35 @@ export async function execute(m: Managed, cmd: AgentCommand): Promise<CommandRes
|
|
|
1442
1440
|
const r = await session.navigateTree(entryId);
|
|
1443
1441
|
return { ok: !r?.cancelled, data: { cancelled: r?.cancelled === true, editorText: r?.editorText } };
|
|
1444
1442
|
}
|
|
1443
|
+
case "editAndResend": {
|
|
1444
|
+
// 编辑用户消息并重发:先停在跑的回合(用户改口即表示不要这一轮的输出),
|
|
1445
|
+
// 再回到该消息之前用新文本重新提问。放在服务端串行做,避免客户端"先 abort 再发"的竞态。
|
|
1446
|
+
const entryId = String((cmd as any).entryId ?? "");
|
|
1447
|
+
const text = (cmd.text ?? "").trim();
|
|
1448
|
+
if (!entryId) return { ok: false, error: "missing entryId" };
|
|
1449
|
+
if (!text) return { ok: false, error: "empty prompt" };
|
|
1450
|
+
if (m.promptSubmitting || m.resourceReloading) return { ok: false, error: "session is busy accepting another command" };
|
|
1451
|
+
m.promptSubmitting = true;
|
|
1452
|
+
try {
|
|
1453
|
+
const session = await ensureSession(m);
|
|
1454
|
+
if (m.disposed) throw new Error("session is disposed");
|
|
1455
|
+
if (session.isStreaming || m.runActive) {
|
|
1456
|
+
await session.abort().catch(() => undefined);
|
|
1457
|
+
// 等 SDK 真正回到空闲(abort 是异步的;最多等 5 秒,超时就按当前状态继续)
|
|
1458
|
+
for (let i = 0; i < 50 && !m.disposed && (session.isStreaming || m.runActive); i += 1) {
|
|
1459
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
if (m.disposed) throw new Error("session is disposed");
|
|
1463
|
+
const moved = await session.navigateTree(entryId);
|
|
1464
|
+
if (moved?.cancelled) return { ok: false, error: "edit cancelled" };
|
|
1465
|
+
await growthOf(m).prepare().catch(() => undefined);
|
|
1466
|
+
await session.prompt(text);
|
|
1467
|
+
return { ok: true };
|
|
1468
|
+
} finally {
|
|
1469
|
+
m.promptSubmitting = false;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1445
1472
|
case "rename": {
|
|
1446
1473
|
const name = (cmd.text ?? "").trim();
|
|
1447
1474
|
if (m.session) m.session.setSessionName(name);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 剪贴板工具:优先用异步 Clipboard API,非安全上下文(局域网 http)退回 textarea + execCommand。
|
|
3
|
+
* 从 ChatWindow 抽出来共用(消息操作行与提示词面板都用它)。
|
|
4
|
+
*/
|
|
5
|
+
export async function copyText(text: string): Promise<boolean> {
|
|
6
|
+
try {
|
|
7
|
+
if (navigator.clipboard?.writeText) {
|
|
8
|
+
await navigator.clipboard.writeText(text);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
} catch {
|
|
12
|
+
// Non-secure local-network contexts need the legacy fallback below.
|
|
13
|
+
}
|
|
14
|
+
const field = document.createElement("textarea");
|
|
15
|
+
field.value = text;
|
|
16
|
+
field.style.position = "fixed";
|
|
17
|
+
field.style.opacity = "0";
|
|
18
|
+
document.body.appendChild(field);
|
|
19
|
+
field.select();
|
|
20
|
+
try {
|
|
21
|
+
return document.execCommand("copy");
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
} finally {
|
|
25
|
+
field.remove();
|
|
26
|
+
}
|
|
27
|
+
}
|