@rexvane/piweb 0.3.10 → 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 +7 -6
- package/.next/build-manifest.json +6 -6
- package/.next/prerender-manifest.json +7 -7
- package/.next/react-loadable-manifest.json +7 -7
- 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 +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +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/growth/route.js.nft.json +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/models/route.js.nft.json +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/plugins/route.js.nft.json +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/security/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- 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/api/workspaces/route.js.nft.json +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 +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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 +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
- 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 +7 -6
- package/.next/server/chunks/229.js +1 -0
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/686.js +2 -2
- 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/static/css/b2d35abc828d4713.css +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 +63 -36
- package/src/components/PromptPanel.tsx +257 -0
- package/src/components/SettingsPanel.tsx +183 -55
- package/src/hooks/usePiWeb.ts +13 -1
- package/src/i18n.tsx +80 -2
- package/src/lib/agent-manager.ts +47 -8
- package/src/lib/clipboard.ts +27 -0
- package/src/lib/command-validation.ts +1 -0
- package/src/lib/event-batcher.ts +62 -0
- package/src/lib/models-service.ts +430 -19
- package/src/lib/prompt-sources.ts +201 -0
- package/src/lib/provider-usage.ts +91 -0
- package/src/lib/session-retention.ts +119 -0
- package/src/lib/types.ts +1 -0
- package/.next/server/chunks/780.js +0 -1
- package/.next/static/CLy0QZWDpEExcItT1yudl/_buildManifest.js +0 -1
- package/.next/static/chunks/155.a3c40ae481da1358.js +0 -1
- package/.next/static/chunks/18.8b025ee3303c3dc7.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-97c833615d1565c5.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-19e37fe240ae3e20.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-eebc3d300017fa47.js +0 -1
- package/.next/static/css/080ec3dba8907e83.css +0 -1
- /package/.next/static/{CLy0QZWDpEExcItT1yudl → 2kt2UiWjv_viXpNlfjvBT}/_ssgManifest.js +0 -0
|
@@ -11,6 +11,7 @@ import { ChatWindow, SessionStatsBar } from "@/components/ChatWindow";
|
|
|
11
11
|
import { ExtensionDialogHost, ExtensionNotices } from "@/components/ExtensionUI";
|
|
12
12
|
import dynamic from "next/dynamic";
|
|
13
13
|
import { SessionSidebar } from "@/components/SessionSidebar";
|
|
14
|
+
import { PromptPanel } from "@/components/PromptPanel";
|
|
14
15
|
|
|
15
16
|
// 首屏不需要的重组件按需加载(设置面板含供应商配置与代码高亮,轨迹/文件只在打开时才用)
|
|
16
17
|
const SettingsPanel = dynamic(() => import("@/components/SettingsPanel").then((m) => m.SettingsPanel), { ssr: false });
|
|
@@ -23,6 +24,7 @@ const ProjectPanel = dynamic(() => import("@/components/ProjectPanel").then((m)
|
|
|
23
24
|
const FileViewer = dynamic(() => import("@/components/FileViewer").then((m) => m.FileViewer), { ssr: false });
|
|
24
25
|
const GitPanel = dynamic(() => import("@/components/GitPanel").then((m) => m.GitPanel), { ssr: false });
|
|
25
26
|
import {
|
|
27
|
+
IconAgentPresetOutline16,
|
|
26
28
|
IconCheckOutline14,
|
|
27
29
|
IconChevronDown14,
|
|
28
30
|
IconFolderClose16,
|
|
@@ -137,6 +139,8 @@ export function AppShell() {
|
|
|
137
139
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
138
140
|
const [selected, setSelected] = useState<TrajEntry | null>(null);
|
|
139
141
|
const [gitDetailsOpen, setGitDetailsOpen] = useState(false);
|
|
142
|
+
/** 提示词来源面板(右侧详情列) */
|
|
143
|
+
const [promptsOpen, setPromptsOpen] = useState(false);
|
|
140
144
|
const [heroCwd, setHeroCwd] = useState("");
|
|
141
145
|
const [heroModel, setHeroModel] = useState<{ provider: string; id: string } | null>(null);
|
|
142
146
|
const [heroThinking, setHeroThinking] = useState("");
|
|
@@ -200,6 +204,7 @@ export function AppShell() {
|
|
|
200
204
|
useEffect(() => {
|
|
201
205
|
setSelected(null);
|
|
202
206
|
setGitDetailsOpen(false);
|
|
207
|
+
setPromptsOpen(false);
|
|
203
208
|
}, [currentPath]);
|
|
204
209
|
|
|
205
210
|
// 已知工作区列表(给 Hero 建议);启动不预选任何工作区,
|
|
@@ -345,28 +350,22 @@ export function AppShell() {
|
|
|
345
350
|
);
|
|
346
351
|
|
|
347
352
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
353
|
+
* 原地编辑用户消息后重新发送:服务端先停在跑的回合(用户改口即表示不要这一轮),
|
|
354
|
+
* 再在同一会话文件内回到该消息之前(navigateTree),用新文本重新提问——
|
|
355
|
+
* 被编辑消息之后的分支作废,模型从这里重新回答。旧的插话(steering)撤回/编辑/发送不变。
|
|
351
356
|
*/
|
|
352
357
|
const doEditMessage = useCallback(
|
|
353
358
|
async (entryId: string, text: string) => {
|
|
354
359
|
if (!currentId) return;
|
|
355
|
-
|
|
356
|
-
|
|
360
|
+
const done = await sendCommand({ cmd: "editAndResend", entryId, text });
|
|
361
|
+
if (!done?.success) {
|
|
362
|
+
setError(done?.error ?? t.editMessageFailed);
|
|
357
363
|
return;
|
|
358
364
|
}
|
|
359
|
-
|
|
360
|
-
if (!moved?.success || moved.data?.cancelled) {
|
|
361
|
-
setError(moved?.error ?? t.editMessageFailed);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
// 被编辑消息之后的尾部要立刻从视图里消失,再发新提问
|
|
365
|
+
// 被编辑消息之后的尾部要从视图里消失(服务端已切断该分支)
|
|
365
366
|
resync();
|
|
366
|
-
const sent = await sendCommand({ cmd: "prompt", text });
|
|
367
|
-
if (!sent?.success) setError(sent?.error ?? t.editMessageFailed);
|
|
368
367
|
},
|
|
369
|
-
[currentId,
|
|
368
|
+
[currentId, resync, sendCommand, setError, t.editMessageFailed],
|
|
370
369
|
);
|
|
371
370
|
|
|
372
371
|
const doArchiveSession = useCallback(
|
|
@@ -605,7 +604,7 @@ export function AppShell() {
|
|
|
605
604
|
|
|
606
605
|
// 三栏网格
|
|
607
606
|
const gridCols = isNarrow ? "minmax(0,1fr)" : `${sidebarCollapsed ? SIDEBAR_COLLAPSED : sidebarWidth}px ${projectOpen ? `${projectWidth}px` : "0px"} minmax(0,1fr) ${
|
|
608
|
-
selected || gitDetailsOpen ? `${detailsWidth}px` : "0px"
|
|
607
|
+
selected || gitDetailsOpen || promptsOpen ? `${detailsWidth}px` : "0px"
|
|
609
608
|
}`;
|
|
610
609
|
const projectColumn = projectOpen && (
|
|
611
610
|
<div
|
|
@@ -777,6 +776,22 @@ export function AppShell() {
|
|
|
777
776
|
>
|
|
778
777
|
<IconFolderOpenOutline16 size={15} />
|
|
779
778
|
</button>
|
|
779
|
+
{/* 提示词来源:系统提示词 / 记忆 / 技能 / 模板 / 工具提示词集中列出,点开即看 */}
|
|
780
|
+
<button
|
|
781
|
+
type="button"
|
|
782
|
+
className="icon-btn"
|
|
783
|
+
style={{ width: 30, height: 30, background: promptsOpen ? "var(--dsw-active)" : undefined }}
|
|
784
|
+
title={t.promptPanel}
|
|
785
|
+
aria-label={t.promptPanel}
|
|
786
|
+
data-testid="prompt-panel-toggle"
|
|
787
|
+
onClick={() => {
|
|
788
|
+
setPromptsOpen((open) => !open);
|
|
789
|
+
setSelected(null);
|
|
790
|
+
setGitDetailsOpen(false);
|
|
791
|
+
}}
|
|
792
|
+
>
|
|
793
|
+
<IconAgentPresetOutline16 size={15} />
|
|
794
|
+
</button>
|
|
780
795
|
</div>
|
|
781
796
|
{projectTrust?.required && !projectTrust.trusted && (
|
|
782
797
|
<div className="mt-2 flex flex-wrap items-center gap-2 rounded-xl px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", border: "0.5px solid var(--dsw-border-l2)" }} role="status">
|
|
@@ -887,8 +902,8 @@ export function AppShell() {
|
|
|
887
902
|
)}
|
|
888
903
|
</div>
|
|
889
904
|
|
|
890
|
-
{/*
|
|
891
|
-
{(selected || gitDetailsOpen) && (
|
|
905
|
+
{/* 提示词来源 / 轨迹 / Git 详情栏 */}
|
|
906
|
+
{(selected || gitDetailsOpen || promptsOpen) && (
|
|
892
907
|
<div
|
|
893
908
|
className="flex min-h-0 flex-col overflow-hidden"
|
|
894
909
|
style={isNarrow ? {
|
|
@@ -901,7 +916,15 @@ export function AppShell() {
|
|
|
901
916
|
} : { background: "var(--dsw-sidebar-fill)", borderLeft: "0.5px solid var(--dsw-border-l2)" }}
|
|
902
917
|
>
|
|
903
918
|
<div className="min-h-0 flex-1">
|
|
904
|
-
{
|
|
919
|
+
{promptsOpen ? (
|
|
920
|
+
<PromptPanel
|
|
921
|
+
cwd={panelCwd}
|
|
922
|
+
sessionId={currentId}
|
|
923
|
+
refreshKey={currentId}
|
|
924
|
+
onOpenContent={(path, content) => viewer.openStatic(path, content)}
|
|
925
|
+
onClose={() => setPromptsOpen(false)}
|
|
926
|
+
/>
|
|
927
|
+
) : selected ? (
|
|
905
928
|
<TrajInspector entry={selected} onClose={() => setSelected(null)} />
|
|
906
929
|
) : (
|
|
907
930
|
<GitPanel cwd={panelCwd} refreshKey={panelRefreshKey} onClose={() => setGitDetailsOpen(false)} onAskCommit={() => insertIntoComposer(t.gitAskCommitPrompt)} onOpenFile={openInEditor} />
|
|
@@ -918,7 +941,7 @@ export function AppShell() {
|
|
|
918
941
|
onPointerDown={onDrag("sidebar")}
|
|
919
942
|
/>
|
|
920
943
|
)}
|
|
921
|
-
{!isNarrow && (selected || gitDetailsOpen) && (
|
|
944
|
+
{!isNarrow && (selected || gitDetailsOpen || promptsOpen) && (
|
|
922
945
|
<div
|
|
923
946
|
className="fixed top-0 h-full w-2 cursor-col-resize"
|
|
924
947
|
style={{ left: `calc(100vw - ${detailsWidth}px - 4px)`, zIndex: 40 }}
|
|
@@ -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
|
});
|
|
@@ -1014,7 +999,7 @@ function isFinalAnswer(m: WebMessage): boolean {
|
|
|
1014
999
|
return hasText && !hasTool && sr !== "toolUse" && sr !== "pending" && sr !== "error" && sr !== "aborted";
|
|
1015
1000
|
}
|
|
1016
1001
|
|
|
1017
|
-
function
|
|
1002
|
+
function TurnBlockView({
|
|
1018
1003
|
turn,
|
|
1019
1004
|
messages,
|
|
1020
1005
|
tools,
|
|
@@ -1195,6 +1180,48 @@ export function SessionStatsBar({
|
|
|
1195
1180
|
);
|
|
1196
1181
|
}
|
|
1197
1182
|
|
|
1183
|
+
type TurnBlockProps = Parameters<typeof TurnBlockView>[0];
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* 回合级跳过:流式期间只有正在变的那条消息身份会变(foldPiWebEvent 只替换被改动的消息),
|
|
1187
|
+
* 历史回合用到的数据全部保持身份,于是 200 条消息的会话里历史回合可以整体跳过重渲染。
|
|
1188
|
+
*/
|
|
1189
|
+
function turnBlockPropsEqual(prev: TurnBlockProps, next: TurnBlockProps): boolean {
|
|
1190
|
+
if (
|
|
1191
|
+
prev.cwd !== next.cwd ||
|
|
1192
|
+
prev.isStreaming !== next.isStreaming ||
|
|
1193
|
+
prev.isLast !== next.isLast ||
|
|
1194
|
+
prev.lastAssistantIndex !== next.lastAssistantIndex ||
|
|
1195
|
+
prev.retryNotice !== next.retryNotice ||
|
|
1196
|
+
prev.streamStartedAt !== next.streamStartedAt ||
|
|
1197
|
+
prev.runningTool !== next.runningTool ||
|
|
1198
|
+
prev.outputTokens !== next.outputTokens ||
|
|
1199
|
+
prev.workingMessage !== next.workingMessage ||
|
|
1200
|
+
prev.contextFiles !== next.contextFiles ||
|
|
1201
|
+
prev.onFork !== next.onFork ||
|
|
1202
|
+
prev.onEditMessage !== next.onEditMessage ||
|
|
1203
|
+
prev.onInspectTool !== next.onInspectTool ||
|
|
1204
|
+
prev.onOpenFile !== next.onOpenFile
|
|
1205
|
+
) return false;
|
|
1206
|
+
// buildTurns 每次重建回合对象:比较内容而不是引用
|
|
1207
|
+
if (prev.turn.key !== next.turn.key || prev.turn.userIndex !== next.turn.userIndex) return false;
|
|
1208
|
+
if (prev.turn.assistantIndexes.length !== next.turn.assistantIndexes.length) return false;
|
|
1209
|
+
if (!prev.turn.assistantIndexes.every((value, index) => value === next.turn.assistantIndexes[index])) return false;
|
|
1210
|
+
const indexes = [prev.turn.userIndex, ...prev.turn.assistantIndexes].filter((index) => index >= 0);
|
|
1211
|
+
if (!indexes.every((index) => prev.messages[index] === next.messages[index])) return false;
|
|
1212
|
+
// 只比较本回合引用到的工具条目:tools 外层容器每次工具事件都会换身份
|
|
1213
|
+
const toolIds: string[] = [];
|
|
1214
|
+
for (const index of indexes) {
|
|
1215
|
+
for (const part of prev.messages[index]?.content ?? []) {
|
|
1216
|
+
const call = part as { type?: string; id?: string };
|
|
1217
|
+
if (call.type === "toolCall" && call.id) toolIds.push(call.id);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return toolIds.every((id) => prev.tools[id] === next.tools[id]);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const TurnBlock = memo(TurnBlockView, turnBlockPropsEqual);
|
|
1224
|
+
|
|
1198
1225
|
export function ChatWindow({
|
|
1199
1226
|
messages,
|
|
1200
1227
|
tools,
|
|
@@ -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
|
+
}
|