@rexvane/piweb 0.3.24 → 0.3.26
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 +11 -11
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +5 -5
- package/.next/react-loadable-manifest.json +13 -20
- package/.next/required-server-files.js +2 -1
- package/.next/required-server-files.json +2 -1
- package/.next/routes-manifest.json +6 -8
- 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/agent/resources/route.js +1 -0
- package/.next/server/app/api/agent/resources/route.js.nft.json +1 -0
- package/.next/server/app/api/agent/resources/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/route.js +1 -1
- package/.next/server/app/api/fs/route.js.nft.json +1 -1
- package/.next/server/app/api/git/route.js +4 -4
- package/.next/server/app/api/git/route.js.nft.json +1 -1
- package/.next/server/app/api/github/route.js +4 -4
- package/.next/server/app/api/github/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/health/route.js +1 -1
- package/.next/server/app/api/models/defaults/route.js +1 -1
- package/.next/server/app/api/models/defaults/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/pi-settings/route.js +1 -1
- package/.next/server/app/api/pi-settings/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/remote/route.js +4 -3
- 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/import/route.js +2 -2
- package/.next/server/app/api/sessions/import/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/version/route.js.nft.json +1 -1
- package/.next/server/app/api/web-auth/route.js +2 -2
- 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 +6 -7
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -7
- package/.next/server/app/index.segments/_full.segment.rsc +6 -7
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -3
- 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.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +11 -11
- package/.next/server/chunks/3924.js +3 -3
- package/.next/server/chunks/538.js +1 -1
- package/.next/server/chunks/620.js +1 -0
- package/.next/server/chunks/6229.js +1 -1
- package/.next/server/chunks/7496.js +1 -1
- package/.next/server/chunks/7662.js +2 -2
- package/.next/server/chunks/8686.js +1 -1
- package/.next/server/functions-config-manifest.json +1 -2
- 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/preview-props.json +3 -3
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/QJCtqgDKzGS2BdKABd9ch/_buildManifest.js +1 -0
- package/.next/static/chunks/2577.86c8538fbf05a8b2.js +1 -0
- package/.next/static/chunks/2716.fa561519675a3ff1.js +1 -0
- package/.next/static/chunks/2906.8a0e09bb47605cf1.js +1 -0
- package/.next/static/chunks/4673.ea36e55005c0f82f.js +1 -0
- package/.next/static/chunks/4686-4dc565817a160351.js +1 -0
- package/.next/static/chunks/4778.0d2c5d75b791d352.js +1 -0
- package/.next/static/chunks/{618.949981da99be7182.js → 618.9a22656b863f242b.js} +2 -2
- package/.next/static/chunks/app/_global-error/page-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/resources/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/config/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/files/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/fs/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/git/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/github/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/health/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/models/defaults/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/models/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/remote/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/security/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/update/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/version/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/page-71f2d3b3ed864b3d.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/webpack-ffc09b5fcc8aaa6d.js +1 -0
- package/.next/static/css/014eb1f21414e615.css +1 -0
- package/.next/static/css/56b129a13637fcfd.css +1 -0
- package/.next/static/css/7e3caf797e4cb19a.css +1 -0
- package/.next/types/app/api/agent/{[id]/side-chat → resources}/route.ts +3 -3
- package/.next/types/routes.d.ts +2 -2
- package/.next/types/validator.ts +9 -9
- package/README.md +1 -1
- package/next.config.ts +1 -1
- package/package.json +9 -9
- package/scripts/apply-global.mjs +87 -0
- package/scripts/launcher.mjs +21 -0
- package/scripts/testing/prepare-sdk-tools.mjs +19 -0
- package/src/app/api/agent/new/route.ts +2 -1
- package/src/app/api/agent/resources/route.ts +18 -0
- package/src/app/api/models/route.ts +22 -1
- package/src/app/api/plugins/route.ts +3 -2
- package/src/app/api/prompts/route.ts +3 -2
- package/src/app/api/remote/route.ts +22 -4
- package/src/app/api/sessions/[id]/export/route.ts +3 -1
- package/src/app/api/skills/route.ts +3 -2
- package/src/app/globals.css +1002 -1765
- package/src/components/AppShell.tsx +128 -93
- package/src/components/ChatInput.tsx +177 -98
- package/src/components/ChatWindow.tsx +189 -56
- package/src/components/ContextMeter.tsx +33 -9
- package/src/components/CustomSelect.module.css +142 -0
- package/src/components/CustomSelect.tsx +157 -0
- package/src/components/DrawerPanel.tsx +1 -1
- package/src/components/ExtensionUI.tsx +9 -3
- package/src/components/FileViewer.tsx +24 -21
- package/src/components/GitPanel.tsx +35 -13
- package/src/components/GrowthTree.tsx +6 -2
- package/src/components/ImageViewer.tsx +72 -0
- package/src/components/MarkdownBlock.tsx +67 -96
- package/src/components/ModelSelector.tsx +36 -39
- package/src/components/PiMark.tsx +76 -41
- package/src/components/PreviewImage.tsx +33 -0
- package/src/components/ProjectPanel.tsx +8 -5
- package/src/components/PromptPanel.tsx +85 -20
- package/src/components/ProviderSetupModal.module.css +12 -11
- package/src/components/ProviderSetupModal.tsx +107 -36
- package/src/components/RemoteSection.tsx +53 -2
- package/src/components/SessionImportSection.tsx +30 -15
- package/src/components/SessionSidebar.tsx +72 -62
- package/src/components/SettingsPanel.tsx +49 -151
- package/src/components/TrajectoryAnalyzer.module.css +26 -11
- package/src/components/TrajectoryView.tsx +11 -3
- package/src/components/chat/markdown-actions.tsx +23 -0
- package/src/components/composer/ComposerFiles.tsx +224 -0
- package/src/components/composer/ComposerImages.tsx +59 -127
- package/src/hooks/usePiWeb.ts +47 -2
- package/src/i18n.tsx +62 -80
- package/src/lib/agent-manager.ts +365 -33
- package/src/lib/chat/file-path.ts +54 -0
- package/src/lib/chat/skill-expansion.ts +51 -0
- package/src/lib/extension-ui.ts +25 -1
- package/src/lib/font.ts +37 -39
- package/src/lib/git-service.ts +4 -2
- package/src/lib/growth-turns.ts +3 -1
- package/src/lib/model-draft.ts +69 -2
- package/src/lib/models-service.ts +53 -3
- package/src/lib/pi.ts +2 -2
- package/src/lib/remote/assets.json +1 -1
- package/src/lib/remote/supervisor.ts +48 -5
- package/src/lib/security-service.ts +2 -1
- package/src/lib/session-reader.ts +25 -13
- package/src/lib/session-retention.ts +11 -8
- package/src/lib/skills-service.ts +2 -1
- package/src/lib/slash-command-token.ts +14 -0
- package/src/lib/trajectory.ts +111 -15
- package/src/lib/types.ts +28 -3
- package/src/lib/update-service.ts +29 -9
- package/src/lib/workspace-store.ts +36 -1
- package/.next/server/app/api/agent/[id]/side-chat/route.js +0 -5
- package/.next/server/app/api/agent/[id]/side-chat/route.js.nft.json +0 -1
- package/.next/server/app/api/agent/[id]/side-chat/route_client-reference-manifest.js +0 -1
- package/.next/server/chunks/3421.js +0 -1
- package/.next/static/chunks/2716.44fdf1b21454b961.js +0 -1
- package/.next/static/chunks/4673.e261707dce391be7.js +0 -1
- package/.next/static/chunks/4686-0cd87513f9ea3538.js +0 -1
- package/.next/static/chunks/4778.eddd3a5813f85824.js +0 -1
- package/.next/static/chunks/7411.5f79671208209b13.js +0 -1
- package/.next/static/chunks/8552.25c39ef4c995eba0.js +0 -1
- package/.next/static/chunks/app/_global-error/page-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/side-chat/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/config/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/files/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/fs/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/git/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/github/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/growth/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/health/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/models/defaults/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/models/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/remote/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/security/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/skills/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/update/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/version/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/app/page-dc3cb41d91c7432e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cbaaee0d7aacb85a.js +0 -1
- package/.next/static/chunks/webpack-2ac3d76b81895f5b.js +0 -1
- package/.next/static/css/08815507594e8b8d.css +0 -1
- package/.next/static/css/9fc559c2c4ad1d0f.css +0 -1
- package/.next/static/css/c6e2227c36ec2fd6.css +0 -1
- package/.next/static/css/f15a853af8b5127f.css +0 -1
- package/.next/static/lvKCJmjFj8LuqSojRVLRF/_buildManifest.js +0 -1
- package/src/app/api/agent/[id]/side-chat/route.ts +0 -77
- package/src/components/side-chat/SideChatPanel.module.css +0 -131
- package/src/components/side-chat/SideChatPanel.tsx +0 -189
- package/src/components/side-chat/icons.tsx +0 -8
- package/src/components/side-chat/request-history.ts +0 -29
- package/src/components/side-chat/store.ts +0 -248
- package/src/lib/chat/stream-settings.ts +0 -192
- package/src/lib/side-chat/context.ts +0 -123
- package/src/lib/side-chat/service.ts +0 -191
- package/src/lib/side-chat/types.ts +0 -61
- /package/.next/static/{lvKCJmjFj8LuqSojRVLRF → QJCtqgDKzGS2BdKABd9ch}/_ssgManifest.js +0 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
10
|
import {
|
|
11
11
|
IconAtOutline16,
|
|
12
|
+
IconCloseOutline14,
|
|
12
13
|
IconFileOutline16,
|
|
13
14
|
IconFolderClose16,
|
|
14
15
|
IconSendArrowUp14,
|
|
@@ -17,12 +18,14 @@ import {
|
|
|
17
18
|
} from "@/components/icons";
|
|
18
19
|
import { ContextMeter } from "@/components/ContextMeter";
|
|
19
20
|
import { COLLAPSE_AT, ComposerImages } from "@/components/composer/ComposerImages";
|
|
21
|
+
import { ComposerFiles } from "@/components/composer/ComposerFiles";
|
|
20
22
|
import { ModelSelector, type ModelChoice } from "@/components/ModelSelector";
|
|
21
23
|
import { useI18n } from "@/i18n";
|
|
22
24
|
import { useWorkspaceFiles } from "@/hooks/useWorkspaceFiles";
|
|
23
25
|
import { fmtBytes } from "@/lib/format";
|
|
24
26
|
import { otherBehavior, useEnterBehavior } from "@/lib/enter-behavior";
|
|
25
27
|
import { atToken, candidateText, suggestPaths, type PathCandidate } from "@/lib/workspace-index";
|
|
28
|
+
import { slashCommandToken } from "@/lib/slash-command-token";
|
|
26
29
|
import type { ImageAttachment } from "@/lib/types";
|
|
27
30
|
|
|
28
31
|
export interface SlashCommand {
|
|
@@ -86,6 +89,7 @@ export function ChatInput({
|
|
|
86
89
|
pendingUploadCount = 0,
|
|
87
90
|
pendingSend = false,
|
|
88
91
|
onSendPendingChange,
|
|
92
|
+
attachmentLayout = "overlay",
|
|
89
93
|
}: {
|
|
90
94
|
disabled?: boolean;
|
|
91
95
|
isStreaming: boolean;
|
|
@@ -131,6 +135,8 @@ export function ChatInput({
|
|
|
131
135
|
pendingUploadCount?: number;
|
|
132
136
|
pendingSend?: boolean;
|
|
133
137
|
onSendPendingChange?: (pending: boolean) => void;
|
|
138
|
+
/** Hero 在输入卡上方预留空间;聊天中仍让附件悬浮、不占布局高度 */
|
|
139
|
+
attachmentLayout?: "overlay" | "flow";
|
|
134
140
|
}) {
|
|
135
141
|
const [localDraft, setLocalDraft] = useState<ChatDraft>(initialDraft ?? EMPTY_CHAT_DRAFT);
|
|
136
142
|
const currentDraft = draft ?? localDraft;
|
|
@@ -159,12 +165,11 @@ export function ChatInput({
|
|
|
159
165
|
const [dragActive, setDragActive] = useState(false);
|
|
160
166
|
/** 引用 chip 超过 COLLAPSE_AT 就先折叠:手机上折行会把输入框顶掉(图片的折叠在 ComposerImages 里) */
|
|
161
167
|
const [refsExpanded, setRefsExpanded] = useState(false);
|
|
162
|
-
/** 上传文件超过 4 个时的展开状态(与引用同一套折叠规则) */
|
|
163
|
-
const [uploadsExpanded, setUploadsExpanded] = useState(false);
|
|
164
168
|
const dragDepth = useRef(0);
|
|
165
169
|
const [modelOpen, setModelOpen] = useState(false);
|
|
166
170
|
const [cmdIdx, setCmdIdx] = useState(0);
|
|
167
171
|
const [cmdDismissed, setCmdDismissed] = useState(false);
|
|
172
|
+
const [commandCaret, setCommandCaret] = useState<number | null>(currentDraft.text.length);
|
|
168
173
|
// @ 引用:打字触发候选,选中后落成引用 chip(发送时统一拼成 `@路径` 行)
|
|
169
174
|
const [mention, setMention] = useState<{ query: string; start: number; tokenEnd: number } | null>(null);
|
|
170
175
|
const [mentionIdx, setMentionIdx] = useState(0);
|
|
@@ -190,11 +195,11 @@ export function ChatInput({
|
|
|
190
195
|
autoSize();
|
|
191
196
|
}, [text]);
|
|
192
197
|
|
|
193
|
-
//
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const typedCommandMode = !!commands?.length &&
|
|
197
|
-
const filteredCommands = (commands ?? []).filter((c) => c.name.toLowerCase().startsWith(slash.
|
|
198
|
+
// 补全光标处的 token,而不是只看草稿的第一个字符
|
|
199
|
+
const commandToken = slashCommandToken(text, commandCaret);
|
|
200
|
+
const slash = commandToken?.query ?? "";
|
|
201
|
+
const typedCommandMode = !adopted && !!commands?.length && !!commandToken && !cmdDismissed;
|
|
202
|
+
const filteredCommands = (commands ?? []).filter((c) => c.name.toLowerCase().startsWith(slash.toLowerCase()));
|
|
198
203
|
const visibleCommands = filteredCommands;
|
|
199
204
|
const commandMode = typedCommandMode;
|
|
200
205
|
|
|
@@ -278,7 +283,67 @@ export function ChatInput({
|
|
|
278
283
|
}, [cmdIdx, visibleCommands.length]);
|
|
279
284
|
const effectiveIdx = Math.min(cmdIdx, Math.max(0, visibleCommands.length - 1));
|
|
280
285
|
|
|
286
|
+
const withoutCommandToken = () => commandToken ? text.slice(0, commandToken.start) + text.slice(commandToken.tokenEnd) : text;
|
|
287
|
+
const adoptCommand = (entry: SlashCommand) => {
|
|
288
|
+
// SDK 只展开开头的一个命令:已经采纳时不再覆盖(先 Backspace 摘掉)
|
|
289
|
+
if (draftRef.current.adopted) return;
|
|
290
|
+
const remaining = withoutCommandToken();
|
|
291
|
+
const caret = commandToken?.start ?? remaining.length;
|
|
292
|
+
setAdopted(entry);
|
|
293
|
+
setText(remaining);
|
|
294
|
+
setCommandCaret(caret);
|
|
295
|
+
setCmdDismissed(true);
|
|
296
|
+
setCmdIdx(0);
|
|
297
|
+
requestAnimationFrame(() => {
|
|
298
|
+
autoSize();
|
|
299
|
+
taRef.current?.focus();
|
|
300
|
+
taRef.current?.setSelectionRange(caret, caret);
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// 行内扩展命令(如 /fast)自己执行:不能为了切换它把周围的草稿、图片或引用一起发出去
|
|
305
|
+
const runInlineExtension = async (entry: SlashCommand) => {
|
|
306
|
+
if (disabled || isBlocked || sendingRef.current || pendingSend || uploadBusy) return;
|
|
307
|
+
const original = text;
|
|
308
|
+
const remaining = withoutCommandToken();
|
|
309
|
+
sendingRef.current = true;
|
|
310
|
+
setSending(true);
|
|
311
|
+
onSendPendingChange?.(true);
|
|
312
|
+
setText(remaining);
|
|
313
|
+
setCommandCaret(remaining.length);
|
|
314
|
+
setCmdDismissed(true);
|
|
315
|
+
try {
|
|
316
|
+
const result = await onSend(`/${entry.name}`, []);
|
|
317
|
+
if (!result?.success) throw new Error(result?.error || "Command failed");
|
|
318
|
+
} catch (error) {
|
|
319
|
+
setText((previous) => previous === remaining ? original : previous);
|
|
320
|
+
if (mountedRef.current) {
|
|
321
|
+
setCommandCaret(commandToken?.tokenEnd ?? original.length);
|
|
322
|
+
setCmdDismissed(false);
|
|
323
|
+
setAttachmentError(error instanceof Error ? error.message : String(error));
|
|
324
|
+
}
|
|
325
|
+
} finally {
|
|
326
|
+
sendingRef.current = false;
|
|
327
|
+
if (mountedRef.current) setSending(false);
|
|
328
|
+
onSendPendingChange?.(false);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
281
332
|
const runEntry = (entry: SlashCommand) => {
|
|
333
|
+
if (draftRef.current.adopted) return;
|
|
334
|
+
// 命令出现在正文中间:只替换这一段 token,前后文字原样保留
|
|
335
|
+
if (commandToken && (commandToken.start > 0 || commandToken.tokenEnd < text.length)) {
|
|
336
|
+
if (entry.kind === "builtin") {
|
|
337
|
+
if (entry.name === "model") setModelOpen(true);
|
|
338
|
+
else onCommand?.(entry.name, "");
|
|
339
|
+
const remaining = withoutCommandToken();
|
|
340
|
+
setText(remaining);
|
|
341
|
+
setCommandCaret(remaining.length);
|
|
342
|
+
setCmdDismissed(true);
|
|
343
|
+
} else if (entry.kind === "extension") void runInlineExtension(entry);
|
|
344
|
+
else adoptCommand(entry);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
282
347
|
const args = slash.slice(entry.name.length).trim();
|
|
283
348
|
if (entry.kind === "builtin" && entry.name === "model") {
|
|
284
349
|
setModelOpen(true);
|
|
@@ -506,28 +571,50 @@ export function ChatInput({
|
|
|
506
571
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
507
572
|
}, []);
|
|
508
573
|
|
|
509
|
-
const hasDraft = text.trim().length > 0 || images.length > 0 || uploads.length > 0 || references.length > 0;
|
|
574
|
+
const hasDraft = adopted !== null || text.trim().length > 0 || images.length > 0 || uploads.length > 0 || references.length > 0;
|
|
510
575
|
|
|
511
576
|
return (
|
|
512
577
|
<div ref={wrapRef} className="relative w-full" data-testid="composer">
|
|
513
|
-
{/*
|
|
578
|
+
{/* 排队中的消息(高质感浮动拟态胶囊 dock:左侧消息与彩色药丸徽标,右侧快捷键微胶囊按钮) */}
|
|
514
579
|
{queuedCount > 0 && (
|
|
515
|
-
<div className="pw-queue">
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
<
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
580
|
+
<div className="pw-queue" role="status" aria-live="polite">
|
|
581
|
+
<div className="pw-queue-list">
|
|
582
|
+
{queue.steering.map((q, i) => (
|
|
583
|
+
<div key={`s${i}`} className="pw-queue-row" title={q}>
|
|
584
|
+
<span className="pw-queue-tag pw-queue-tag-steer">
|
|
585
|
+
<span className="pw-queue-dot" />
|
|
586
|
+
{t.modeSteer}
|
|
587
|
+
</span>
|
|
588
|
+
<span className="pw-queue-text">{q}</span>
|
|
589
|
+
</div>
|
|
590
|
+
))}
|
|
591
|
+
{queue.followUp.map((q, i) => (
|
|
592
|
+
<div key={`f${i}`} className="pw-queue-row" title={q}>
|
|
593
|
+
<span className="pw-queue-tag pw-queue-tag-queue">
|
|
594
|
+
<span className="pw-queue-dot" />
|
|
595
|
+
{t.modeQueue}
|
|
596
|
+
</span>
|
|
597
|
+
<span className="pw-queue-text">{q}</span>
|
|
598
|
+
</div>
|
|
599
|
+
))}
|
|
600
|
+
</div>
|
|
528
601
|
<div className="pw-queue-foot">
|
|
529
|
-
<button
|
|
530
|
-
|
|
602
|
+
<button
|
|
603
|
+
type="button"
|
|
604
|
+
className="pw-queue-btn pw-queue-btn-pull"
|
|
605
|
+
onClick={() => void pullQueue()}
|
|
606
|
+
title="↑"
|
|
607
|
+
>
|
|
608
|
+
<span className="pw-queue-kbd">↑</span>
|
|
609
|
+
<span>{t.queueEdit.replace(/^[↑\s]+/, "") || t.queueEdit}</span>
|
|
610
|
+
</button>
|
|
611
|
+
<button
|
|
612
|
+
type="button"
|
|
613
|
+
className="pw-queue-btn pw-queue-btn-drop"
|
|
614
|
+
onClick={() => void onClearQueue?.()}
|
|
615
|
+
>
|
|
616
|
+
{t.queueDrop}
|
|
617
|
+
</button>
|
|
531
618
|
</div>
|
|
532
619
|
</div>
|
|
533
620
|
)}
|
|
@@ -541,7 +628,7 @@ export function ChatInput({
|
|
|
541
628
|
<div
|
|
542
629
|
ref={mentionMenuRef}
|
|
543
630
|
data-testid="mention-menu"
|
|
544
|
-
className="popover absolute bottom-full left-0 right-0 z-50 mb-2 max-h-[340px] overflow-y-auto py-1.5"
|
|
631
|
+
className="popover absolute bottom-full left-0 right-0 z-50 mb-2 max-h-[340px] overflow-x-hidden overflow-y-auto py-1.5"
|
|
545
632
|
role="listbox"
|
|
546
633
|
aria-label={t.mentionFiles}
|
|
547
634
|
style={{ overflowY: "auto" }}
|
|
@@ -568,23 +655,25 @@ export function ChatInput({
|
|
|
568
655
|
type="button"
|
|
569
656
|
role="option"
|
|
570
657
|
aria-selected={i === effectiveMentionIdx}
|
|
571
|
-
|
|
572
|
-
|
|
658
|
+
data-active={i === effectiveMentionIdx ? "true" : undefined}
|
|
659
|
+
className="isx-menu-item mx-1.5 flex w-[calc(100%-12px)] items-center gap-2 px-3 py-1.5 text-left"
|
|
573
660
|
onMouseEnter={() => setMentionIdx(i)}
|
|
574
661
|
onClick={() => applyMention(candidate)}
|
|
575
662
|
title={candidateText(candidate)}
|
|
576
663
|
>
|
|
577
|
-
{
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
664
|
+
<span className="isx-menu-icon" style={{ display: "inline-flex", flex: "none" }}>
|
|
665
|
+
{candidate.dir ? (
|
|
666
|
+
<IconFolderClose16 size={13} style={{ flex: "none", color: "var(--dsw-warn)" }} />
|
|
667
|
+
) : (
|
|
668
|
+
<IconFileOutline16 size={13} style={{ flex: "none", color: "var(--dsw-label-caption)" }} />
|
|
669
|
+
)}
|
|
670
|
+
</span>
|
|
582
671
|
{/* 和 pi 终端一样:名字在前,路径在后;根目录下的条目两者相同,不重复显示 */}
|
|
583
|
-
<span className="flex-none truncate" style={{ maxWidth: "55%", fontSize: 12.5, fontFamily: "var(--font-mono)"
|
|
672
|
+
<span className="isx-menu-shift flex-none truncate" style={{ maxWidth: "55%", fontSize: 12.5, fontFamily: "var(--font-mono)" }}>
|
|
584
673
|
{label}
|
|
585
674
|
</span>
|
|
586
675
|
{description.replace(/\/$/, "") !== label.replace(/\/$/, "") && (
|
|
587
|
-
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 11.5, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)" }}>
|
|
676
|
+
<span className="isx-menu-sub min-w-0 flex-1 truncate pr-2" style={{ fontSize: 11.5, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)" }}>
|
|
588
677
|
{description}
|
|
589
678
|
</span>
|
|
590
679
|
)}
|
|
@@ -595,7 +684,7 @@ export function ChatInput({
|
|
|
595
684
|
|
|
596
685
|
{/* 斜杠命令菜单(dsh:悬浮于输入卡上方,同宽) */}
|
|
597
686
|
{commandMode && visibleCommands.length > 0 && (
|
|
598
|
-
<div ref={cmdMenuRef} className="popover absolute bottom-full left-0 right-0 z-50 mb-2 max-h-[340px] overflow-y-auto py-1.5" role="listbox" aria-label={t.slashCatalog} style={{ overflowY: "auto" }}>
|
|
687
|
+
<div ref={cmdMenuRef} className="popover absolute bottom-full left-0 right-0 z-50 mb-2 max-h-[340px] overflow-x-hidden overflow-y-auto py-1.5" role="listbox" aria-label={t.slashCatalog} style={{ overflowY: "auto" }}>
|
|
599
688
|
<div className="px-4 pb-1 pt-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
600
689
|
{t.slashCatalog}
|
|
601
690
|
</div>
|
|
@@ -604,21 +693,21 @@ export function ChatInput({
|
|
|
604
693
|
key={`${c.kind}:${c.name}`}
|
|
605
694
|
role="option"
|
|
606
695
|
aria-selected={i === effectiveIdx}
|
|
607
|
-
|
|
608
|
-
|
|
696
|
+
data-active={i === effectiveIdx ? "true" : undefined}
|
|
697
|
+
className="isx-menu-item mx-1.5 flex w-[calc(100%-12px)] items-baseline gap-3 px-3 py-2 text-left"
|
|
609
698
|
onMouseEnter={() => setCmdIdx(i)}
|
|
610
699
|
onClick={() => runEntry(c)}
|
|
611
700
|
>
|
|
612
|
-
<span style={{ fontSize: 14, fontWeight: 500,
|
|
701
|
+
<span className="isx-menu-shift" style={{ fontSize: 14, fontWeight: 500, flex: "none" }}>{c.name}</span>
|
|
613
702
|
{c.kind !== "builtin" && (
|
|
614
|
-
<span className="rounded px-1.5 py-0.5" style={{ fontSize: 10.5, color: "var(--dsw-label-caption)", background: "var(--dsw-selector)", flex: "none" }}>
|
|
703
|
+
<span className="isx-menu-sub rounded px-1.5 py-0.5" style={{ fontSize: 10.5, color: "var(--dsw-label-caption)", background: "var(--dsw-selector)", flex: "none" }}>
|
|
615
704
|
{c.kind === "skill" ? t.skillType : c.kind === "template" ? t.promptType : t.extensionCmdType}
|
|
616
705
|
</span>
|
|
617
706
|
)}
|
|
618
707
|
{c.argumentHint && (
|
|
619
|
-
<span style={{ fontSize: 12, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)", flex: "none" }}>{c.argumentHint}</span>
|
|
708
|
+
<span className="isx-menu-sub" style={{ fontSize: 12, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)", flex: "none" }}>{c.argumentHint}</span>
|
|
620
709
|
)}
|
|
621
|
-
<span className="truncate" style={{ fontSize: 13, color: "var(--dsw-label-tertiary)" }}>
|
|
710
|
+
<span className="isx-menu-sub truncate pr-2" style={{ fontSize: 13, color: "var(--dsw-label-tertiary)" }}>
|
|
622
711
|
{c.desc}
|
|
623
712
|
</span>
|
|
624
713
|
</button>
|
|
@@ -627,11 +716,12 @@ export function ChatInput({
|
|
|
627
716
|
)}
|
|
628
717
|
|
|
629
718
|
{/*
|
|
630
|
-
* 附件轨(图片 / 引用 /
|
|
719
|
+
* 附件轨(图片 / 引用 / 上传文件):聊天中悬浮于输入卡上方,不占布局高度 ——
|
|
631
720
|
* 输入框位置不动,多出来的附件直接盖住后面的聊天记录(用户要的就是这个)。
|
|
721
|
+
* 新对话页用 flow 占位,避免 @ 引用和图片盖住上方的工作区选择器。
|
|
632
722
|
* 外层 pointer-events-none,只有附件本身接收点击,免得挡住聊天区的交互。
|
|
633
723
|
*/}
|
|
634
|
-
<div className=
|
|
724
|
+
{(images.length > 0 || references.length > 0 || uploads.length > 0) && <div className={`pointer-events-none z-20 flex flex-col gap-1.5 pb-2 ${attachmentLayout === "flow" ? "relative" : "absolute inset-x-0 bottom-full"}`} data-testid="composer-attachments">
|
|
635
725
|
{/* 附件轨:图片(超过 4 张折叠成牌堆,展开沿弧线扇开;点开可放大、切换、删除) */}
|
|
636
726
|
{images.length > 0 && (
|
|
637
727
|
<ComposerImages
|
|
@@ -681,51 +771,28 @@ export function ChatInput({
|
|
|
681
771
|
</div>
|
|
682
772
|
)}
|
|
683
773
|
|
|
684
|
-
{/*
|
|
774
|
+
{/* 附件轨:上传的普通文件(超过 4 个折叠成牌堆,展开沿竖直向上弧度展开) */}
|
|
685
775
|
{uploads.length > 0 && (
|
|
686
|
-
<
|
|
687
|
-
{
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
<button
|
|
693
|
-
type="button"
|
|
694
|
-
className="pw-ref-close"
|
|
695
|
-
disabled={sendBusy}
|
|
696
|
-
onClick={() => changeDraft((previous) => ({ ...previous, uploads: previous.uploads.filter((_, index) => index !== i) }))}
|
|
697
|
-
aria-label={`${t.delete} ${f.name}`}
|
|
698
|
-
>
|
|
699
|
-
✕
|
|
700
|
-
</button>
|
|
701
|
-
</span>
|
|
702
|
-
))}
|
|
703
|
-
{uploads.length > COLLAPSE_AT ? (
|
|
704
|
-
<button type="button" className="pw-chip" data-testid="uploads-toggle" onClick={() => setUploadsExpanded((open) => !open)}>
|
|
705
|
-
{uploadsExpanded ? t.chipCollapse : t.chipExpandMore.replace("{n}", String(uploads.length - COLLAPSE_AT))}
|
|
706
|
-
</button>
|
|
707
|
-
) : null}
|
|
708
|
-
</div>
|
|
776
|
+
<ComposerFiles
|
|
777
|
+
files={uploads}
|
|
778
|
+
disabled={sendBusy}
|
|
779
|
+
onRemove={(index) => changeDraft((previous) => ({ ...previous, uploads: previous.uploads.filter((_, at) => at !== index) }))}
|
|
780
|
+
onClear={() => changeDraft((previous) => ({ ...previous, uploads: [] }))}
|
|
781
|
+
/>
|
|
709
782
|
)}
|
|
710
783
|
|
|
711
|
-
</div>
|
|
784
|
+
</div>}
|
|
712
785
|
|
|
713
|
-
{/*
|
|
714
|
-
<div
|
|
715
|
-
className="
|
|
716
|
-
style={{
|
|
717
|
-
borderRadius: 22,
|
|
718
|
-
background: "var(--dsw-input-major)",
|
|
719
|
-
boxShadow: "var(--dsw-elevation-soft)",
|
|
720
|
-
border: "0.5px solid var(--dsw-border-l1)",
|
|
721
|
-
}}
|
|
722
|
-
>
|
|
723
|
-
<div className="px-4 pt-3 flex items-start gap-1.5">
|
|
786
|
+
{/* 卡片(Iconsax .isx-composer:24px 圆角 + 聚焦高对比描边 + 底部流光短横条)—— 拖放由 document 级监听统一接管 */}
|
|
787
|
+
<div className="isx-composer relative w-full">
|
|
788
|
+
<div className="px-4 pt-3 flex flex-col items-stretch gap-1.5">
|
|
724
789
|
{adopted && (
|
|
725
790
|
<button
|
|
726
791
|
type="button"
|
|
727
|
-
className="flex-none flex items-center gap-1 self-start"
|
|
728
|
-
|
|
792
|
+
className="flex-none flex max-w-full items-center gap-1 self-start"
|
|
793
|
+
disabled={disabled || sendBusy}
|
|
794
|
+
aria-label={`${t.delete} /${adopted.name}`}
|
|
795
|
+
title={`${t.delete} /${adopted.name} (Backspace)`}
|
|
729
796
|
style={{
|
|
730
797
|
fontSize: "var(--piweb-chat-font-size, var(--dsh-content-font-size))",
|
|
731
798
|
lineHeight: "var(--piweb-chat-line-height, 1.55)",
|
|
@@ -740,7 +807,8 @@ export function ChatInput({
|
|
|
740
807
|
}}
|
|
741
808
|
>
|
|
742
809
|
<IconTerminalOutline14 size={14} style={{ color: "var(--dsw-accent)", flex: "none" }} />
|
|
743
|
-
<span style={{ color: "var(--dsw-accent)", fontWeight: 600 }}>/{adopted.name}</span>
|
|
810
|
+
<span className="min-w-0 truncate" style={{ color: "var(--dsw-accent)", fontWeight: 600 }}>/{adopted.name}</span>
|
|
811
|
+
<IconCloseOutline14 size={12} style={{ flex: "none", color: "var(--dsw-label-caption)" }} />
|
|
744
812
|
</button>
|
|
745
813
|
)}
|
|
746
814
|
<textarea
|
|
@@ -750,11 +818,12 @@ export function ChatInput({
|
|
|
750
818
|
rows={1}
|
|
751
819
|
placeholder={adopted ? (adopted.argumentHint ?? t.inputPlaceholder) : !disabled && isBlocked ? t.blockedComposer : t.inputPlaceholder}
|
|
752
820
|
suppressHydrationWarning
|
|
753
|
-
className="min-w-0
|
|
821
|
+
className="min-w-0 w-full resize-none"
|
|
754
822
|
style={{ fontSize: "var(--piweb-chat-font-size, var(--dsh-content-font-size))", lineHeight: 1.55 }}
|
|
755
823
|
onChange={(e) => {
|
|
756
824
|
const value = e.target.value;
|
|
757
825
|
setText(value);
|
|
826
|
+
setCommandCaret(e.target.selectionStart);
|
|
758
827
|
setCmdDismissed(false);
|
|
759
828
|
setCmdIdx(0);
|
|
760
829
|
syncMention(value, e.target.selectionStart);
|
|
@@ -763,7 +832,10 @@ export function ChatInput({
|
|
|
763
832
|
onSelect={(e) => {
|
|
764
833
|
// 选中了一段文字时不弹:那时 Enter / Tab 应该照常工作
|
|
765
834
|
const ta = e.target as HTMLTextAreaElement;
|
|
766
|
-
|
|
835
|
+
const caret = ta.selectionStart === ta.selectionEnd ? ta.selectionStart : null;
|
|
836
|
+
setCommandCaret(caret);
|
|
837
|
+
if (slashCommandToken(ta.value, caret)?.start !== commandToken?.start) setCmdDismissed(false);
|
|
838
|
+
syncMention(ta.value, caret);
|
|
767
839
|
}}
|
|
768
840
|
onPaste={(e) => {
|
|
769
841
|
const files = Array.from(e.clipboardData.files);
|
|
@@ -801,13 +873,16 @@ export function ChatInput({
|
|
|
801
873
|
}
|
|
802
874
|
}
|
|
803
875
|
}
|
|
804
|
-
if (adopted &&
|
|
805
|
-
|
|
876
|
+
if (adopted && !e.nativeEvent.isComposing && (
|
|
877
|
+
(e.key === "Backspace" && e.currentTarget.selectionStart === 0 && e.currentTarget.selectionEnd === 0) ||
|
|
878
|
+
(e.key === "Escape" && text === "")
|
|
879
|
+
)) {
|
|
880
|
+
// 光标在开头时退格 / 空参数时 Esc:只摘掉命令标记,不碰任何正文
|
|
806
881
|
e.preventDefault();
|
|
807
882
|
setAdopted(null);
|
|
808
883
|
return;
|
|
809
884
|
}
|
|
810
|
-
if (commandMode && visibleCommands.length > 0) {
|
|
885
|
+
if (commandMode && visibleCommands.length > 0 && !e.nativeEvent.isComposing) {
|
|
811
886
|
if (e.key === "ArrowDown") {
|
|
812
887
|
e.preventDefault();
|
|
813
888
|
setCmdIdx((i) => Math.min(i + 1, visibleCommands.length - 1));
|
|
@@ -820,10 +895,10 @@ export function ChatInput({
|
|
|
820
895
|
}
|
|
821
896
|
if (e.key === "Escape") {
|
|
822
897
|
e.preventDefault();
|
|
823
|
-
|
|
898
|
+
setCmdDismissed(true);
|
|
824
899
|
return;
|
|
825
900
|
}
|
|
826
|
-
if (e.key === "Enter"
|
|
901
|
+
if (e.key === "Enter") {
|
|
827
902
|
e.preventDefault();
|
|
828
903
|
runEntry(visibleCommands[effectiveIdx]);
|
|
829
904
|
return;
|
|
@@ -833,13 +908,7 @@ export function ChatInput({
|
|
|
833
908
|
e.preventDefault();
|
|
834
909
|
const entry = visibleCommands[effectiveIdx];
|
|
835
910
|
if (!entry) return;
|
|
836
|
-
|
|
837
|
-
setText("");
|
|
838
|
-
setCmdIdx(0);
|
|
839
|
-
requestAnimationFrame(() => {
|
|
840
|
-
autoSize();
|
|
841
|
-
taRef.current?.focus();
|
|
842
|
-
});
|
|
911
|
+
adoptCommand(entry);
|
|
843
912
|
return;
|
|
844
913
|
}
|
|
845
914
|
}
|
|
@@ -900,13 +969,23 @@ export function ChatInput({
|
|
|
900
969
|
|
|
901
970
|
{/* 停止 / 发送圆钮:运行中没草稿 → 停止;运行中有草稿 → 幽灵停止钮 + 发送(发送即按设置插话 / 排队) */}
|
|
902
971
|
{isStreaming && !hasDraft ? (
|
|
903
|
-
<button
|
|
972
|
+
<button
|
|
973
|
+
className="btn-primary-circle isx-btn-stop"
|
|
974
|
+
data-streaming="true"
|
|
975
|
+
title={t.stop}
|
|
976
|
+
onClick={() => void stopAndRestore()}
|
|
977
|
+
>
|
|
904
978
|
<IconStopFill16 size={16} />
|
|
905
979
|
</button>
|
|
906
980
|
) : (
|
|
907
981
|
<>
|
|
908
982
|
{isStreaming && (
|
|
909
|
-
<button
|
|
983
|
+
<button
|
|
984
|
+
className="btn-ghost-circle isx-btn-stop"
|
|
985
|
+
data-streaming="true"
|
|
986
|
+
title={t.stop}
|
|
987
|
+
onClick={() => void stopAndRestore()}
|
|
988
|
+
>
|
|
910
989
|
<IconStopFill16 size={14} />
|
|
911
990
|
</button>
|
|
912
991
|
)}
|