@spzxg/cli-ui 0.6.2 → 0.6.5
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/CHANGELOG.md +26 -0
- package/README.md +137 -11
- package/dist/cli-ui-embed-BmLN2Jjg.js +43290 -0
- package/dist/{cli-ui-index-BY5i3wId.js → cli-ui-index-D4qfPHMd.js} +2 -2
- package/dist/{cli-ui-jszip.min-BZsUwI-s.js → cli-ui-jszip.min-DC9898pC.js} +1 -1
- package/dist/{cli-ui-jszip.min-m01fexHW.js → cli-ui-jszip.min-DpEl0G1z.js} +2 -2
- package/dist/{cli-ui-xlsx-CpWicJLT.js → cli-ui-xlsx-zs7oEtPj.js} +1 -1
- package/dist/cli-ui.css +32 -1
- package/dist/cli-ui.js +1 -1
- package/package.json +14 -8
- package/src/board/BrowserPane.tsx +160 -74
- package/src/board/ChangesPane.tsx +2 -2
- package/src/board/CliBoard.tsx +79 -50
- package/src/board/FileViewer.tsx +32 -22
- package/src/board/FilesPane.tsx +32 -15
- package/src/board/PanelControls.tsx +28 -0
- package/src/board/blocks.tsx +5 -5
- package/src/board/context.ts +65 -0
- package/src/board/fileOps.ts +26 -3
- package/src/board/fileWatch.ts +88 -0
- package/src/board/folderDownload.ts +30 -14
- package/src/board/panels.ts +33 -0
- package/src/board/textLoad.ts +2 -2
- package/src/board/workspace.ts +38 -9
- package/src/chat/CliChat.tsx +73 -30
- package/src/chat/Composer.tsx +41 -41
- package/src/chat/RunControls.tsx +99 -0
- package/src/chat/configuration.ts +15 -0
- package/src/chat/draft.ts +41 -0
- package/src/chat/export.ts +2 -2
- package/src/components.tsx +13 -0
- package/src/core/blocks.ts +2 -2
- package/src/core/callbacks.ts +8 -0
- package/src/core/client.ts +8 -6
- package/src/core/conversation.ts +3 -3
- package/src/core/id.ts +15 -0
- package/src/core/identity.ts +33 -0
- package/src/core/provider.tsx +24 -14
- package/src/core/store.ts +83 -26
- package/src/core/types.ts +34 -0
- package/src/elements.tsx +135 -29
- package/src/embed.ts +6 -8
- package/src/index.ts +24 -17
- package/src/layout/CliSplit.tsx +1 -1
- package/src/msglist/CliMsgList.tsx +10 -10
- package/src/operator/branchFlow.ts +35 -0
- package/src/operator/index.ts +9 -0
- package/src/operator/liveStream.ts +1 -0
- package/src/operator/node/AgentConnect.tsx +624 -0
- package/src/operator/node/AppearancePanel.tsx +35 -0
- package/src/operator/node/BranchHistory.tsx +67 -0
- package/src/operator/node/BranchNavigator.tsx +36 -0
- package/src/operator/node/BrowserPane.tsx +128 -0
- package/src/operator/node/BrowserPopup.tsx +22 -0
- package/src/operator/node/Chat.tsx +517 -0
- package/src/operator/node/CommandPalette.tsx +213 -0
- package/src/operator/node/Composer.tsx +287 -0
- package/src/operator/node/InsightsView.tsx +831 -0
- package/src/operator/node/Layout.tsx +190 -0
- package/src/operator/node/Markdown.tsx +61 -0
- package/src/operator/node/Modal.tsx +179 -0
- package/src/operator/node/NeedsYouMenu.tsx +54 -0
- package/src/operator/node/OnboardingWizard.tsx +250 -0
- package/src/operator/node/Orchestrator.tsx +634 -0
- package/src/operator/node/ProjectLanding.tsx +86 -0
- package/src/operator/node/ProjectsColumn.tsx +137 -0
- package/src/operator/node/ProviderAuth.tsx +561 -0
- package/src/operator/node/RunControls.tsx +2 -0
- package/src/operator/node/RunSwitch.tsx +113 -0
- package/src/operator/node/Services.tsx +245 -0
- package/src/operator/node/SessionRail.tsx +260 -0
- package/src/operator/node/SessionView.tsx +288 -0
- package/src/operator/node/SettingsView.tsx +690 -0
- package/src/operator/node/TaskBoard.tsx +352 -0
- package/src/operator/node/TaskChanges.tsx +530 -0
- package/src/operator/node/TaskHero.tsx +50 -0
- package/src/operator/node/TasksColumn.tsx +200 -0
- package/src/operator/node/Terminal.tsx +143 -0
- package/src/operator/node/Transcript.tsx +463 -0
- package/src/operator/node/ViewFile.tsx +79 -0
- package/src/operator/node/Welcome.tsx +101 -0
- package/src/operator/node/WorktreePrune.tsx +333 -0
- package/src/operator/node/agents.ts +58 -0
- package/src/operator/node/api.ts +40 -0
- package/src/operator/node/boardBackend.ts +80 -0
- package/src/operator/node/boardEvents.ts +22 -0
- package/src/operator/node/boardInteraction.ts +15 -0
- package/src/operator/node/boardState.ts +57 -0
- package/src/operator/node/boardWorkspace.ts +12 -0
- package/src/operator/node/branchFlow.ts +1 -0
- package/src/operator/node/composerDraft.ts +52 -0
- package/src/operator/node/fileLinks.ts +81 -0
- package/src/operator/node/format.ts +290 -0
- package/src/operator/node/github.tsx +234 -0
- package/src/operator/node/icons.tsx +88 -0
- package/src/operator/node/liveStream.ts +270 -0
- package/src/operator/node/modals.tsx +720 -0
- package/src/operator/node/navHistory.ts +70 -0
- package/src/operator/node/persist.ts +26 -0
- package/src/operator/node/protocol/agents/optIn.ts +9 -0
- package/src/operator/node/protocol/agents/types.ts +394 -0
- package/src/operator/node/protocol/approvalFailure.ts +77 -0
- package/src/operator/node/protocol/authFailure.ts +66 -0
- package/src/operator/node/protocol/customModels.ts +23 -0
- package/src/operator/node/protocol/events.ts +13 -0
- package/src/operator/node/protocol/features.ts +70 -0
- package/src/operator/node/protocol/internalUsage.ts +1 -0
- package/src/operator/node/protocol/promptLimits.ts +52 -0
- package/src/operator/node/protocol/storageRetention.ts +13 -0
- package/src/operator/node/protocol/types.ts +332 -0
- package/src/operator/node/protocol/usageLimit.ts +72 -0
- package/src/operator/node/shared.tsx +89 -0
- package/src/operator/node/transcriptModel.ts +129 -0
- package/src/operator/node/types.ts +296 -0
- package/src/operator/node/useGlobalEvents.ts +97 -0
- package/src/operator/node/useOrchestrator.ts +696 -0
- package/src/operator/node/usePrefs.ts +114 -0
- package/src/operator/node/useRecaps.ts +91 -0
- package/src/operator/node/useTaskStream.ts +279 -0
- package/src/operator.base.css +1798 -0
- package/src/operator.css +186 -16
- package/src/styles.css +20 -0
- package/src/ui/Markdown.tsx +7 -2
- package/src/ui/Popover.tsx +77 -0
- package/src/ui/TextBlock.tsx +3 -3
- package/src/ui/Transcript.tsx +10 -8
- package/src/ui/fileLinks.ts +6 -0
- package/src/ui/format.ts +1 -1
- package/src/ui/shared.tsx +1 -1
- package/src/ui/transcriptModel.ts +2 -2
- package/dist/cli-ui-embed-6IJ_jRVh.js +0 -41223
package/src/core/store.ts
CHANGED
|
@@ -10,12 +10,18 @@
|
|
|
10
10
|
*
|
|
11
11
|
* 单独用某个组件时它会自己建一个仓库;放进同一个 <CliCenterProvider>(或同一个 group 的 Web Component)就共享一个。
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
13
|
+
import { clientId } from "./id";
|
|
14
|
+
import { connectionDraftScope } from "./identity";
|
|
15
|
+
import { notifyCallback } from "./callbacks";
|
|
16
|
+
import { CenterError, createCenterClient, firstLine, type CenterClient, type CenterClientOptions } from "./client";
|
|
17
|
+
import { addTurnsIfAbsent, applyItems, applyOlderPage, applySnapshot, closedCallback, convEnded, convKey, emptyConv, isChatTask, NEW_TASK, parseConvKey, TERMINAL_JOB, TERMINAL_TURN, turnCallback, upsertTurns, type ConvState } from "./conversation";
|
|
18
|
+
import { formatAnswersText } from "../ui/format";
|
|
19
|
+
import { backgroundBlock, charCount, MAX_MESSAGE_CHARS, serializeBlock } from "./blocks";
|
|
20
|
+
import { cloneBoardView, reportBoardOwner, activateBoardOwner, selectedBoardOwner, mergeBoardHistory, rememberBoardFile } from "../board/context";
|
|
21
|
+
import { cloneWorkspaceChange, subscribeWorkspaceFiles, retryWorkspaceFiles, type WorkspaceWatchOptions } from "../board/fileWatch";
|
|
22
|
+
import type { WorkspaceChangeEvent, WorkspaceWatchStatus } from "./types";
|
|
23
|
+
import { createWorkspace, type BoardView, type CliWorkspace } from "../board/workspace";
|
|
24
|
+
import type { AskAnswers, AskQuestion, CenterJob, CenterTurn, ConversationClosedCallback, ConversationTurnCallback, NewConversationOptions } from "./types";
|
|
19
25
|
|
|
20
26
|
export interface JobEntry {
|
|
21
27
|
job: CenterJob | null;
|
|
@@ -46,6 +52,8 @@ export interface CliState {
|
|
|
46
52
|
viewing: Record<string, { ref: BoardRef; off: boolean }>;
|
|
47
53
|
/** 看板现在的样子(按对话键):正在看的文件、开着的文件页签、当前页签。没有看板时没有。 */
|
|
48
54
|
boards: Record<string, BoardView>;
|
|
55
|
+
/** 最近看板背景,收起/卸载后保留,供下一条消息被动参考。 */
|
|
56
|
+
boardContext: Record<string, BoardView>;
|
|
49
57
|
/** 业务方补充的项目背景(按对话键;"" = 还没建的新对话),随下一条消息发出(见 appendContext)。 */
|
|
50
58
|
context: Record<string, ContextItem[]>;
|
|
51
59
|
}
|
|
@@ -100,12 +108,24 @@ export type CliEvent =
|
|
|
100
108
|
| { type: "board_ref"; key: string; refs: BoardRef[] }
|
|
101
109
|
/** 看板上切了页签、打开 / 关掉了文件(view 为 null = 看板关了)。 */
|
|
102
110
|
| { type: "board_change"; key: string; view: BoardView | null }
|
|
111
|
+
| { type: "board_context"; jobId: string; view: BoardView }
|
|
112
|
+
| WorkspaceChangeEvent
|
|
113
|
+
| WorkspaceWatchStatus
|
|
103
114
|
/**
|
|
104
115
|
* 补充背景变了:追加 / 去掉(pending 是还没发出的),或随消息发出了(sent 是这次发出的)。
|
|
105
116
|
* key 是对话键;新对话还没建时是 ""。
|
|
106
117
|
*/
|
|
107
118
|
| { type: "context"; key: string; pending: ContextItem[]; sent?: ContextItem[] };
|
|
108
119
|
|
|
120
|
+
/** Public workspace metadata must not be shared between business listeners. */
|
|
121
|
+
export function cloneWorkspaceEvent(event:CliEvent):CliEvent {
|
|
122
|
+
if(event.type==='files_changed')return cloneWorkspaceChange(event);
|
|
123
|
+
if(event.type==='files_watch')return {...event};
|
|
124
|
+
if(event.type==='board_context')return {...event,view:cloneBoardView(event.view)};
|
|
125
|
+
if(event.type==='board_change')return {...event,view:event.view?cloneBoardView(event.view):null};
|
|
126
|
+
return event;
|
|
127
|
+
}
|
|
128
|
+
|
|
109
129
|
/** 看板要打开的东西。 */
|
|
110
130
|
export type OpenTarget = { kind: "file"; path: string } | { kind: "browser" } | { kind: "changes" } | { kind: "files"; path?: string }
|
|
111
131
|
/** 宿主用 extraTabs 加的页签。 */
|
|
@@ -199,7 +219,7 @@ export function createCliStore(options: CliStoreOptions = {}) {
|
|
|
199
219
|
// 列表只放宿主给的 jobIds 和本页里新建 / 接续出来的对话:不从 center 发现,也不从本地存储恢复——
|
|
200
220
|
// 不传就不加载,免得把这个产品线令牌下别的业务、别的用户的对话列出来。
|
|
201
221
|
|
|
202
|
-
let state: CliState = { selected: null, jobIds: [], jobs: {}, convs: {}, listLoading: false, listError: null, refs: {}, viewing: {}, boards: {}, context: {} };
|
|
222
|
+
let state: CliState = { selected: null, jobIds: [], jobs: {}, convs: {}, listLoading: false, listError: null, refs: {}, viewing: {}, boards: {}, boardContext: {}, context: {} };
|
|
203
223
|
const listeners = new Set<() => void>();
|
|
204
224
|
const eventListeners = new Set<(event: CliEvent) => void>();
|
|
205
225
|
const settledTurns = new Set<string>();
|
|
@@ -210,9 +230,18 @@ export function createCliStore(options: CliStoreOptions = {}) {
|
|
|
210
230
|
|
|
211
231
|
const set = (next: Partial<CliState>) => {
|
|
212
232
|
state = { ...state, ...next };
|
|
213
|
-
for (const fn of listeners) fn
|
|
233
|
+
for (const fn of [...listeners]) notifyCallback(fn, undefined);
|
|
234
|
+
};
|
|
235
|
+
// Business callbacks may synchronously navigate or register more listeners.
|
|
236
|
+
// Preserve emission order and keep a fixed audience for each notification.
|
|
237
|
+
const eventQueue:Array<{event:CliEvent;listeners:Array<(event:CliEvent)=>void>}>=[];
|
|
238
|
+
let dispatching=false;
|
|
239
|
+
const emit = (event:CliEvent) => {
|
|
240
|
+
eventQueue.push({event,listeners:[...eventListeners]});if(dispatching)return;
|
|
241
|
+
dispatching=true;
|
|
242
|
+
try{while(eventQueue.length){const next=eventQueue.shift()!;for(const fn of next.listeners)notifyCallback(fn,cloneWorkspaceEvent(next.event));}}
|
|
243
|
+
finally{dispatching=false;}
|
|
214
244
|
};
|
|
215
|
-
const emit = (event: CliEvent) => { for (const fn of eventListeners) fn(event); };
|
|
216
245
|
const patchConv = (key: string, fn: (conv: ConvState) => ConvState) => {
|
|
217
246
|
const prev = state.convs[key] ?? emptyConv(key);
|
|
218
247
|
const next = fn(prev);
|
|
@@ -286,7 +315,7 @@ export function createCliStore(options: CliStoreOptions = {}) {
|
|
|
286
315
|
|
|
287
316
|
function removeJob(jobId: string) {
|
|
288
317
|
const ids = state.jobIds.filter((id) => id !== jobId);
|
|
289
|
-
set({ jobIds: ids, selected: state.selected === jobId ? null : state.selected });
|
|
318
|
+
set({ jobIds: ids, selected: state.selected && parseConvKey(state.selected).jobId === jobId ? null : state.selected });
|
|
290
319
|
}
|
|
291
320
|
|
|
292
321
|
/** 对话接续:旧 job 换成新 job(列表位置不变,选中跟过去)。oldId / newId 可以是对话键。 */
|
|
@@ -723,15 +752,44 @@ export function createCliStore(options: CliStoreOptions = {}) {
|
|
|
723
752
|
}
|
|
724
753
|
|
|
725
754
|
// ── 看板现在的样子(给业务方:当前打开的文件等)────────
|
|
726
|
-
function
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if
|
|
733
|
-
|
|
734
|
-
|
|
755
|
+
function applyBoardSelection(key:string,top:BoardView|null,history:import("../board/workspace").BoardFile[]) {
|
|
756
|
+
const current=state.boards[key],previous=state.boardContext[key];
|
|
757
|
+
const boardChanged=JSON.stringify(top)!==JSON.stringify(current??null);
|
|
758
|
+
const retained=top?{...cloneBoardView(top),history,visible:top.visible!==false}:previous?{...cloneBoardView(previous),history,visible:false}:null;
|
|
759
|
+
const contextChanged=!!retained&&JSON.stringify(previous)!==JSON.stringify(retained);
|
|
760
|
+
if(!boardChanged&&!contextChanged)return;
|
|
761
|
+
const boards={...state.boards};if(top)boards[key]=cloneBoardView(top);else delete boards[key];
|
|
762
|
+
// Commit both public views before subscribers can read them or navigate again.
|
|
763
|
+
set({...(boardChanged?{boards}:{}),...(contextChanged?{boardContext:{...state.boardContext,[key]:retained!}}:{})});
|
|
764
|
+
if(boardChanged&&state.boards[key]===boards[key])emit({type:"board_change",key,view:top?cloneBoardView(top):null});
|
|
765
|
+
// A reentrant callback may already have selected a newer file. Do not emit
|
|
766
|
+
// or restore the older automatic context over that newer selection.
|
|
767
|
+
if(contextChanged&&state.boardContext[key]===retained)emit({type:"board_context",jobId:key,view:cloneBoardView(retained!)});
|
|
768
|
+
}
|
|
769
|
+
function reportBoard(key:string,view:BoardView|null,owner="default") {
|
|
770
|
+
const sources=boardSources.get(key)??new Map<string,BoardView>();
|
|
771
|
+
let history=state.boardContext[key]?.history??[];
|
|
772
|
+
if(view){history=mergeBoardHistory(history,sources.get(owner),view);reportBoardOwner(sources,owner,cloneBoardView(view));}
|
|
773
|
+
else sources.delete(owner);
|
|
774
|
+
if(sources.size)boardSources.set(key,sources);else boardSources.delete(key);
|
|
775
|
+
const top=selectedBoardOwner(sources)?.[1]??null;applyBoardSelection(key,top,history);
|
|
776
|
+
}
|
|
777
|
+
function activateBoard(key:string,owner:string) {
|
|
778
|
+
const sources=boardSources.get(key);if(!sources)return;
|
|
779
|
+
const top=activateBoardOwner(sources,owner)?.[1];if(!top)return;
|
|
780
|
+
const file=top.current??(top.file?{path:top.file,sourceId:key}:null);
|
|
781
|
+
const history=file?rememberBoardFile(state.boardContext[key]?.history??[],file):state.boardContext[key]?.history??[];
|
|
782
|
+
applyBoardSelection(key,top,history);
|
|
783
|
+
}
|
|
784
|
+
const fileSubscriptions = new Map<string,{listeners:Set<{change?:(event:WorkspaceChangeEvent)=>void;status?:WorkspaceWatchOptions['onStatus']}>;stop:()=>void;status?:WorkspaceWatchStatus}>();
|
|
785
|
+
function watchWorkspace(key:string,listener?:(event:WorkspaceChangeEvent)=>void,options:WorkspaceWatchOptions={}) {
|
|
786
|
+
if(!client.board) throw new Error("当前后端不支持工作区文件订阅");
|
|
787
|
+
const item={change:listener,status:options.onStatus};let subscription=fileSubscriptions.get(key);
|
|
788
|
+
if(!subscription){
|
|
789
|
+
subscription={listeners:new Set([item]),stop:()=>{}};fileSubscriptions.set(key,subscription);const owned=subscription;
|
|
790
|
+
owned.stop=subscribeWorkspaceFiles(client.board,key,event=>{emit(event);for(const next of [...owned.listeners])notifyCallback(next.change,cloneWorkspaceChange(event));},{...options,onStatus:status=>{owned.status=status;emit(status);for(const next of [...owned.listeners])notifyCallback(next.status,{...status});}});
|
|
791
|
+
}else{subscription.listeners.add(item);if(subscription.status)notifyCallback(item.status,{...subscription.status});retryWorkspaceFiles(client.board,key);}
|
|
792
|
+
const owned=subscription;let active=true;return()=>{if(!active)return;active=false;owned.listeners.delete(item);if(!owned.listeners.size){if(fileSubscriptions.get(key)===owned)fileSubscriptions.delete(key);owned.stop();}};
|
|
735
793
|
}
|
|
736
794
|
|
|
737
795
|
// ── 补充背景:业务方随时追加,随下一条消息发给智能体 ────────
|
|
@@ -773,6 +831,8 @@ export function createCliStore(options: CliStoreOptions = {}) {
|
|
|
773
831
|
backend: client.board,
|
|
774
832
|
key: k,
|
|
775
833
|
view: () => state.boards[k],
|
|
834
|
+
context: () => state.boardContext[k] ? cloneBoardView(state.boardContext[k]) : null,
|
|
835
|
+
watch: (listener, options) => watchWorkspace(k,listener,options),
|
|
776
836
|
open: (path) => openInBoard(k, { kind: "file", path }),
|
|
777
837
|
});
|
|
778
838
|
}
|
|
@@ -802,6 +862,7 @@ export function createCliStore(options: CliStoreOptions = {}) {
|
|
|
802
862
|
|
|
803
863
|
return {
|
|
804
864
|
client,
|
|
865
|
+
draftScope: connectionDraftScope(options),
|
|
805
866
|
getState: () => state,
|
|
806
867
|
/** 在看板里打开文件 / 浏览器 / 改动。返回有没有看板接住。 */
|
|
807
868
|
openInBoard,
|
|
@@ -821,11 +882,11 @@ export function createCliStore(options: CliStoreOptions = {}) {
|
|
|
821
882
|
addRef, removeRef, takeRefs, restoreRefs,
|
|
822
883
|
setViewing, dismissViewing, takeViewing, restoreViewing,
|
|
823
884
|
/** 看板报告自己的样子(看板组件用)。 */
|
|
824
|
-
reportBoard,
|
|
885
|
+
reportBoard, activateBoard,
|
|
825
886
|
/** 业务方追加项目背景:随下一条消息发给智能体。 */
|
|
826
887
|
appendContext, removeContext, pendingContext,
|
|
827
888
|
/** 工作区文件接口(不显示看板也能用):列文件、读文件、下载、打包。不传 = 当前选中的对话。 */
|
|
828
|
-
workspace,
|
|
889
|
+
workspace, watchWorkspace,
|
|
829
890
|
open, send, answerAsk, cancelTurn, close, loadOlder,
|
|
830
891
|
};
|
|
831
892
|
}
|
|
@@ -854,8 +915,4 @@ async function retryOnce<T>(fn: () => Promise<T>): Promise<T> {
|
|
|
854
915
|
}
|
|
855
916
|
}
|
|
856
917
|
|
|
857
|
-
|
|
858
|
-
const cryptoObj = globalThis.crypto as Crypto | undefined;
|
|
859
|
-
if (cryptoObj?.randomUUID) return cryptoObj.randomUUID();
|
|
860
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
861
|
-
}
|
|
918
|
+
const uid = clientId;
|
package/src/core/types.ts
CHANGED
|
@@ -380,6 +380,8 @@ export interface FileEntry {
|
|
|
380
380
|
type: "dir" | "file";
|
|
381
381
|
size: number;
|
|
382
382
|
mtime: number;
|
|
383
|
+
/** Opaque metadata fingerprint for reconnect reconciliation, not an immutable content version. */
|
|
384
|
+
changeToken?: string;
|
|
383
385
|
}
|
|
384
386
|
|
|
385
387
|
export interface DirListing { type: "dir"; path: string; entries: FileEntry[]; truncated: boolean; /** 目录里的条目总数(新版节点给)。 */ total?: number }
|
|
@@ -436,3 +438,35 @@ export type BrowserMessage =
|
|
|
436
438
|
|
|
437
439
|
/** 浏览器实时连接(GET /browser/live)的画面连接状况。 */
|
|
438
440
|
export type BrowserFramesState = { state: "idle" | "connecting" | "live" | "retrying"; detail?: string };
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
export interface WorkspaceFileChange {
|
|
444
|
+
kind: "created" | "modified" | "deleted";
|
|
445
|
+
path: string;
|
|
446
|
+
file?: FileEntry;
|
|
447
|
+
previous?: FileEntry;
|
|
448
|
+
}
|
|
449
|
+
export type WorkspaceFileBatch =
|
|
450
|
+
| { type: "snapshot"; files: FileEntry[]; revision?: string; complete: boolean }
|
|
451
|
+
| { type: "changes"; changes: WorkspaceFileChange[]; revision?: string; complete: boolean }
|
|
452
|
+
| { type: "resync"; reason: string; complete: false; revision?: string };
|
|
453
|
+
export interface WorkspaceChangeEvent {
|
|
454
|
+
type: "files_changed";
|
|
455
|
+
/** 对话键;节点适配下是 task.id。 */
|
|
456
|
+
jobId: string;
|
|
457
|
+
mode: "events" | "poll";
|
|
458
|
+
coverage: "workspace" | "listed-files";
|
|
459
|
+
complete: boolean;
|
|
460
|
+
changes: WorkspaceFileChange[];
|
|
461
|
+
revision?: string;
|
|
462
|
+
resync?: boolean;
|
|
463
|
+
}
|
|
464
|
+
export interface WorkspaceWatchStatus {
|
|
465
|
+
type: "files_watch";
|
|
466
|
+
jobId: string;
|
|
467
|
+
state: "connecting" | "ready" | "reconnecting" | "error";
|
|
468
|
+
mode: "events" | "poll";
|
|
469
|
+
coverage: "workspace" | "listed-files";
|
|
470
|
+
complete: boolean;
|
|
471
|
+
error?: string;
|
|
472
|
+
}
|
package/src/elements.tsx
CHANGED
|
@@ -28,34 +28,111 @@
|
|
|
28
28
|
* 渲染在 light DOM 里(不用 Shadow DOM),样式靠页面引入 styles.css,作用域都在 .cli-ui 内。
|
|
29
29
|
*/
|
|
30
30
|
import { createRoot, type Root } from "react-dom/client";
|
|
31
|
-
import { CliMsgList } from "./
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import type
|
|
38
|
-
import { attachSplit, type SplitHandle } from "./layout/split.js";
|
|
31
|
+
import { CliMsgList, CliChat, CliBoard } from "./components";
|
|
32
|
+
import type { BoardTab } from "./board/CliBoard";
|
|
33
|
+
import { CliBrowserView, CliFilePreview } from "./board/blocks";
|
|
34
|
+
import { createCliStore, cloneWorkspaceEvent, type CliEvent, type CliStore } from "./core/store";
|
|
35
|
+
import { convKey } from "./core/conversation";
|
|
36
|
+
import type { CliWorkspace } from "./board/workspace";
|
|
37
|
+
import { attachSplit, type SplitHandle } from "./layout/split";
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
type StoreOptions = ReturnType<typeof storeOptions>;
|
|
40
|
+
type GroupMember = { el: HTMLElement; refresh: () => void };
|
|
41
|
+
type StoreBucket = { store: CliStore; options: StoreOptions; members: Set<GroupMember> };
|
|
42
|
+
type StoreGroup = { buckets: Map<string, StoreBucket>; preferred: StoreBucket | null; owner: GroupMember | null; configured: boolean; members: Set<GroupMember> };
|
|
43
|
+
type GroupBinding = { groupName: string; group: StoreGroup; bucket: StoreBucket };
|
|
44
|
+
const groups = new Map<string, StoreGroup>();
|
|
45
|
+
const STORE_ATTRS = ["base-url", "token", "line", "namespace", "new-conversation"] as const;
|
|
46
|
+
const identity = (options: StoreOptions) => JSON.stringify(options);
|
|
47
|
+
const configured = (el: HTMLElement) => STORE_ATTRS.some(name => el.hasAttribute(name));
|
|
48
|
+
|
|
49
|
+
const memberships = new WeakMap<GroupMember, { groupName: string; group: StoreGroup }>();
|
|
50
|
+
function notifyGroup(group: StoreGroup, except?: GroupMember) {
|
|
51
|
+
const peers = [...group.members];
|
|
52
|
+
queueMicrotask(() => { for (const peer of peers) if (peer !== except) peer.refresh(); });
|
|
53
|
+
}
|
|
54
|
+
function leaveGroup(member: GroupMember) {
|
|
55
|
+
const membership = memberships.get(member);
|
|
56
|
+
if (!membership) return;
|
|
57
|
+
const { groupName, group } = membership;
|
|
58
|
+
group.members.delete(member);
|
|
59
|
+
for (const [key, bucket] of group.buckets) {
|
|
60
|
+
bucket.members.delete(member);
|
|
61
|
+
if (!bucket.members.size && bucket !== group.preferred) group.buckets.delete(key);
|
|
62
|
+
}
|
|
63
|
+
if (group.owner === member) group.owner = null;
|
|
64
|
+
memberships.delete(member);
|
|
65
|
+
if (!group.members.size) groups.delete(groupName); else notifyGroup(group);
|
|
66
|
+
}
|
|
67
|
+
function releaseGroup(binding: GroupBinding, member: GroupMember, retainOwner = false) {
|
|
68
|
+
const { group, bucket } = binding;
|
|
69
|
+
bucket.members.delete(member);
|
|
70
|
+
if (!retainOwner && group.owner === member) group.owner = null;
|
|
71
|
+
if (!bucket.members.size && bucket !== group.preferred) group.buckets.delete(identity(bucket.options));
|
|
72
|
+
notifyGroup(group, member);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function inheritedOptions(el: HTMLElement, base: StoreOptions | null): StoreOptions {
|
|
76
|
+
if (!base) return storeOptions(el);
|
|
77
|
+
return {
|
|
78
|
+
baseUrl: el.hasAttribute("base-url") ? el.getAttribute("base-url")! : base.baseUrl,
|
|
79
|
+
token: el.hasAttribute("token") ? el.getAttribute("token")! : base.token,
|
|
80
|
+
line: el.hasAttribute("line") ? el.getAttribute("line")! : base.line,
|
|
81
|
+
namespace: el.hasAttribute("namespace") ? el.getAttribute("namespace")! : base.namespace,
|
|
82
|
+
newConversation: el.hasAttribute("new-conversation") ? jsonAttr(el, "new-conversation") : base.newConversation,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
41
85
|
|
|
42
86
|
// 服务端渲染(Next.js 等)时没有 HTMLElement:模块照样能导入,defineCliElements() 在服务端什么都不做。
|
|
43
87
|
const Base = (typeof HTMLElement === "undefined" ? class {} : HTMLElement) as typeof HTMLElement;
|
|
44
88
|
|
|
45
|
-
function storeFor(el: HTMLElement):
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
groups.set(key, store);
|
|
89
|
+
function storeFor(el: HTMLElement, member: GroupMember, previous?: GroupBinding): GroupBinding | undefined {
|
|
90
|
+
const groupName = el.getAttribute("group");
|
|
91
|
+
if (!groupName) { leaveGroup(member); return undefined; }
|
|
92
|
+
if (memberships.get(member)?.groupName !== groupName) leaveGroup(member);
|
|
93
|
+
let group = groups.get(groupName);
|
|
94
|
+
if (!group) {
|
|
95
|
+
group = { buckets: new Map(), preferred: null, owner: null, configured: false, members: new Set() };
|
|
96
|
+
groups.set(groupName, group);
|
|
54
97
|
}
|
|
55
|
-
|
|
98
|
+
group.members.add(member);
|
|
99
|
+
memberships.set(member, { groupName, group });
|
|
100
|
+
const ownsDefaults = group.owner === member || (!group.configured && configured(el));
|
|
101
|
+
const matches = [...group.buckets.values()].filter(item => [...item.members].some(peer => configured(peer.el))).filter(item => {
|
|
102
|
+
const values = storeOptions(el);
|
|
103
|
+
return STORE_ATTRS.every(name => {
|
|
104
|
+
if (!el.hasAttribute(name)) return true;
|
|
105
|
+
const key = ({ "base-url": "baseUrl", token: "token", line: "line", namespace: "namespace", "new-conversation": "newConversation" } as const)[name as typeof STORE_ATTRS[number]];
|
|
106
|
+
return JSON.stringify(values[key as keyof StoreOptions]) === JSON.stringify(item.options[key as keyof StoreOptions]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
if (!ownsDefaults && matches.length > 1) throw new Error("同一 group 有多个连接配置,请明确设置 base-url、token、line 或 namespace,避免混用会话。");
|
|
110
|
+
const options = inheritedOptions(el, ownsDefaults ? null : matches[0]?.options ?? group.preferred?.options ?? null);
|
|
111
|
+
const key = identity(options);
|
|
112
|
+
let bucket = group.buckets.get(key);
|
|
113
|
+
const newBucket = !bucket;
|
|
114
|
+
if (!bucket) {
|
|
115
|
+
bucket = { store: createCliStore(options), options, members: new Set() };
|
|
116
|
+
group.buckets.set(key, bucket);
|
|
117
|
+
}
|
|
118
|
+
bucket.members.add(member);
|
|
119
|
+
if (newBucket) notifyGroup(group, member);
|
|
120
|
+
if (!group.preferred || ownsDefaults) {
|
|
121
|
+
const changed = group.preferred !== bucket;
|
|
122
|
+
group.preferred = bucket;
|
|
123
|
+
if (ownsDefaults) { group.configured = true; group.owner = member; }
|
|
124
|
+
if (changed) {
|
|
125
|
+
// Config may arrive after a sibling with only group was connected. Move
|
|
126
|
+
// inheriting siblings together; explicitly different credentials remain isolated.
|
|
127
|
+
notifyGroup(group, member);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const binding = { groupName, group, bucket };
|
|
131
|
+
if (previous && (previous.group !== group || previous.bucket !== bucket)) releaseGroup(previous, member, previous.group === group);
|
|
132
|
+
return binding;
|
|
56
133
|
}
|
|
57
134
|
|
|
58
|
-
const ATTRS = ["base-url", "token", "line", "namespace", "group", "selected-id", "job-id", "task-id", "title", "color", "theme", "context", "read-only", "config", "new-conversation", "tabs", "default-tab", "file-open", "path", "no-quote", "hidden"];
|
|
135
|
+
const ATTRS = ["base-url", "token", "line", "namespace", "group", "selected-id", "job-id", "task-id", "title", "color", "theme", "context", "read-only", "config", "new-conversation", "tabs", "available-tabs", "default-tab", "file-open", "path", "no-quote", "hidden", "watch-files", "passive-context"];
|
|
59
136
|
|
|
60
137
|
function jsonAttr(el: HTMLElement, name: string): object | undefined {
|
|
61
138
|
const raw = el.getAttribute(name);
|
|
@@ -77,6 +154,9 @@ abstract class CliElement extends Base {
|
|
|
77
154
|
static observedAttributes = ATTRS;
|
|
78
155
|
private root: Root | null = null;
|
|
79
156
|
private ownStore: CliStore | undefined;
|
|
157
|
+
private ownStoreKey: string | undefined;
|
|
158
|
+
private groupBinding: GroupBinding | undefined;
|
|
159
|
+
private groupMember: GroupMember = { el: this, refresh: () => { if (this.root) this.render(); } };
|
|
80
160
|
private configValue: object | undefined;
|
|
81
161
|
|
|
82
162
|
/** 界面配置(对象)。优先于 config 属性里的 JSON。 */
|
|
@@ -90,7 +170,12 @@ abstract class CliElement extends Base {
|
|
|
90
170
|
|
|
91
171
|
/** 这个元素用的仓库(同一个 group 的元素共用一个)。高级用法:store.on(...)、store.select(...)。 */
|
|
92
172
|
get store(): CliStore {
|
|
93
|
-
|
|
173
|
+
const binding = storeFor(this, this.groupMember, this.groupBinding);
|
|
174
|
+
if (binding) { this.groupBinding = binding; return binding.bucket.store; }
|
|
175
|
+
if (this.groupBinding) { releaseGroup(this.groupBinding, this.groupMember); this.groupBinding = undefined; }
|
|
176
|
+
const options = storeOptions(this), key = identity(options);
|
|
177
|
+
if (!this.ownStore || this.ownStoreKey !== key) { this.ownStore = createCliStore(options); this.ownStoreKey = key; }
|
|
178
|
+
return this.ownStore;
|
|
94
179
|
}
|
|
95
180
|
/** 元素自己写的对话(job-id + task-id);没写时 null(跟着这一组的选中走)。 */
|
|
96
181
|
private ownKey(): string | null {
|
|
@@ -129,7 +214,12 @@ abstract class CliElement extends Base {
|
|
|
129
214
|
}
|
|
130
215
|
disconnectedCallback() {
|
|
131
216
|
// 移动节点时会先断开再连上:推到下一轮再卸载。
|
|
132
|
-
queueMicrotask(() => { if (!this.isConnected) {
|
|
217
|
+
queueMicrotask(() => { if (!this.isConnected) {
|
|
218
|
+
this.root?.unmount(); this.root = null;
|
|
219
|
+
leaveGroup(this.groupMember); this.groupBinding = undefined;
|
|
220
|
+
this.ownStore = undefined; this.ownStoreKey = undefined;
|
|
221
|
+
this.appliedSelection = null; this.selectionStore = null;
|
|
222
|
+
} });
|
|
133
223
|
}
|
|
134
224
|
attributeChangedCallback() { this.syncHidden(); if (this.root) this.render(); }
|
|
135
225
|
private syncHidden() {
|
|
@@ -138,12 +228,15 @@ abstract class CliElement extends Base {
|
|
|
138
228
|
}
|
|
139
229
|
|
|
140
230
|
private appliedSelection: string | null = null;
|
|
231
|
+
private selectionStore: CliStore | null = null;
|
|
141
232
|
/** selected-id:这一组一起看哪个对话。属性变了才切(列表点击换了选中,不会被属性拉回去)。隐藏的元素也生效。 */
|
|
142
233
|
private applySelection() {
|
|
143
234
|
const selection = this.getAttribute("selected-id");
|
|
144
|
-
|
|
235
|
+
const store = this.store;
|
|
236
|
+
if (selection !== null && (selection !== this.appliedSelection || store !== this.selectionStore)) {
|
|
145
237
|
this.appliedSelection = selection;
|
|
146
|
-
this.
|
|
238
|
+
this.selectionStore = store;
|
|
239
|
+
store.select(selection || null);
|
|
147
240
|
}
|
|
148
241
|
}
|
|
149
242
|
protected common() {
|
|
@@ -153,7 +246,12 @@ abstract class CliElement extends Base {
|
|
|
153
246
|
title: this.getAttribute("title") ?? undefined,
|
|
154
247
|
color: this.getAttribute("color") ?? undefined,
|
|
155
248
|
theme: (this.getAttribute("theme") as "light" | "dark" | null) ?? undefined,
|
|
156
|
-
onEvent: (event: unknown) =>
|
|
249
|
+
onEvent: (event: unknown) => {
|
|
250
|
+
this.dispatchEvent(new CustomEvent("cli-event", { detail: cloneWorkspaceEvent(event as CliEvent), bubbles: true }));
|
|
251
|
+
const type=(event as {type?:string}).type;
|
|
252
|
+
const name=type==='files_changed'?'cli-files-change':type==='files_watch'?'cli-files-watch':type==='board_context'?'cli-board-context':null;
|
|
253
|
+
if(name)this.dispatchEvent(new CustomEvent(name,{detail:event,bubbles:true}));
|
|
254
|
+
},
|
|
157
255
|
onTurnEnd: (payload: unknown) => this.dispatchEvent(new CustomEvent("cli-turn-end", { detail: payload, bubbles: true })),
|
|
158
256
|
onConversationEnd: (payload: unknown) => this.dispatchEvent(new CustomEvent("cli-conversation-end", { detail: payload, bubbles: true })),
|
|
159
257
|
onCallback: (payload: unknown) => this.dispatchEvent(new CustomEvent("cli-callback", { detail: payload, bubbles: true })),
|
|
@@ -164,8 +262,14 @@ abstract class CliElement extends Base {
|
|
|
164
262
|
protected abstract element(): React.ReactNode;
|
|
165
263
|
// 隐藏的元素什么都不渲染:不轮询、不占连接;workspace() / appendContext() 照样能用。
|
|
166
264
|
private render() {
|
|
167
|
-
|
|
168
|
-
|
|
265
|
+
try {
|
|
266
|
+
this.applySelection();
|
|
267
|
+
this.root?.render(this.hasAttribute("hidden") ? null : this.element());
|
|
268
|
+
} catch (error) {
|
|
269
|
+
// Configuration errors must unmount the previous connection, never leave
|
|
270
|
+
// another account's old interface interactive under new attributes.
|
|
271
|
+
this.root?.render(this.hasAttribute("hidden") ? null : <div className="cli-ui cli-error" role="alert">{error instanceof Error ? error.message : "组件连接配置无效"}</div>);
|
|
272
|
+
}
|
|
169
273
|
}
|
|
170
274
|
}
|
|
171
275
|
|
|
@@ -183,7 +287,7 @@ class CliChatElement extends CliElement {
|
|
|
183
287
|
const jobId = this.getAttribute("job-id");
|
|
184
288
|
const taskId = this.getAttribute("task-id");
|
|
185
289
|
const fileOpen = this.getAttribute("file-open") as "auto" | "board" | "modal" | "link" | null;
|
|
186
|
-
return <CliChat {...this.common()} {...(jobId !== null ? { jobId: jobId || null } : {})} {...(taskId ? { taskId } : {})} readOnly={this.hasAttribute("read-only")} {...(fileOpen ? { fileOpen } : {})} />;
|
|
290
|
+
return <CliChat {...this.common()} {...(jobId !== null ? { jobId: jobId || null } : {})} {...(taskId ? { taskId } : {})} passiveContext={this.getAttribute("passive-context")!=="false"} readOnly={this.hasAttribute("read-only")} {...(fileOpen ? { fileOpen } : {})} />;
|
|
187
291
|
}
|
|
188
292
|
}
|
|
189
293
|
|
|
@@ -191,12 +295,14 @@ class CliBoardElement extends CliElement {
|
|
|
191
295
|
protected element() {
|
|
192
296
|
const jobId = this.getAttribute("job-id");
|
|
193
297
|
const tabs = this.getAttribute("tabs")?.split(",").map((t) => t.trim()).filter(Boolean) as BoardTab[] | undefined;
|
|
298
|
+
const availableTabs = this.getAttribute("available-tabs")?.split(",").map(t=>t.trim()).filter(Boolean) as BoardTab[] | undefined;
|
|
194
299
|
const defaultTab = (this.getAttribute("default-tab") as BoardTab | null) ?? undefined;
|
|
195
300
|
// 结果事件只由 <cli-chat> 发(同上);看板只发 cli-event。
|
|
196
301
|
const { onTurnEnd: _a, onConversationEnd: _b, onCallback: _c, title: _t, color: _k, config: _g, ...common } = this.common();
|
|
197
302
|
const taskId = this.getAttribute("task-id");
|
|
198
|
-
return <CliBoard {...common} {...(jobId !== null ? { jobId: jobId || null } : {})} {...(taskId ? { taskId } : {})} tabs={tabs
|
|
303
|
+
return <CliBoard {...common} {...(jobId !== null ? { jobId: jobId || null } : {})} {...(taskId ? { taskId } : {})} tabs={this.hasAttribute("tabs") ? tabs ?? [] : undefined} availableTabs={availableTabs} defaultTab={defaultTab} readOnly={this.hasAttribute("read-only")}
|
|
199
304
|
// 同一个 group 里有对话:看板里「引用到对话」带进那个对话的下一条消息;no-quote 关掉。
|
|
305
|
+
watchFiles={this.getAttribute("watch-files")!=="false"}
|
|
200
306
|
quoteToChat={this.hasAttribute("group") && !this.hasAttribute("no-quote")} />;
|
|
201
307
|
}
|
|
202
308
|
}
|
package/src/embed.ts
CHANGED
|
@@ -14,14 +14,12 @@
|
|
|
14
14
|
import "./styles.css";
|
|
15
15
|
import { createElement } from "react";
|
|
16
16
|
import { createRoot } from "react-dom/client";
|
|
17
|
-
import { defineCliElements } from "./elements
|
|
18
|
-
import { CliChat, type CliChatProps } from "./
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { createCliStore } from "./core/store.js";
|
|
24
|
-
import { createCenterClient } from "./core/client.js";
|
|
17
|
+
import { defineCliElements } from "./elements";
|
|
18
|
+
import { CliChat, CliMsgList, CliBoard, type CliChatProps, type CliMsgListProps, type CliBoardProps } from "./components";
|
|
19
|
+
import { CliBrowserView, CliFilePreview, type CliBrowserViewProps, type CliFilePreviewProps } from "./board/blocks";
|
|
20
|
+
import { CliCenterProvider } from "./core/provider";
|
|
21
|
+
import { createCliStore } from "./core/store";
|
|
22
|
+
import { createCenterClient } from "./core/client";
|
|
25
23
|
|
|
26
24
|
export interface Mounted<P> {
|
|
27
25
|
/** 合并更新参数(只传要改的)。 */
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { CliMsgList, CliChat, CliBoard, type CliMsgListProps, type CliChatProps, type CliBoardProps } from "./components";
|
|
1
2
|
/**
|
|
2
3
|
* @spzxg/cli-ui —— 任务中心(cli-task-center)对话任务的前端积木,数据全部来自 center 服务。
|
|
3
4
|
*
|
|
@@ -19,27 +20,33 @@
|
|
|
19
20
|
* import "@spzxg/cli-ui/styles.css";
|
|
20
21
|
* 非 React 页面用 Web Component:import { defineCliElements } from "@spzxg/cli-ui/elements"。
|
|
21
22
|
*/
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
28
|
-
export {
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
31
|
-
export { attachSplit, type SplitPane, type SplitOptions, type SplitHandle } from "./layout/split.js";
|
|
32
|
-
export { CliCenterProvider, useCliStore, useCliState, type CliCenterProviderProps, type ResultHandlers } from "./core/provider.js";
|
|
23
|
+
export { type BoardTab, type BoardExtraTab } from "./board/CliBoard";
|
|
24
|
+
export { downloadFolder, zipFolder, listFolder, FOLDER_MAX_FILES, FOLDER_MAX_BYTES, type FolderResult, type FolderZip, type FolderProgress } from "./board/folderDownload";
|
|
25
|
+
export { createWorkspace, WORKSPACE_TEXT_MAX, WORKSPACE_LIST_MAX, WORKSPACE_FILE_MAX, type CliWorkspace, type BoardView } from "./board/workspace";
|
|
26
|
+
export { CliFilePreview, CliBrowserView, type CliFilePreviewProps, type CliBrowserViewProps } from "./board/blocks";
|
|
27
|
+
export { FileViewer, downloadFile, type FileViewerProps, type FileAction } from "./board/FileViewer";
|
|
28
|
+
export { BrowserPane } from "./board/BrowserPane";
|
|
29
|
+
export { CliSplit, type CliSplitProps } from "./layout/CliSplit";
|
|
30
|
+
export { attachSplit, type SplitPane, type SplitOptions, type SplitHandle } from "./layout/split";
|
|
31
|
+
export { CliCenterProvider, useCliStore, useCliState, type CliCenterProviderProps, type ResultHandlers } from "./core/provider";
|
|
33
32
|
|
|
34
33
|
export {
|
|
35
34
|
resolveChatConfig, resolveMsgListConfig, DEFAULT_CHAT_CONFIG, DEFAULT_MSGLIST_CONFIG,
|
|
36
35
|
type CliChatConfig, type CliMsgListConfig, type ResolvedChatConfig, type ResolvedMsgListConfig,
|
|
37
|
-
} from "./core/config
|
|
38
|
-
export { createCliStore, CONTEXT_ITEM_MAX, type CliStore, type CliStoreOptions, type CliState, type CliEvent, type JobEntry, type OpenTarget, type BoardRef, type ContextItem } from "./core/store
|
|
39
|
-
export { createCenterClient, CenterError, boardFileUrl, type CenterClient, type CenterClientOptions, type StreamHandlers, type BoardBackend, type ConversationBackend } from "./core/client
|
|
40
|
-
export { viewMessages, isRunning, runningTurn, convKey, parseConvKey, NEW_TASK, type ConvState } from "./core/conversation
|
|
36
|
+
} from "./core/config";
|
|
37
|
+
export { createCliStore, CONTEXT_ITEM_MAX, type CliStore, type CliStoreOptions, type CliState, type CliEvent, type JobEntry, type OpenTarget, type BoardRef, type ContextItem } from "./core/store";
|
|
38
|
+
export { createCenterClient, CenterError, boardFileUrl, type CenterClient, type CenterClientOptions, type StreamHandlers, type BoardBackend, type ConversationBackend } from "./core/client";
|
|
39
|
+
export { viewMessages, isRunning, runningTurn, convKey, parseConvKey, NEW_TASK, type ConvState } from "./core/conversation";
|
|
41
40
|
export {
|
|
42
41
|
composeMessage, splitMessage, serializeBlock, readFileText, isLongText, ACCEPTED_EXTENSIONS, MAX_MESSAGE_CHARS,
|
|
43
42
|
type TextBlock, type MessagePart,
|
|
44
|
-
} from "./core/blocks
|
|
45
|
-
export type * from "./core/types
|
|
43
|
+
} from "./core/blocks";
|
|
44
|
+
export type * from "./core/types";
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
export { RunControls, type RunControlProps, type RunCapabilities, type RunTask } from "./chat/RunControls";
|
|
48
|
+
|
|
49
|
+
export { formatBoardContext, appendBoardContext, BOARD_HISTORY_MAX } from "./board/context";
|
|
50
|
+
export { subscribeWorkspaceFiles, type WorkspaceWatchOptions } from "./board/fileWatch";
|
|
51
|
+
export type { WorkspaceHandlers } from "./core/provider";
|
|
52
|
+
export type { BoardFile } from "./board/workspace";
|
package/src/layout/CliSplit.tsx
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 三个组件在同一个 Provider 里照常联动(列表选中 → 对话和看板跟着切;对话里点文件 → 看板打开),分栏只管宽度。
|
|
13
13
|
*/
|
|
14
14
|
import { Children, useEffect, useRef, type CSSProperties, type ReactNode } from "react";
|
|
15
|
-
import { attachSplit, type SplitHandle, type SplitPane } from "./split
|
|
15
|
+
import { attachSplit, type SplitHandle, type SplitPane } from "./split";
|
|
16
16
|
|
|
17
17
|
export type CliSplitProps = {
|
|
18
18
|
children: ReactNode;
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
// 一张卡片是 center 里的一个对话任务(mode=conversation)。对话任务里开了几个对话时,卡片下面按对话展开,
|
|
3
3
|
// 点哪个对话选哪个(选中值是对话键 "job#task");卡片上的「+」在这个对话任务里新开一个对话(同一个工作区)。
|
|
4
4
|
import { useEffect, useMemo, useState, type CSSProperties } from "react";
|
|
5
|
-
import { CliErrorBoundary } from "../ui/ErrorBoundary
|
|
6
|
-
import type { CliEvent, JobEntry } from "../core/store
|
|
7
|
-
import type { CliStoreOptions, CliStore } from "../core/store
|
|
8
|
-
import { useCliState, useCliStore, useCliStoreContext, useComponentEvents, withOwnProvider, type ResultHandlers } from "../core/provider
|
|
9
|
-
import { convKey, NEW_TASK, parseConvKey, TERMINAL_JOB } from "../core/conversation
|
|
10
|
-
import type { ConversationInfo } from "../core/types
|
|
11
|
-
import { Icon } from "../ui/icons
|
|
12
|
-
import { agentLabel, relTime, toMs } from "../ui/format
|
|
13
|
-
import { AgentBadge, ErrNote, Skel, StatusDot, type DotCls } from "../ui/shared
|
|
14
|
-
import { resolveMsgListConfig, type CliMsgListConfig, type ResolvedMsgListConfig } from "../core/config
|
|
5
|
+
import { CliErrorBoundary } from "../ui/ErrorBoundary";
|
|
6
|
+
import type { CliEvent, JobEntry } from "../core/store";
|
|
7
|
+
import type { CliStoreOptions, CliStore } from "../core/store";
|
|
8
|
+
import { useCliState, useCliStore, useCliStoreContext, useComponentEvents, withOwnProvider, type ResultHandlers } from "../core/provider";
|
|
9
|
+
import { convKey, NEW_TASK, parseConvKey, TERMINAL_JOB } from "../core/conversation";
|
|
10
|
+
import type { ConversationInfo } from "../core/types";
|
|
11
|
+
import { Icon } from "../ui/icons";
|
|
12
|
+
import { agentLabel, relTime, toMs } from "../ui/format";
|
|
13
|
+
import { AgentBadge, ErrNote, Skel, StatusDot, type DotCls } from "../ui/shared";
|
|
14
|
+
import { resolveMsgListConfig, type CliMsgListConfig, type ResolvedMsgListConfig } from "../core/config";
|
|
15
15
|
|
|
16
16
|
/** onTurnEnd / onConversationEnd:浏览器里从实时流算出的结果通知,不依赖 center 的 callback.url。 */
|
|
17
17
|
export interface CliMsgListProps extends CliStoreOptions, ResultHandlers {
|