@rexvane/piweb 0.3.20 → 0.3.21
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 +5 -5
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +4 -4
- package/.next/react-loadable-manifest.json +4 -4
- package/.next/required-server-files.js +2 -1
- package/.next/required-server-files.json +2 -1
- 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_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 +3 -3
- 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/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +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/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 -1
- package/.next/server/app/api/prompts/route.js.nft.json +1 -1
- 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/workspaces/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +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_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.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +5 -5
- package/.next/server/chunks/421.js +1 -0
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/662.js +2 -0
- package/.next/server/chunks/686.js +1 -1
- package/.next/server/chunks/807.js +1 -1
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/instrumentation.js.nft.json +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/chunks/4686-882531aef1d2e250.js +1 -0
- package/.next/static/chunks/{4778.e4c9ed093a413bc4.js → 4778.305dc6abb9889627.js} +1 -1
- package/.next/static/chunks/839.bfd2db36483faa3e.js +1 -0
- package/.next/static/chunks/8552.25c39ef4c995eba0.js +1 -0
- package/.next/static/chunks/app/page-c04af72e549695bd.js +1 -0
- package/.next/static/chunks/{webpack-3614a8337843456b.js → webpack-9b4bb574e5817654.js} +1 -1
- package/.next/static/css/ea27b9e30f76303f.css +1 -0
- package/README.md +14 -5
- package/next.config.ts +4 -2
- package/package.json +1 -1
- package/scripts/release.mjs +1 -1
- package/src/app/api/agent/[id]/events/route.ts +42 -13
- package/src/app/api/update/route.ts +18 -2
- package/src/app/globals.css +89 -0
- package/src/components/AppShell.tsx +7 -3
- package/src/components/ChatInput.tsx +1 -0
- package/src/components/ChatWindow.tsx +105 -81
- package/src/components/ContextMeter.tsx +31 -0
- package/src/components/DrawerPanel.tsx +19 -26
- package/src/components/ExtensionUI.tsx +8 -2
- package/src/components/MarkdownBlock.tsx +333 -37
- package/src/components/SettingsPanel.tsx +122 -53
- package/src/components/chat/chat-scroll.ts +18 -0
- package/src/components/update/CopyCommand.tsx +34 -0
- package/src/components/update/UpdateNotice.tsx +130 -0
- package/src/components/update/update-store.ts +155 -0
- package/src/hooks/usePiWeb.ts +42 -25
- package/src/i18n.tsx +74 -4
- package/src/instrumentation-node.ts +3 -0
- package/src/lib/agent-manager.ts +100 -57
- package/src/lib/chat/event-stream.ts +113 -0
- package/src/lib/chat/stream-settings.ts +192 -0
- package/src/lib/types.ts +7 -0
- package/src/lib/update/startup-check.ts +117 -0
- package/src/lib/update-service.ts +23 -3
- package/.next/server/chunks/131.js +0 -1
- package/.next/server/chunks/243.js +0 -1
- package/.next/static/chunks/4686-b1e9fa876d0ea671.js +0 -1
- package/.next/static/chunks/839.d3fe337a9117c325.js +0 -1
- package/.next/static/chunks/8552.6bfe3e101250b20f.js +0 -1
- package/.next/static/chunks/app/page-2e425ef83fba0782.js +0 -1
- package/.next/static/css/7391c42e1ae617f9.css +0 -1
- /package/.next/static/{_Y3Lq0lI5RJ1tFSOnDi0L → sNX5GQkOWuK5YEID1hT-C}/_buildManifest.js +0 -0
- /package/.next/static/{_Y3Lq0lI5RJ1tFSOnDi0L → sNX5GQkOWuK5YEID1hT-C}/_ssgManifest.js +0 -0
|
@@ -28,6 +28,7 @@ import { DiffView, type DiffLine, parseUnifiedDiff } from "@/components/DiffView
|
|
|
28
28
|
import { languageForPath } from "@/lib/highlight";
|
|
29
29
|
import { copyText } from "@/lib/clipboard";
|
|
30
30
|
import { OutlineRail } from "@/components/OutlineRail";
|
|
31
|
+
import { ChatScrollContext, type ChatScroll } from "@/components/chat/chat-scroll";
|
|
31
32
|
import { useI18n } from "@/i18n";
|
|
32
33
|
import type { ToolCardState } from "@/hooks/usePiWeb";
|
|
33
34
|
import type { ContextResource, TrajEntry, TrajTokens, WebContent, WebMessage, WebStats } from "@/lib/types";
|
|
@@ -614,9 +615,13 @@ const ThinkStep = memo(function ThinkStep({ text, live, startedAt }: { text: str
|
|
|
614
615
|
// ---------- 叙述:模型在工具之间说的话,作为阶段标题(基线对齐,主色) ----------
|
|
615
616
|
|
|
616
617
|
const NarrationBlock = memo(function NarrationBlock({ text, live, error }: { text: string; live?: boolean; error?: boolean }) {
|
|
618
|
+
// 流过的这一段一直交给流式组件:live 结束后它先把还没放出来的字放完,再自己换成整段渲染。
|
|
619
|
+
// 一结束就换成 <Markdown>,打字机这类慢样式积压的字会一口吐出来。
|
|
620
|
+
const [streamed, setStreamed] = useState(Boolean(live));
|
|
621
|
+
if (live && !streamed) setStreamed(true);
|
|
617
622
|
return (
|
|
618
623
|
<div className="pw-narr" data-error={error || undefined}>
|
|
619
|
-
{
|
|
624
|
+
{streamed ? <StreamingMarkdown text={text} live={Boolean(live)} /> : <Markdown text={text} />}
|
|
620
625
|
</div>
|
|
621
626
|
);
|
|
622
627
|
});
|
|
@@ -1425,6 +1430,16 @@ export function ChatWindow({
|
|
|
1425
1430
|
const widthPreference = useRef<number | null>(null);
|
|
1426
1431
|
const widthDrag = useRef<{ side: "left" | "right"; origin: number; width: number; pointerId: number } | null>(null);
|
|
1427
1432
|
const stickToBottom = useRef(true);
|
|
1433
|
+
/** 上一次滚动事件时的 scrollTop:比它小就是在往上走(用户翻看、大纲 / 搜索跳转) */
|
|
1434
|
+
const lastScrollTop = useRef(0);
|
|
1435
|
+
// 流式正文逐帧变高时据此决定要不要跟着贴底(见 chat/chat-scroll)
|
|
1436
|
+
const chatScroll = useMemo<ChatScroll>(() => ({
|
|
1437
|
+
shouldPin: () => stickToBottom.current,
|
|
1438
|
+
pin: () => {
|
|
1439
|
+
const node = scrollRef.current;
|
|
1440
|
+
if (node) node.scrollTop = node.scrollHeight;
|
|
1441
|
+
},
|
|
1442
|
+
}), []);
|
|
1428
1443
|
const { t } = useI18n();
|
|
1429
1444
|
useEffect(() => {
|
|
1430
1445
|
const root = rootRef.current;
|
|
@@ -1635,89 +1650,98 @@ export function ChatWindow({
|
|
|
1635
1650
|
}}
|
|
1636
1651
|
/>
|
|
1637
1652
|
))}
|
|
1638
|
-
<
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
{/* 重连提示:跟随消息流显示在最后一条输出下面 */}
|
|
1691
|
-
{!connected && (
|
|
1692
|
-
<div className="mt-3 flex justify-center">
|
|
1693
|
-
<div
|
|
1694
|
-
className="rounded-full px-3 py-1"
|
|
1695
|
-
style={{ fontSize: "var(--piweb-chat-font-t)", color: "var(--dsw-label-caption)", border: "0.5px dashed var(--dsw-border-l3)" }}
|
|
1696
|
-
>
|
|
1697
|
-
{t.reconnecting}
|
|
1653
|
+
<ChatScrollContext.Provider value={chatScroll}>
|
|
1654
|
+
<div
|
|
1655
|
+
ref={scrollRef}
|
|
1656
|
+
className="min-h-0 flex-1 overflow-y-auto"
|
|
1657
|
+
role="log"
|
|
1658
|
+
aria-live="polite"
|
|
1659
|
+
aria-relevant="additions"
|
|
1660
|
+
onScroll={(event) => {
|
|
1661
|
+
const node = event.currentTarget;
|
|
1662
|
+
const distance = node.scrollHeight - node.scrollTop - node.clientHeight;
|
|
1663
|
+
const atBottom = distance < 96;
|
|
1664
|
+
// 往上走就立刻松开贴底,不等离底 96px:流式正文每帧都会贴一次底,
|
|
1665
|
+
// 大纲 / 搜索的平滑跳转刚起步、还在 96px 以内时就会被拽回去。
|
|
1666
|
+
// 已经在最底下(内容变短被浏览器夹回来)不算往上翻。
|
|
1667
|
+
const movedUp = node.scrollTop < lastScrollTop.current - 1 && distance > 2;
|
|
1668
|
+
lastScrollTop.current = node.scrollTop;
|
|
1669
|
+
if (movedUp) stickToBottom.current = false;
|
|
1670
|
+
else stickToBottom.current = atBottom;
|
|
1671
|
+
setAwayFromBottom(!atBottom);
|
|
1672
|
+
if (atBottom) setUnseen(0);
|
|
1673
|
+
scheduleUpgradeCheck();
|
|
1674
|
+
}}
|
|
1675
|
+
>
|
|
1676
|
+
<div className="mx-auto flex w-full flex-col px-4 py-6" style={{ maxWidth: "var(--dsh-chat-content-width)" }}>
|
|
1677
|
+
{turns.map((turn, ti) => (
|
|
1678
|
+
// 回合间 28px 的分隔挂在槽上:用户消息现在总是槽里的第一个子元素,它自己的 mt-7 会被 first:mt-0 吃掉
|
|
1679
|
+
<div key={turn.key} data-turn-slot={ti} ref={slotRef(ti)} className="mt-7 first:mt-0">
|
|
1680
|
+
{isNear(ti) ? (
|
|
1681
|
+
<TurnBlock
|
|
1682
|
+
turn={turn}
|
|
1683
|
+
messages={messages}
|
|
1684
|
+
tools={tools}
|
|
1685
|
+
cwd={cwd}
|
|
1686
|
+
isStreaming={isStreaming}
|
|
1687
|
+
isLast={ti === turns.length - 1}
|
|
1688
|
+
lastAssistantIndex={lastAssistantIndex}
|
|
1689
|
+
contextFiles={turn.userIndex >= 0 && turn.userIndex === firstUser ? contextFiles : undefined}
|
|
1690
|
+
retryNotice={retryNotice}
|
|
1691
|
+
streamStartedAt={streamStartedAt}
|
|
1692
|
+
runningTool={runningTool}
|
|
1693
|
+
outputTokens={stats?.tokens.output}
|
|
1694
|
+
workingMessage={workingMessage}
|
|
1695
|
+
onFork={onFork}
|
|
1696
|
+
onEditMessage={onEditMessage}
|
|
1697
|
+
roundBadge={turn.userIndex >= 0 ? roundBadges?.get(messages[turn.userIndex].id ?? "") : undefined}
|
|
1698
|
+
onShowRound={onShowRound}
|
|
1699
|
+
onInspectTool={onOpenTrajectory}
|
|
1700
|
+
onOpenFile={onOpenFile}
|
|
1701
|
+
/>
|
|
1702
|
+
) : (
|
|
1703
|
+
<TurnLite turn={turn} messages={messages} />
|
|
1704
|
+
)}
|
|
1698
1705
|
</div>
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
<span>{compaction.phase === "start" ? t.compacting : compaction.errorMessage ? `${t.compactionFailed}: ${compaction.errorMessage}` : t.compactionComplete}</span>
|
|
1705
|
-
{compaction.phase === "end" && <button type="button" onClick={onClearCompaction} aria-label={t.close} className="icon-btn">×</button>}
|
|
1706
|
+
))}
|
|
1707
|
+
{/* 还没有任何消息就已在流式(极少见):单独给一条工作指示 */}
|
|
1708
|
+
{isStreaming && streamStartedAt && turns.length === 0 && (
|
|
1709
|
+
<div className="pw-turn">
|
|
1710
|
+
<WorkingStep startedAt={streamStartedAt} outputTokens={stats?.tokens.output} workingMessage={workingMessage} hasLiveStep={Boolean(runningTool)} />
|
|
1706
1711
|
</div>
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1712
|
+
)}
|
|
1713
|
+
{/* 重连提示:跟随消息流显示在最后一条输出下面 */}
|
|
1714
|
+
{!connected && (
|
|
1715
|
+
<div className="mt-3 flex justify-center">
|
|
1716
|
+
<div
|
|
1717
|
+
className="rounded-full px-3 py-1"
|
|
1718
|
+
style={{ fontSize: "var(--piweb-chat-font-t)", color: "var(--dsw-label-caption)", border: "0.5px dashed var(--dsw-border-l3)" }}
|
|
1719
|
+
>
|
|
1720
|
+
{t.reconnecting}
|
|
1721
|
+
</div>
|
|
1722
|
+
</div>
|
|
1723
|
+
)}
|
|
1724
|
+
{compaction && (
|
|
1725
|
+
<div className="mt-3 flex justify-center" role="status">
|
|
1726
|
+
<div className="flex items-center gap-2 rounded-xl px-3 py-2" style={{ fontSize: "var(--piweb-chat-font-s)", background: "var(--dsw-hover)", color: "var(--dsw-label-secondary)" }}>
|
|
1727
|
+
<span>{compaction.phase === "start" ? t.compacting : compaction.errorMessage ? `${t.compactionFailed}: ${compaction.errorMessage}` : t.compactionComplete}</span>
|
|
1728
|
+
{compaction.phase === "end" && <button type="button" onClick={onClearCompaction} aria-label={t.close} className="icon-btn">×</button>}
|
|
1729
|
+
</div>
|
|
1730
|
+
</div>
|
|
1731
|
+
)}
|
|
1732
|
+
{error && (
|
|
1733
|
+
<button
|
|
1734
|
+
className="mt-3 rounded-xl px-3 py-2 text-left"
|
|
1735
|
+
style={{ fontSize: "var(--piweb-chat-font-s)", background: "var(--dsw-danger)", color: "white" }}
|
|
1736
|
+
onClick={onClearError}
|
|
1737
|
+
role="alert"
|
|
1738
|
+
>
|
|
1739
|
+
{error}
|
|
1740
|
+
</button>
|
|
1741
|
+
)}
|
|
1742
|
+
</div>
|
|
1719
1743
|
</div>
|
|
1720
|
-
</
|
|
1744
|
+
</ChatScrollContext.Provider>
|
|
1721
1745
|
{/* 右侧大纲导航:从助手回答的标题和用户消息生成,随滚动高亮,悬停展开 */}
|
|
1722
1746
|
<OutlineRail scrollRef={scrollRef} revision={`${messages.length}:${nearTurns.size}:${isStreaming ? 1 : 0}`} />
|
|
1723
1747
|
{/* 脱离底部时的“回到底部”浮钮:与输入框卡片同轴,挂在右上角顶部,图标带新内容角标 */}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* 分段口径:全部为字符/4 启发式估算(与 SDK estimateTokens 一致),
|
|
8
8
|
* 服务端分项(System Prompt / Tools / Memory / Skills / Compacted / Buffer)来自快照,
|
|
9
9
|
* 消息分项(User / Agent Text / Thinking / Tool Call / Tool Output)弹窗打开时前端统计。
|
|
10
|
+
* 冷会话(只浏览历史、agent 没启动)拿不到组装后的 System Prompt 和工具定义:
|
|
11
|
+
* 其余分项照常显示,另给「现在统计」按需启动会话,分项随后经 context_breakdown 事件刷新。
|
|
10
12
|
* 无对话时不渲染(由父组件经 contextVisible 控制)。
|
|
11
13
|
*/
|
|
12
14
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -40,11 +42,20 @@ export function ContextMeter({
|
|
|
40
42
|
systemChars: number;
|
|
41
43
|
messages: { role?: string; content: Array<{ type: string; text?: string; thinking?: string; arguments?: unknown }> }[];
|
|
42
44
|
breakdown?: ContextBreakdown;
|
|
45
|
+
/** 冷会话里点「现在统计」:启动会话(prepare),System Prompt 与工具定义才算得出来 */
|
|
46
|
+
loadFull?: () => Promise<unknown> | unknown;
|
|
43
47
|
};
|
|
44
48
|
}) {
|
|
45
49
|
const [open, setOpen] = useState(false);
|
|
50
|
+
const [counting, setCounting] = useState(false);
|
|
46
51
|
const ref = useRef<HTMLDivElement>(null);
|
|
47
52
|
const { t } = useI18n();
|
|
53
|
+
const cold = Boolean(source?.breakdown?.cold);
|
|
54
|
+
|
|
55
|
+
// 新分项到了(不再是冷的)就收起「统计中」
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!cold) setCounting(false);
|
|
58
|
+
}, [cold]);
|
|
48
59
|
|
|
49
60
|
useEffect(() => {
|
|
50
61
|
if (!open) return;
|
|
@@ -154,6 +165,26 @@ export function ContextMeter({
|
|
|
154
165
|
))}
|
|
155
166
|
{window_ > 0 && <SegmentRow label={t.contextFreeSpace} tokens={free} color="var(--dsw-selector)" />}
|
|
156
167
|
</div>
|
|
168
|
+
{cold && source?.loadFull ? (
|
|
169
|
+
<div className="mt-3 flex items-center justify-between gap-2" style={{ color: "var(--dsw-label-tertiary)" }}>
|
|
170
|
+
<span className="min-w-0" style={{ fontSize: 11.5 }}>{t.contextColdHint}</span>
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
className="btn-outline flex-none"
|
|
174
|
+
style={{ height: 24, padding: "0 10px", fontSize: 12 }}
|
|
175
|
+
disabled={counting}
|
|
176
|
+
onClick={() => {
|
|
177
|
+
setCounting(true);
|
|
178
|
+
// 启动失败(例如没配模型)就把按钮还回来;成功时等 context_breakdown 事件把冷标记去掉
|
|
179
|
+
void Promise.resolve(source.loadFull?.()).then((result) => {
|
|
180
|
+
if ((result as { success?: boolean } | undefined)?.success === false) setCounting(false);
|
|
181
|
+
}, () => setCounting(false));
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
{counting ? t.contextCounting : t.contextCountNow}
|
|
185
|
+
</button>
|
|
186
|
+
</div>
|
|
187
|
+
) : null}
|
|
157
188
|
</div>
|
|
158
189
|
)}
|
|
159
190
|
</div>
|
|
@@ -87,22 +87,10 @@ export function DrawerPanel({
|
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
89
|
<div className="flex h-full min-h-0 flex-col" data-testid="drawer-panel">
|
|
90
|
-
{/* 头部:与 PromptPanel / ProjectPanel 同一套排布。
|
|
91
|
-
提示词 / Git 两个面板自带同款标题行(含关闭),这里就不再叠一层;
|
|
92
|
-
「导入会话」段落没有标题行,抽屉自己出这行,并关掉整个抽屉。 */}
|
|
93
|
-
{section === "import" && (
|
|
94
|
-
<div className="hairline-b flex items-center gap-1.5 px-3 pb-2 pt-3">
|
|
95
|
-
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 13, fontWeight: 600 }}>
|
|
96
|
-
{t.drawerPanel}
|
|
97
|
-
</span>
|
|
98
|
-
<button className="icon-btn" style={{ width: 22, height: 22 }} title={t.close} aria-label={t.close} onClick={onClose}>
|
|
99
|
-
<IconCloseOutline14 size={13} />
|
|
100
|
-
</button>
|
|
101
|
-
</div>
|
|
102
|
-
)}
|
|
103
|
-
|
|
104
90
|
{/* 入口列表:横向一排图标,不显示文字(名字放 title / aria-label)。
|
|
105
|
-
激活态复用设置页那套下划线(.settings-tab-underline),不自创样式。
|
|
91
|
+
激活态复用设置页那套下划线(.settings-tab-underline),不自创样式。
|
|
92
|
+
悬停底色交给 CSS(.drawer-tab:hover):以前用 onMouseEnter/Leave 直接改 style,
|
|
93
|
+
悬停时点下去这一项变成选中,移开时「选中就不复原」,底色就永远留在上面。 */}
|
|
106
94
|
<div className="hairline-b flex items-stretch gap-1 px-2" role="tablist" aria-label={t.drawerSections}>
|
|
107
95
|
{entries.map((entry) => (
|
|
108
96
|
<button
|
|
@@ -110,16 +98,10 @@ export function DrawerPanel({
|
|
|
110
98
|
type="button"
|
|
111
99
|
role="tab"
|
|
112
100
|
aria-selected={section === entry.id}
|
|
113
|
-
className="settings-tab-underline flex flex-1 items-center justify-center rounded-t-lg"
|
|
101
|
+
className="settings-tab-underline drawer-tab flex flex-1 items-center justify-center rounded-t-lg"
|
|
114
102
|
data-active={section === entry.id}
|
|
115
103
|
title={entry.label}
|
|
116
104
|
aria-label={entry.label}
|
|
117
|
-
onMouseEnter={(e) => {
|
|
118
|
-
if (section !== entry.id) e.currentTarget.style.background = "var(--dsw-hover)";
|
|
119
|
-
}}
|
|
120
|
-
onMouseLeave={(e) => {
|
|
121
|
-
if (section !== entry.id) e.currentTarget.style.background = "transparent";
|
|
122
|
-
}}
|
|
123
105
|
onClick={() => onSectionChange(entry.id)}
|
|
124
106
|
data-testid={`drawer-entry-${entry.id}`}
|
|
125
107
|
>
|
|
@@ -128,12 +110,23 @@ export function DrawerPanel({
|
|
|
128
110
|
))}
|
|
129
111
|
</div>
|
|
130
112
|
|
|
131
|
-
{/* 所选入口的内容:项目 / 提示词 / Git
|
|
113
|
+
{/* 所选入口的内容:项目 / 提示词 / Git 三个面板自带标题行与滚动。
|
|
114
|
+
「导入会话」段落没有标题行,这里补一行,排布照抄 PromptPanel(同样在入口列表下面、关掉整个抽屉) */}
|
|
132
115
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
133
116
|
{section === "import" ? (
|
|
134
|
-
|
|
135
|
-
<
|
|
136
|
-
|
|
117
|
+
<>
|
|
118
|
+
<div className="hairline-b flex items-center gap-2 px-4 py-2.5">
|
|
119
|
+
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 13.5, fontWeight: 600 }}>
|
|
120
|
+
{t.importSessions}
|
|
121
|
+
</span>
|
|
122
|
+
<button type="button" className="icon-btn" style={{ width: 26, height: 26 }} title={t.close} aria-label={t.close} onClick={onClose}>
|
|
123
|
+
<IconCloseOutline14 size={14} />
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="min-h-0 flex-1 overflow-y-auto px-3 py-3">
|
|
127
|
+
<SessionImportSection cwd={cwd} />
|
|
128
|
+
</div>
|
|
129
|
+
</>
|
|
137
130
|
) : section === "project" ? (
|
|
138
131
|
<ProjectPanel
|
|
139
132
|
growth={growth}
|
|
@@ -152,9 +152,12 @@ export function ExtensionDialogHost({
|
|
|
152
152
|
export function ExtensionNotices({
|
|
153
153
|
notices,
|
|
154
154
|
onDismiss,
|
|
155
|
+
children,
|
|
155
156
|
}: {
|
|
156
157
|
notices: { id: string; message: string; type: "info" | "warning" | "error"; ts: number }[];
|
|
157
158
|
onDismiss: (id: string) => void;
|
|
159
|
+
/** 同一摞里的其它常驻提示(如更新提示条),排在扩展通知上面,免得两处浮层叠在一起 */
|
|
160
|
+
children?: React.ReactNode;
|
|
158
161
|
}) {
|
|
159
162
|
// 通知 6 秒后自动消失
|
|
160
163
|
useEffect(() => {
|
|
@@ -162,10 +165,13 @@ export function ExtensionNotices({
|
|
|
162
165
|
const timers = notices.map((n) => setTimeout(() => onDismiss(n.id), Math.max(500, 6000 - (Date.now() - n.ts))));
|
|
163
166
|
return () => timers.forEach(clearTimeout);
|
|
164
167
|
}, [notices, onDismiss]);
|
|
165
|
-
|
|
168
|
+
// 有 children 时容器常驻:children 自己决定显示与否,容器里没东西时什么也看不见(pointer-events-none)
|
|
169
|
+
if (!notices.length && !children) return null;
|
|
166
170
|
const color = (type: string) => (type === "error" ? "var(--dsw-danger)" : type === "warning" ? "var(--dsw-warn)" : "var(--dsw-accent)");
|
|
171
|
+
// 手机上输入卡占满底部,放底部会盖住发送按钮:窄屏改挂到顶栏下面
|
|
167
172
|
return (
|
|
168
|
-
<div className="pointer-events-none fixed
|
|
173
|
+
<div className="pointer-events-none fixed right-4 top-16 z-[110] flex w-[min(92vw,360px)] flex-col gap-2 sm:top-auto sm:bottom-24">
|
|
174
|
+
{children}
|
|
169
175
|
{notices.map((n) => (
|
|
170
176
|
<button
|
|
171
177
|
key={n.id}
|