Implement host-mode multi-workspace concurrency while preserving one active task per workspace. Host terminals are scoped by workspace id, host workspace switching no longer globally refreshes or blocks on tasks, and task APIs now expose workspace/conversation metadata for frontend coordination. Update conversation navigation so creating/loading conversations during an active workspace task is view-only and does not mutate backend terminal context. This prevents running task output from being pushed into the currently viewed conversation after switching or creating a new conversation. Add running task awareness to the frontend sidebar, composer, and polling flow. Running tasks can be shown across workspaces, current-workspace active conversations render inline loaders, completed unviewed tasks persist as check indicators across refresh, and completed tasks clear once viewed. Restore running conversations correctly after switching workspace/conversation or refreshing during an active task. REST polling is rebound only for the matching conversation/task, stale poll responses are ignored, empty assistant placeholders are restored before first content, and completion clears loader/stop states without requiring page refresh. Adjust host workspace UX so switching workspaces loads the active conversation if one is running, otherwise opens the new-conversation state. Other conversations in a busy workspace are view-only with disabled input instead of blocked navigation. Validation: python3 -m py_compile server/auth.py server/context.py server/conversation.py server/status.py server/tasks.py; python3 -m unittest test.test_server_refactor_smoke; npm run build --silent 2>&1 | tail -n 12.
251 lines
8.0 KiB
TypeScript
251 lines
8.0 KiB
TypeScript
// @ts-nocheck
|
||
import { ICONS, TOOL_CATEGORY_ICON_MAP } from '../utils/icons';
|
||
|
||
export function dataState() {
|
||
return {
|
||
// 路由相关
|
||
initialRouteResolved: false,
|
||
dropToolEvents: false,
|
||
|
||
// 轮询模式标志(禁用 WebSocket 事件处理)
|
||
usePollingMode: true,
|
||
// 后台子智能体等待状态
|
||
waitingForSubAgent: false,
|
||
// 后台 run_command 等待状态(用于文案区分)
|
||
waitingForBackgroundCommand: false,
|
||
// 是否在对话区展示 system 消息
|
||
hideSystemMessages: true,
|
||
|
||
// 工具状态跟踪
|
||
preparingTools: new Map(),
|
||
activeTools: new Map(),
|
||
toolActionIndex: new Map(),
|
||
toolStacks: new Map(),
|
||
// 当前任务是否仍在进行中(用于保持输入区的"停止"状态)
|
||
taskInProgress: false,
|
||
// 等待后台通知期间,用于发现并接管新建任务的轮询定时器
|
||
waitingTaskProbeTimer: null,
|
||
// 宿主机多工作区任务列表后台刷新定时器(用于当前未查看运行对话时同步完成态)
|
||
runningWorkspaceTasksRefreshTimer: null,
|
||
// 运行期消息堆积(提前发送 / 引导对话)
|
||
runtimeQueuedMessages: [],
|
||
runtimeGuidanceFallbackQueue: [],
|
||
runtimeQueueSuppressedMessageIds: new Set(),
|
||
runtimeGuidanceSuppressedTextCounts: {},
|
||
runtimeQueueLimit: 5,
|
||
runtimeQueueAutoSendInProgress: false,
|
||
runtimeQueueSyncLockKey: '',
|
||
runtimeQueueSyncLockUntil: 0,
|
||
// 输入区动态保留高度(用于同步扩大消息区可滚动范围)
|
||
composerReservedHeight: 80,
|
||
// 记录上一次成功加载历史的对话ID,防止初始化阶段重复加载导致动画播放两次
|
||
lastHistoryLoadedConversationId: null,
|
||
|
||
// ==========================================
|
||
// 对话管理相关状态
|
||
// ==========================================
|
||
|
||
// 搜索功能
|
||
// ==========================================
|
||
searchRequestSeq: 0,
|
||
searchActiveQuery: '',
|
||
searchResultIdSet: new Set(),
|
||
searchPreviewCache: {},
|
||
|
||
// Token统计相关状态(修复版)
|
||
// ==========================================
|
||
|
||
// 对话压缩状态
|
||
compressing: false,
|
||
compressionInProgress: false,
|
||
compressionMode: '',
|
||
compressionStage: '',
|
||
compressionError: '',
|
||
compressionToastId: null,
|
||
skipConversationLoadedEvent: false,
|
||
skipConversationHistoryReload: false,
|
||
_scrollListenerReady: false,
|
||
historyLoading: false,
|
||
historyLoadingFor: null,
|
||
historyLoadSeq: 0,
|
||
blankHeroActive: false,
|
||
blankHeroExiting: false,
|
||
blankWelcomeText: '',
|
||
lastBlankConversationId: null,
|
||
// 对话标题打字效果
|
||
titleTypingText: '',
|
||
titleTypingTarget: '',
|
||
titleTypingTimer: null,
|
||
titleReady: false,
|
||
suppressTitleTyping: false,
|
||
headerMenuOpen: false,
|
||
blankWelcomePool: [
|
||
'有什么可以帮忙的?',
|
||
'想了解些热点吗?',
|
||
'要我帮你完成作业吗?',
|
||
'整点代码?',
|
||
'随便聊点什么?',
|
||
'想让我帮你整理一下思路吗?',
|
||
'要不要我帮你写个小工具?',
|
||
'发我一句话,我来接着做。',
|
||
'我来给你画幅画?',
|
||
'想看小猫吗?',
|
||
'有什么难题丢过来吧。',
|
||
'想不想看个有意思的?',
|
||
'来,说说你的需求~'
|
||
],
|
||
mobileViewportQuery: null,
|
||
modeMenuOpen: false,
|
||
modelMenuOpen: false,
|
||
permissionMenuOpen: false,
|
||
currentPermissionMode: 'unrestricted',
|
||
pendingPermissionMode: '',
|
||
executionModeEnabled: false,
|
||
currentExecutionMode: 'sandbox',
|
||
pendingExecutionMode: '',
|
||
executionModeDirectUntil: null,
|
||
executionModeAutoFallbackToastId: null,
|
||
executionModeTtlSeconds: 600,
|
||
executionModeExpirySyncTimer: null,
|
||
pathAuthorizationDialogOpen: false,
|
||
pathAuthorizationMode: 'writable',
|
||
pathAuthorizationWritableDraft: '',
|
||
pathAuthorizationReadableDraft: '',
|
||
pathAuthorizationDraft: '',
|
||
pathAuthorizationSaving: false,
|
||
versioningHostMode: false,
|
||
hostWorkspaces: [],
|
||
currentHostWorkspaceId: '',
|
||
defaultHostWorkspaceId: '',
|
||
hostWorkspaceSwitching: false,
|
||
hostWorkspaceCreateDialogOpen: false,
|
||
hostWorkspaceCreatePath: '',
|
||
hostWorkspaceCreateLabel: '',
|
||
hostWorkspaceCreateSubmitting: false,
|
||
hostWorkspaceCreateError: '',
|
||
versioningEnabled: false,
|
||
newConversationVersioningEnabled: false,
|
||
versioningTrackingMode: 'workspace_and_conversation',
|
||
newConversationVersioningTrackingMode: 'workspace_and_conversation',
|
||
versioningMode: 'overwrite',
|
||
versioningMismatch: false,
|
||
versioningWorkspaceMatched: true,
|
||
versioningDialogOpen: false,
|
||
versioningLoading: false,
|
||
versioningCheckpoints: [],
|
||
versioningSelectedSeq: null,
|
||
versioningSelectedDetail: null,
|
||
versioningDetailLoading: false,
|
||
versioningRestoring: false,
|
||
versioningRestoreMode: 'overwrite',
|
||
permissionModeOptions: [
|
||
{
|
||
value: 'readonly',
|
||
label: '只读',
|
||
description: '仅允许读取/搜索类工具,禁止修改工作区'
|
||
},
|
||
{
|
||
value: 'approval',
|
||
label: '批准',
|
||
description: '对工作区文件进行修改的工具需用户批准后才会执行'
|
||
},
|
||
{
|
||
value: 'auto_approval',
|
||
label: '自动审核',
|
||
description: '工作区内写入直通,高风险操作由后台审核智能体自动审批'
|
||
},
|
||
{
|
||
value: 'unrestricted',
|
||
label: '无限制',
|
||
description: '保持当前默认行为,不额外拦截'
|
||
}
|
||
],
|
||
executionModeOptions: [
|
||
{
|
||
value: 'sandbox',
|
||
label: '沙箱',
|
||
description: '所有指令会在系统沙箱中执行'
|
||
},
|
||
{
|
||
value: 'direct',
|
||
label: '完全访问权限',
|
||
description: '所有指令会在宿主机直接执行'
|
||
}
|
||
],
|
||
pendingToolApprovals: [],
|
||
decidingApprovalIds: [],
|
||
pendingUserQuestions: [],
|
||
userQuestionDialogVisible: false,
|
||
userQuestionMinimized: false,
|
||
userQuestionActiveIndex: 0,
|
||
answeringUserQuestionIds: [],
|
||
userQuestionOriginalTitle: '',
|
||
userQuestionTitleBlinkTimer: null,
|
||
userQuestionTitleBlinkRed: true,
|
||
autoApprovalFeedLines: [],
|
||
autoApprovalFinalMessage: '',
|
||
approvalAutoCloseTimer: null,
|
||
imageEntries: [],
|
||
imageLoading: false,
|
||
videoEntries: [],
|
||
videoLoading: false,
|
||
conversationHasImages: false,
|
||
conversationHasVideos: false,
|
||
conversationListRequestSeq: 0,
|
||
conversationListRefreshToken: 0,
|
||
connectionHeartbeatTimer: null,
|
||
connectionHeartbeatActive: false,
|
||
connectionHeartbeatFailCount: 0,
|
||
connectionHeartbeatSeq: 0,
|
||
connectionHeartbeatLastLatencyMs: 0,
|
||
connectionHeartbeatLastError: '',
|
||
connectionHeartbeatLastStatusCode: null,
|
||
connectionHeartbeatLastChangeAt: 0,
|
||
connectionHeartbeatInFlight: false,
|
||
connectionHeartbeatFailThreshold: 3,
|
||
connectionHeartbeatRequestTimeoutMs: 5000,
|
||
connectionHeartbeatIntervalMs: 8000,
|
||
connectionHeartbeatDisconnectedIntervalMs: 1000,
|
||
composerDraftSaveTimer: null,
|
||
composerDraftDirty: false,
|
||
composerDraftLastSyncedContent: '',
|
||
composerDraftFetchSeq: 0,
|
||
|
||
// 工具控制菜单
|
||
icons: ICONS,
|
||
toolCategoryIcons: TOOL_CATEGORY_ICON_MAP,
|
||
|
||
// 对话回顾
|
||
reviewDialogOpen: false,
|
||
reviewSelectedConversationId: null,
|
||
reviewSubmitting: false,
|
||
reviewPreviewLines: [],
|
||
reviewPreviewLoading: false,
|
||
reviewPreviewError: null,
|
||
reviewPreviewLimit: 20,
|
||
reviewSendToModel: true,
|
||
reviewGeneratedPath: null,
|
||
|
||
// 新手教程首次引导弹窗
|
||
tutorialPromptVisible: false,
|
||
tutorialPromptLoading: false,
|
||
tutorialPromptUsername: '',
|
||
|
||
// 拖拽上传状态
|
||
dragOverActive: false,
|
||
|
||
// 拖拽事件绑定函数(用于正确移除监听)
|
||
_boundDragEnter: null,
|
||
_boundDragOver: null,
|
||
_boundDragLeave: null,
|
||
_boundDrop: null,
|
||
_manualScrollSuppressUntil: 0,
|
||
_escapedByUserScroll: false,
|
||
_autoRelockCooldownUntil: 0,
|
||
|
||
// stick-to-bottom 状态(用于“回到底部”按钮显隐)
|
||
stickIsAtBottom: true,
|
||
stickIsNearBottom: true
|
||
};
|
||
}
|