refactor(frontend): 整理应用逻辑层与状态管理
This commit is contained in:
parent
1bda147a6f
commit
8be1c37b6a
@ -196,7 +196,7 @@ const appOptions = {
|
|||||||
personalizationOpenDrawer: 'openDrawer'
|
personalizationOpenDrawer: 'openDrawer'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(appOptions as any).components = appComponents;
|
(appOptions as any).components = appComponents;
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,6 @@ function normalizeShowImageSrc(src: string) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSafeImageSrc(src: string) {
|
|
||||||
return !!normalizeShowImageSrc(src);
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(input: string) {
|
function escapeHtml(input: string) {
|
||||||
return input
|
return input
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@ -34,20 +30,20 @@ function renderShowImages(root: ParentNode | null = document) {
|
|||||||
if (!root) return;
|
if (!root) return;
|
||||||
// 处理因自闭合解析导致的嵌套:把子 show_image 平铺到父后面
|
// 处理因自闭合解析导致的嵌套:把子 show_image 平铺到父后面
|
||||||
const nested = Array.from(root.querySelectorAll('show_image show_image')).reverse();
|
const nested = Array.from(root.querySelectorAll('show_image show_image')).reverse();
|
||||||
nested.forEach(child => {
|
nested.forEach((child) => {
|
||||||
const parent = child.parentElement;
|
const parent = child.parentElement;
|
||||||
if (parent && parent !== root) {
|
if (parent && parent !== root) {
|
||||||
parent.after(child);
|
parent.after(child);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const nodes = Array.from(root.querySelectorAll('show_image:not([data-rendered])')).reverse();
|
const nodes = Array.from(root.querySelectorAll('show_image:not([data-rendered])')).reverse();
|
||||||
nodes.forEach(node => {
|
nodes.forEach((node) => {
|
||||||
// 将 show_image 内误被包裹的内容移动到当前节点之后,保持原有顺序
|
// 将 show_image 内误被包裹的内容移动到当前节点之后,保持原有顺序
|
||||||
if (node.parentNode && node.firstChild) {
|
if (node.parentNode && node.firstChild) {
|
||||||
const parent = node.parentNode;
|
const parent = node.parentNode;
|
||||||
const ref = node.nextSibling; // 可能为 null,insertBefore 会当 append
|
const ref = node.nextSibling; // 可能为 null,insertBefore 会当 append
|
||||||
const children = Array.from(node.childNodes);
|
const children = Array.from(node.childNodes);
|
||||||
children.forEach(child => parent.insertBefore(child, ref));
|
children.forEach((child) => parent.insertBefore(child, ref));
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawSrc = node.getAttribute('src') || '';
|
const rawSrc = node.getAttribute('src') || '';
|
||||||
@ -112,7 +108,9 @@ function updateViewportHeightVar() {
|
|||||||
const vh = visualViewport.height;
|
const vh = visualViewport.height;
|
||||||
const bottomInset = Math.max(
|
const bottomInset = Math.max(
|
||||||
0,
|
0,
|
||||||
(window.innerHeight || docEl.clientHeight || vh) - visualViewport.height - visualViewport.offsetTop
|
(window.innerHeight || docEl.clientHeight || vh) -
|
||||||
|
visualViewport.height -
|
||||||
|
visualViewport.offsetTop
|
||||||
);
|
);
|
||||||
docEl.style.setProperty('--app-viewport', `${vh}px`);
|
docEl.style.setProperty('--app-viewport', `${vh}px`);
|
||||||
docEl.style.setProperty('--app-bottom-inset', `${bottomInset}px`);
|
docEl.style.setProperty('--app-bottom-inset', `${bottomInset}px`);
|
||||||
|
|||||||
@ -1,17 +1,31 @@
|
|||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
import ChatArea from '../components/chat/ChatArea.vue';
|
import ChatArea from '../components/chat/ChatArea.vue';
|
||||||
import ConversationSidebar from '../components/sidebar/ConversationSidebar.vue';
|
import ConversationSidebar from '../components/sidebar/ConversationSidebar.vue';
|
||||||
import LeftPanel from '../components/panels/LeftPanel.vue';
|
import LeftPanel from '../components/panels/LeftPanel.vue';
|
||||||
import TokenDrawer from '../components/token/TokenDrawer.vue';
|
import TokenDrawer from '../components/token/TokenDrawer.vue';
|
||||||
import PersonalizationDrawer from '../components/personalization/PersonalizationDrawer.vue';
|
|
||||||
import QuickMenu from '../components/input/QuickMenu.vue';
|
import QuickMenu from '../components/input/QuickMenu.vue';
|
||||||
import InputComposer from '../components/input/InputComposer.vue';
|
import InputComposer from '../components/input/InputComposer.vue';
|
||||||
import AppShell from '../components/shell/AppShell.vue';
|
import AppShell from '../components/shell/AppShell.vue';
|
||||||
import ImagePicker from '../components/overlay/ImagePicker.vue';
|
|
||||||
import ConversationReviewDialog from '../components/overlay/ConversationReviewDialog.vue';
|
const PersonalizationDrawer = defineAsyncComponent(
|
||||||
import SubAgentActivityDialog from '../components/overlay/SubAgentActivityDialog.vue';
|
() => import('../components/personalization/PersonalizationDrawer.vue')
|
||||||
import BackgroundCommandDialog from '../components/overlay/BackgroundCommandDialog.vue';
|
);
|
||||||
import TutorialOverlay from '../components/overlay/TutorialOverlay.vue';
|
const ImagePicker = defineAsyncComponent(() => import('../components/overlay/ImagePicker.vue'));
|
||||||
import NewUserTutorialPrompt from '../components/overlay/NewUserTutorialPrompt.vue';
|
const ConversationReviewDialog = defineAsyncComponent(
|
||||||
|
() => import('../components/overlay/ConversationReviewDialog.vue')
|
||||||
|
);
|
||||||
|
const SubAgentActivityDialog = defineAsyncComponent(
|
||||||
|
() => import('../components/overlay/SubAgentActivityDialog.vue')
|
||||||
|
);
|
||||||
|
const BackgroundCommandDialog = defineAsyncComponent(
|
||||||
|
() => import('../components/overlay/BackgroundCommandDialog.vue')
|
||||||
|
);
|
||||||
|
const TutorialOverlay = defineAsyncComponent(
|
||||||
|
() => import('../components/overlay/TutorialOverlay.vue')
|
||||||
|
);
|
||||||
|
const NewUserTutorialPrompt = defineAsyncComponent(
|
||||||
|
() => import('../components/overlay/NewUserTutorialPrompt.vue')
|
||||||
|
);
|
||||||
|
|
||||||
export const appComponents = {
|
export const appComponents = {
|
||||||
ChatArea,
|
ChatArea,
|
||||||
|
|||||||
@ -124,7 +124,7 @@ export const computed = {
|
|||||||
return options.map((opt) => ({
|
return options.map((opt) => ({
|
||||||
...opt,
|
...opt,
|
||||||
disabled: disabledSet.has(opt.key)
|
disabled: disabledSet.has(opt.key)
|
||||||
})).filter((opt) => true);
|
}));
|
||||||
},
|
},
|
||||||
titleRibbonVisible() {
|
titleRibbonVisible() {
|
||||||
return !this.isMobileViewport && this.chatDisplayMode === 'chat';
|
return !this.isMobileViewport && this.chatDisplayMode === 'chat';
|
||||||
@ -162,7 +162,14 @@ export const computed = {
|
|||||||
},
|
},
|
||||||
composerBusy() {
|
composerBusy() {
|
||||||
const monitorLock = this.monitorIsLocked && this.chatDisplayMode === 'monitor';
|
const monitorLock = this.monitorIsLocked && this.chatDisplayMode === 'monitor';
|
||||||
return this.streamingUi || this.taskInProgress || monitorLock || this.stopRequested || this.compressionInProgress || this.compressing;
|
return (
|
||||||
|
this.streamingUi ||
|
||||||
|
this.taskInProgress ||
|
||||||
|
monitorLock ||
|
||||||
|
this.stopRequested ||
|
||||||
|
this.compressionInProgress ||
|
||||||
|
this.compressing
|
||||||
|
);
|
||||||
},
|
},
|
||||||
composerHeroActive() {
|
composerHeroActive() {
|
||||||
return (this.blankHeroActive || this.blankHeroExiting) && !this.composerInteractionActive;
|
return (this.blankHeroActive || this.blankHeroExiting) && !this.composerInteractionActive;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { debugLog, traceLog } from './common';
|
import { debugLog, traceLog } from './common';
|
||||||
|
import { usePersonalizationStore } from '../../stores/personalization';
|
||||||
|
|
||||||
export const conversationMethods = {
|
export const conversationMethods = {
|
||||||
// 完整重置所有状态
|
// 完整重置所有状态
|
||||||
@ -33,7 +34,9 @@ export const conversationMethods = {
|
|||||||
this.toolResetTracking();
|
this.toolResetTracking();
|
||||||
|
|
||||||
// 新增:将所有未完成的工具标记为已完成,并清理awaitingFirstContent状态
|
// 新增:将所有未完成的工具标记为已完成,并清理awaitingFirstContent状态
|
||||||
const assistantMsgsBefore = this.messages.filter(m => m.role === 'assistant').map(m => ({
|
const assistantMsgsBefore = this.messages
|
||||||
|
.filter((m) => m.role === 'assistant')
|
||||||
|
.map((m) => ({
|
||||||
awaitingFirstContent: m.awaitingFirstContent,
|
awaitingFirstContent: m.awaitingFirstContent,
|
||||||
generatingLabel: m.generatingLabel
|
generatingLabel: m.generatingLabel
|
||||||
}));
|
}));
|
||||||
@ -50,9 +53,11 @@ export const conversationMethods = {
|
|||||||
|
|
||||||
// 清理工具状态
|
// 清理工具状态
|
||||||
if (msg.actions) {
|
if (msg.actions) {
|
||||||
msg.actions.forEach(action => {
|
msg.actions.forEach((action) => {
|
||||||
if (action.type === 'tool' &&
|
if (
|
||||||
(action.tool.status === 'preparing' || action.tool.status === 'running')) {
|
action.type === 'tool' &&
|
||||||
|
(action.tool.status === 'preparing' || action.tool.status === 'running')
|
||||||
|
) {
|
||||||
action.tool.status = 'completed';
|
action.tool.status = 'completed';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -60,7 +65,9 @@ export const conversationMethods = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const assistantMsgsAfter = this.messages.filter(m => m.role === 'assistant').map(m => ({
|
const assistantMsgsAfter = this.messages
|
||||||
|
.filter((m) => m.role === 'assistant')
|
||||||
|
.map((m) => ({
|
||||||
awaitingFirstContent: m.awaitingFirstContent,
|
awaitingFirstContent: m.awaitingFirstContent,
|
||||||
generatingLabel: m.generatingLabel
|
generatingLabel: m.generatingLabel
|
||||||
}));
|
}));
|
||||||
@ -106,7 +113,10 @@ export const conversationMethods = {
|
|||||||
this.logMessageState('resetAllStates:after-cleanup', { reason });
|
this.logMessageState('resetAllStates:after-cleanup', { reason });
|
||||||
},
|
},
|
||||||
|
|
||||||
scheduleResetAfterTask(reason = 'unspecified', options: { preserveMonitorWindows?: boolean } = {}) {
|
scheduleResetAfterTask(
|
||||||
|
reason = 'unspecified',
|
||||||
|
options: { preserveMonitorWindows?: boolean } = {}
|
||||||
|
) {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const maxWait = 4000;
|
const maxWait = 4000;
|
||||||
const interval = 200;
|
const interval = 200;
|
||||||
@ -131,7 +141,8 @@ export const conversationMethods = {
|
|||||||
async loadConversationsList() {
|
async loadConversationsList() {
|
||||||
const queryOffset = this.conversationsOffset;
|
const queryOffset = this.conversationsOffset;
|
||||||
const queryLimit = this.conversationsLimit;
|
const queryLimit = this.conversationsLimit;
|
||||||
const refreshToken = queryOffset === 0 ? ++this.conversationListRefreshToken : this.conversationListRefreshToken;
|
const refreshToken =
|
||||||
|
queryOffset === 0 ? ++this.conversationListRefreshToken : this.conversationListRefreshToken;
|
||||||
const requestSeq = ++this.conversationListRequestSeq;
|
const requestSeq = ++this.conversationListRequestSeq;
|
||||||
this.conversationsLoading = true;
|
this.conversationsLoading = true;
|
||||||
try {
|
try {
|
||||||
@ -197,7 +208,11 @@ export const conversationMethods = {
|
|||||||
async loadConversation(conversationId, options = {}) {
|
async loadConversation(conversationId, options = {}) {
|
||||||
const force = Boolean(options.force);
|
const force = Boolean(options.force);
|
||||||
debugLog('加载对话:', conversationId);
|
debugLog('加载对话:', conversationId);
|
||||||
traceLog('loadConversation:start', { conversationId, currentConversationId: this.currentConversationId, force });
|
traceLog('loadConversation:start', {
|
||||||
|
conversationId,
|
||||||
|
currentConversationId: this.currentConversationId,
|
||||||
|
force
|
||||||
|
});
|
||||||
this.logMessageState('loadConversation:start', { conversationId, force });
|
this.logMessageState('loadConversation:start', { conversationId, force });
|
||||||
this.suppressTitleTyping = true;
|
this.suppressTitleTyping = true;
|
||||||
this.titleReady = false;
|
this.titleReady = false;
|
||||||
@ -226,7 +241,9 @@ export const conversationMethods = {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (this.currentConversationId) {
|
if (this.currentConversationId) {
|
||||||
await fetch(`/api/conversations/${this.currentConversationId}/compression_cancel`, { method: 'POST' });
|
await fetch(`/api/conversations/${this.currentConversationId}/compression_cancel`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('取消压缩失败:', e);
|
console.warn('取消压缩失败:', e);
|
||||||
@ -243,7 +260,6 @@ export const conversationMethods = {
|
|||||||
|
|
||||||
// 应用个性化设置中的默认模型和思考模式
|
// 应用个性化设置中的默认模型和思考模式
|
||||||
try {
|
try {
|
||||||
const { usePersonalizationStore } = await import('../../stores/personalization');
|
|
||||||
const personalizationStore = usePersonalizationStore();
|
const personalizationStore = usePersonalizationStore();
|
||||||
|
|
||||||
if (personalizationStore.loaded) {
|
if (personalizationStore.loaded) {
|
||||||
@ -275,7 +291,8 @@ export const conversationMethods = {
|
|||||||
try {
|
try {
|
||||||
const { useTaskStore } = await import('../../stores/task');
|
const { useTaskStore } = await import('../../stores/task');
|
||||||
const taskStore = useTaskStore();
|
const taskStore = useTaskStore();
|
||||||
const hasActiveTask = taskStore.hasActiveTask || this.taskInProgress || this.waitingForSubAgent;
|
const hasActiveTask =
|
||||||
|
taskStore.hasActiveTask || this.taskInProgress || this.waitingForSubAgent;
|
||||||
if (hasActiveTask) {
|
if (hasActiveTask) {
|
||||||
const waitingLabel = this.waitingForBackgroundCommand ? '后台指令' : '后台子智能体';
|
const waitingLabel = this.waitingForBackgroundCommand ? '后台指令' : '后台子智能体';
|
||||||
const confirmed = await this.confirmAction({
|
const confirmed = await this.confirmAction({
|
||||||
@ -332,7 +349,11 @@ export const conversationMethods = {
|
|||||||
this.suppressTitleTyping = false;
|
this.suppressTitleTyping = false;
|
||||||
this.startTitleTyping(this.currentConversationTitle, { animate: false });
|
this.startTitleTyping(this.currentConversationTitle, { animate: false });
|
||||||
this.promoteConversationToTop(conversationId);
|
this.promoteConversationToTop(conversationId);
|
||||||
history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`);
|
history.pushState(
|
||||||
|
{ conversationId },
|
||||||
|
'',
|
||||||
|
`/${this.stripConversationPrefix(conversationId)}`
|
||||||
|
);
|
||||||
this.skipConversationLoadedEvent = true;
|
this.skipConversationLoadedEvent = true;
|
||||||
|
|
||||||
// 3. 重置UI状态
|
// 3. 重置UI状态
|
||||||
@ -348,7 +369,6 @@ export const conversationMethods = {
|
|||||||
conversationId,
|
conversationId,
|
||||||
messagesLen: Array.isArray(this.messages) ? this.messages.length : 'n/a'
|
messagesLen: Array.isArray(this.messages) ? this.messages.length : 'n/a'
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error('对话加载失败:', result.message);
|
console.error('对话加载失败:', result.message);
|
||||||
this.suppressTitleTyping = false;
|
this.suppressTitleTyping = false;
|
||||||
@ -361,7 +381,10 @@ export const conversationMethods = {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载对话异常:', error);
|
console.error('加载对话异常:', error);
|
||||||
traceLog('loadConversation:error', { conversationId, error: error?.message || String(error) });
|
traceLog('loadConversation:error', {
|
||||||
|
conversationId,
|
||||||
|
error: error?.message || String(error)
|
||||||
|
});
|
||||||
this.suppressTitleTyping = false;
|
this.suppressTitleTyping = false;
|
||||||
this.titleReady = true;
|
this.titleReady = true;
|
||||||
this.uiPushToast({
|
this.uiPushToast({
|
||||||
@ -376,7 +399,7 @@ export const conversationMethods = {
|
|||||||
if (!Array.isArray(this.conversations) || !conversationId) {
|
if (!Array.isArray(this.conversations) || !conversationId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const index = this.conversations.findIndex(conv => conv && conv.id === conversationId);
|
const index = this.conversations.findIndex((conv) => conv && conv.id === conversationId);
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
const [selected] = this.conversations.splice(index, 1);
|
const [selected] = this.conversations.splice(index, 1);
|
||||||
this.conversations.unshift(selected);
|
this.conversations.unshift(selected);
|
||||||
@ -397,7 +420,9 @@ export const conversationMethods = {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (this.currentConversationId) {
|
if (this.currentConversationId) {
|
||||||
await fetch(`/api/conversations/${this.currentConversationId}/compression_cancel`, { method: 'POST' });
|
await fetch(`/api/conversations/${this.currentConversationId}/compression_cancel`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('取消压缩失败:', e);
|
console.warn('取消压缩失败:', e);
|
||||||
@ -421,7 +446,6 @@ export const conversationMethods = {
|
|||||||
|
|
||||||
// 应用个性化设置中的默认模型和思考模式
|
// 应用个性化设置中的默认模型和思考模式
|
||||||
try {
|
try {
|
||||||
const { usePersonalizationStore } = await import('../../stores/personalization');
|
|
||||||
const personalizationStore = usePersonalizationStore();
|
const personalizationStore = usePersonalizationStore();
|
||||||
|
|
||||||
if (personalizationStore.loaded) {
|
if (personalizationStore.loaded) {
|
||||||
@ -528,7 +552,7 @@ export const conversationMethods = {
|
|||||||
};
|
};
|
||||||
this.conversations = [
|
this.conversations = [
|
||||||
placeholder,
|
placeholder,
|
||||||
...this.conversations.filter(conv => conv && conv.id !== newConversationId)
|
...this.conversations.filter((conv) => conv && conv.id !== newConversationId)
|
||||||
];
|
];
|
||||||
|
|
||||||
// 直接加载新对话,确保状态一致
|
// 直接加载新对话,确保状态一致
|
||||||
@ -613,7 +637,6 @@ export const conversationMethods = {
|
|||||||
// 刷新对话列表
|
// 刷新对话列表
|
||||||
this.conversationsOffset = 0;
|
this.conversationsOffset = 0;
|
||||||
await this.loadConversationsList();
|
await this.loadConversationsList();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error('删除对话失败:', result.message);
|
console.error('删除对话失败:', result.message);
|
||||||
this.uiPushToast({
|
this.uiPushToast({
|
||||||
|
|||||||
@ -61,7 +61,9 @@ export const historyMethods = {
|
|||||||
this.historyLoadingFor = targetConversationId;
|
this.historyLoadingFor = targetConversationId;
|
||||||
try {
|
try {
|
||||||
debugLog('开始获取历史对话内容...');
|
debugLog('开始获取历史对话内容...');
|
||||||
this.logMessageState('fetchAndDisplayHistory:start', { conversationId: this.currentConversationId });
|
this.logMessageState('fetchAndDisplayHistory:start', {
|
||||||
|
conversationId: this.currentConversationId
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用专门的API获取对话消息历史
|
// 使用专门的API获取对话消息历史
|
||||||
@ -86,7 +88,10 @@ export const historyMethods = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果在等待期间用户已切换到其他对话,则丢弃结果
|
// 如果在等待期间用户已切换到其他对话,则丢弃结果
|
||||||
if (loadSeq !== this.historyLoadSeq || this.currentConversationId !== targetConversationId) {
|
if (
|
||||||
|
loadSeq !== this.historyLoadSeq ||
|
||||||
|
this.currentConversationId !== targetConversationId
|
||||||
|
) {
|
||||||
debugLog('检测到对话已切换,丢弃过期的历史加载结果');
|
debugLog('检测到对话已切换,丢弃过期的历史加载结果');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -125,12 +130,13 @@ export const historyMethods = {
|
|||||||
this.messages = [];
|
this.messages = [];
|
||||||
this.logMessageState('fetchAndDisplayHistory:invalid-data-cleared');
|
this.logMessageState('fetchAndDisplayHistory:invalid-data-cleared');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取历史对话失败:', error);
|
console.error('获取历史对话失败:', error);
|
||||||
debugLog('尝试不显示错误弹窗,仅在控制台记录');
|
debugLog('尝试不显示错误弹窗,仅在控制台记录');
|
||||||
// 不显示alert,避免打断用户体验
|
// 不显示alert,避免打断用户体验
|
||||||
this.logMessageState('fetchAndDisplayHistory:error-clear', { error: error?.message || String(error) });
|
this.logMessageState('fetchAndDisplayHistory:error-clear', {
|
||||||
|
error: error?.message || String(error)
|
||||||
|
});
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
this.logMessageState('fetchAndDisplayHistory:error-cleared');
|
this.logMessageState('fetchAndDisplayHistory:error-cleared');
|
||||||
}
|
}
|
||||||
@ -191,7 +197,6 @@ export const historyMethods = {
|
|||||||
metadata: message.metadata || {}
|
metadata: message.metadata || {}
|
||||||
});
|
});
|
||||||
debugLog('添加用户消息:', message.content?.substring(0, 50) + '...');
|
debugLog('添加用户消息:', message.content?.substring(0, 50) + '...');
|
||||||
|
|
||||||
} else if (message.role === 'assistant') {
|
} else if (message.role === 'assistant') {
|
||||||
// AI消息 - 如果没有当前assistant消息,创建一个
|
// AI消息 - 如果没有当前assistant消息,创建一个
|
||||||
if (!currentAssistantMessage) {
|
if (!currentAssistantMessage) {
|
||||||
@ -242,9 +247,10 @@ export const historyMethods = {
|
|||||||
message.tool_calls.forEach((toolCall, tcIndex) => {
|
message.tool_calls.forEach((toolCall, tcIndex) => {
|
||||||
let arguments_obj = {};
|
let arguments_obj = {};
|
||||||
try {
|
try {
|
||||||
arguments_obj = typeof toolCall.function.arguments === 'string'
|
arguments_obj =
|
||||||
|
typeof toolCall.function.arguments === 'string'
|
||||||
? JSON.parse(toolCall.function.arguments || '{}')
|
? JSON.parse(toolCall.function.arguments || '{}')
|
||||||
: (toolCall.function.arguments || {});
|
: toolCall.function.arguments || {};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('解析工具参数失败:', e);
|
console.warn('解析工具参数失败:', e);
|
||||||
arguments_obj = {};
|
arguments_obj = {};
|
||||||
@ -276,7 +282,6 @@ export const historyMethods = {
|
|||||||
debugLog('添加工具调用:', toolCall.function.name);
|
debugLog('添加工具调用:', toolCall.function.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (message.role === 'tool') {
|
} else if (message.role === 'tool') {
|
||||||
// 工具结果 - 更新当前assistant消息中对应的工具
|
// 工具结果 - 更新当前assistant消息中对应的工具
|
||||||
if (currentAssistantMessage) {
|
if (currentAssistantMessage) {
|
||||||
@ -285,17 +290,15 @@ export const historyMethods = {
|
|||||||
|
|
||||||
// 优先按tool_call_id匹配
|
// 优先按tool_call_id匹配
|
||||||
if (message.tool_call_id) {
|
if (message.tool_call_id) {
|
||||||
toolAction = currentAssistantMessage.actions.find(action =>
|
toolAction = currentAssistantMessage.actions.find(
|
||||||
action.type === 'tool' &&
|
(action) => action.type === 'tool' && action.tool.id === message.tool_call_id
|
||||||
action.tool.id === message.tool_call_id
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果找不到,按name匹配最后一个同名工具
|
// 如果找不到,按name匹配最后一个同名工具
|
||||||
if (!toolAction && message.name) {
|
if (!toolAction && message.name) {
|
||||||
const sameNameTools = currentAssistantMessage.actions.filter(action =>
|
const sameNameTools = currentAssistantMessage.actions.filter(
|
||||||
action.type === 'tool' &&
|
(action) => action.type === 'tool' && action.tool.name === message.name
|
||||||
action.tool.name === message.name
|
|
||||||
);
|
);
|
||||||
toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个
|
toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个
|
||||||
}
|
}
|
||||||
@ -330,7 +333,6 @@ export const historyMethods = {
|
|||||||
console.warn('找不到对应的工具调用:', message.name, message.tool_call_id);
|
console.warn('找不到对应的工具调用:', message.name, message.tool_call_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 其他类型消息(如system)- 先结束当前assistant消息
|
// 其他类型消息(如system)- 先结束当前assistant消息
|
||||||
if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) {
|
if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) {
|
||||||
@ -400,7 +402,8 @@ export const historyMethods = {
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const blockCount = this.$el && this.$el.querySelectorAll
|
const blockCount =
|
||||||
|
this.$el && this.$el.querySelectorAll
|
||||||
? this.$el.querySelectorAll('.message-block').length
|
? this.$el.querySelectorAll('.message-block').length
|
||||||
: 'N/A';
|
: 'N/A';
|
||||||
debugLog('[Messages] DOM 渲染统计', {
|
debugLog('[Messages] DOM 渲染统计', {
|
||||||
|
|||||||
@ -351,8 +351,9 @@ export const messageMethods = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 等待后端确认
|
// 等待后端确认
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
const shouldKeepBusy = Boolean(taskStore.currentTaskId) &&
|
const shouldKeepBusy =
|
||||||
|
Boolean(taskStore.currentTaskId) &&
|
||||||
['running', 'pending', 'cancel_requested'].includes(String(taskStore.taskStatus));
|
['running', 'pending', 'cancel_requested'].includes(String(taskStore.taskStatus));
|
||||||
|
|
||||||
// 清理前端状态
|
// 清理前端状态
|
||||||
@ -390,7 +391,8 @@ export const messageMethods = {
|
|||||||
console.error('[Message] 取消任务失败:', error);
|
console.error('[Message] 取消任务失败:', error);
|
||||||
const { useTaskStore } = await import('../../stores/task');
|
const { useTaskStore } = await import('../../stores/task');
|
||||||
const taskStore = useTaskStore();
|
const taskStore = useTaskStore();
|
||||||
const shouldKeepBusy = Boolean(taskStore.currentTaskId) &&
|
const shouldKeepBusy =
|
||||||
|
Boolean(taskStore.currentTaskId) &&
|
||||||
['running', 'pending', 'cancel_requested'].includes(String(taskStore.taskStatus));
|
['running', 'pending', 'cancel_requested'].includes(String(taskStore.taskStatus));
|
||||||
|
|
||||||
// 即使失败也清理状态
|
// 即使失败也清理状态
|
||||||
|
|||||||
@ -28,10 +28,10 @@ export const resourceMethods = {
|
|||||||
if (this.containerStatus.mode !== 'docker') {
|
if (this.containerStatus.mode !== 'docker') {
|
||||||
return 'status-pill--host';
|
return 'status-pill--host';
|
||||||
}
|
}
|
||||||
const rawStatus = (
|
const rawStatus =
|
||||||
this.containerStatus.state &&
|
(this.containerStatus.state &&
|
||||||
(this.containerStatus.state.status || this.containerStatus.state.Status)
|
(this.containerStatus.state.status || this.containerStatus.state.Status)) ||
|
||||||
) || '';
|
'';
|
||||||
const status = String(rawStatus).toLowerCase();
|
const status = String(rawStatus).toLowerCase();
|
||||||
if (status.includes('running')) {
|
if (status.includes('running')) {
|
||||||
return 'status-pill--running';
|
return 'status-pill--running';
|
||||||
@ -52,10 +52,10 @@ export const resourceMethods = {
|
|||||||
if (this.containerStatus.mode !== 'docker') {
|
if (this.containerStatus.mode !== 'docker') {
|
||||||
return '宿主机模式';
|
return '宿主机模式';
|
||||||
}
|
}
|
||||||
const rawStatus = (
|
const rawStatus =
|
||||||
this.containerStatus.state &&
|
(this.containerStatus.state &&
|
||||||
(this.containerStatus.state.status || this.containerStatus.state.Status)
|
(this.containerStatus.state.status || this.containerStatus.state.Status)) ||
|
||||||
) || '';
|
'';
|
||||||
const status = String(rawStatus).toLowerCase();
|
const status = String(rawStatus).toLowerCase();
|
||||||
if (status.includes('running')) {
|
if (status.includes('running')) {
|
||||||
return '运行中';
|
return '运行中';
|
||||||
|
|||||||
@ -66,7 +66,9 @@ export const searchMethods = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/conversations?limit=${batchSize}&offset=${this.searchOffset}`);
|
const response = await fetch(
|
||||||
|
`/api/conversations?limit=${batchSize}&offset=${this.searchOffset}`
|
||||||
|
);
|
||||||
const payload = await response.json();
|
const payload = await response.json();
|
||||||
|
|
||||||
if (requestSeq !== this.searchRequestSeq) {
|
if (requestSeq !== this.searchRequestSeq) {
|
||||||
@ -126,11 +128,16 @@ export const searchMethods = {
|
|||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (this.searchPreviewCache && Object.prototype.hasOwnProperty.call(this.searchPreviewCache, conversationId)) {
|
if (
|
||||||
|
this.searchPreviewCache &&
|
||||||
|
Object.prototype.hasOwnProperty.call(this.searchPreviewCache, conversationId)
|
||||||
|
) {
|
||||||
return this.searchPreviewCache[conversationId];
|
return this.searchPreviewCache[conversationId];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/conversations/${conversationId}/review_preview?limit=${SEARCH_PREVIEW_LIMIT}`);
|
const resp = await fetch(
|
||||||
|
`/api/conversations/${conversationId}/review_preview?limit=${SEARCH_PREVIEW_LIMIT}`
|
||||||
|
);
|
||||||
const payload = await resp.json();
|
const payload = await resp.json();
|
||||||
if (requestSeq !== this.searchRequestSeq) {
|
if (requestSeq !== this.searchRequestSeq) {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { debugLog } from './common';
|
import { debugLog } from './common';
|
||||||
|
|
||||||
const debugNotifyLog = (..._args: any[]) => {};
|
const debugNotifyLog = (...args: any[]) => {
|
||||||
|
void args;
|
||||||
|
};
|
||||||
const keyNotifyLog = (...args: any[]) => {
|
const keyNotifyLog = (...args: any[]) => {
|
||||||
console.log(...args);
|
console.log(...args);
|
||||||
};
|
};
|
||||||
@ -58,7 +60,8 @@ export const taskPollingMethods = {
|
|||||||
status: t?.status
|
status: t?.status
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
const runningTask = tasks.find((task: any) =>
|
const runningTask = tasks.find(
|
||||||
|
(task: any) =>
|
||||||
task?.status === 'running' && task?.conversation_id === this.currentConversationId
|
task?.status === 'running' && task?.conversation_id === this.currentConversationId
|
||||||
);
|
);
|
||||||
if (!runningTask?.task_id) {
|
if (!runningTask?.task_id) {
|
||||||
@ -171,7 +174,11 @@ export const taskPollingMethods = {
|
|||||||
'conversation_resolved',
|
'conversation_resolved',
|
||||||
'compression_finished'
|
'compression_finished'
|
||||||
]);
|
]);
|
||||||
if (!crossConversationAllowed.has(eventType) && eventData.conversation_id && this.currentConversationId) {
|
if (
|
||||||
|
!crossConversationAllowed.has(eventType) &&
|
||||||
|
eventData.conversation_id &&
|
||||||
|
this.currentConversationId
|
||||||
|
) {
|
||||||
if (eventData.conversation_id !== this.currentConversationId) {
|
if (eventData.conversation_id !== this.currentConversationId) {
|
||||||
console.log(`[DEBUG_AWAITING] 忽略不匹配的事件`, {
|
console.log(`[DEBUG_AWAITING] 忽略不匹配的事件`, {
|
||||||
eventType,
|
eventType,
|
||||||
@ -179,7 +186,9 @@ export const taskPollingMethods = {
|
|||||||
eventConversationId: eventData.conversation_id,
|
eventConversationId: eventData.conversation_id,
|
||||||
currentConversationId: this.currentConversationId
|
currentConversationId: this.currentConversationId
|
||||||
});
|
});
|
||||||
debugLog(`[TaskPolling] 忽略不匹配的事件 #${eventIdx}: ${eventType}, 事件对话=${eventData.conversation_id}, 当前对话=${this.currentConversationId}`);
|
debugLog(
|
||||||
|
`[TaskPolling] 忽略不匹配的事件 #${eventIdx}: ${eventType}, 事件对话=${eventData.conversation_id}, 当前对话=${this.currentConversationId}`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,7 +347,8 @@ export const taskPollingMethods = {
|
|||||||
// 1. 有assistant消息
|
// 1. 有assistant消息
|
||||||
// 2. 且该消息正在streaming或者没有任何内容(说明是刚创建的)
|
// 2. 且该消息正在streaming或者没有任何内容(说明是刚创建的)
|
||||||
// 3. 且不是历史加载完成的消息(历史消息的awaitingFirstContent应该是false)
|
// 3. 且不是历史加载完成的消息(历史消息的awaitingFirstContent应该是false)
|
||||||
const isRefreshRestore = hasAssistantMessage &&
|
const isRefreshRestore =
|
||||||
|
hasAssistantMessage &&
|
||||||
(this.streamingMessage || !lastMessage.actions || lastMessage.actions.length === 0) &&
|
(this.streamingMessage || !lastMessage.actions || lastMessage.actions.length === 0) &&
|
||||||
(lastMessage.awaitingFirstContent === true || this.taskInProgress);
|
(lastMessage.awaitingFirstContent === true || this.taskInProgress);
|
||||||
|
|
||||||
@ -468,7 +478,7 @@ export const taskPollingMethods = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleThinkingChunk(data: any, eventIdx: number) {
|
handleThinkingChunk(data: any) {
|
||||||
if (this.runMode === 'fast' || this.thinkingMode === false) {
|
if (this.runMode === 'fast' || this.thinkingMode === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -513,7 +523,7 @@ export const taskPollingMethods = {
|
|||||||
this.monitorEndModelOutput();
|
this.monitorEndModelOutput();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTextStart(data: any) {
|
handleTextStart() {
|
||||||
console.log('[TextStart] 开始处理');
|
console.log('[TextStart] 开始处理');
|
||||||
debugLog('[TaskPolling] 文本开始');
|
debugLog('[TaskPolling] 文本开始');
|
||||||
|
|
||||||
@ -1061,7 +1071,7 @@ export const taskPollingMethods = {
|
|||||||
|
|
||||||
// 更新对话列表中的标题
|
// 更新对话列表中的标题
|
||||||
if (data.title && Array.isArray(this.conversations)) {
|
if (data.title && Array.isArray(this.conversations)) {
|
||||||
const conv = this.conversations.find(c => c && c.id === data.conversation_id);
|
const conv = this.conversations.find((c) => c && c.id === data.conversation_id);
|
||||||
if (conv) {
|
if (conv) {
|
||||||
conv.title = data.title;
|
conv.title = data.title;
|
||||||
debugLog('[TaskPolling] 更新对话列表中的标题:', data.title);
|
debugLog('[TaskPolling] 更新对话列表中的标题:', data.title);
|
||||||
@ -1255,7 +1265,8 @@ export const taskPollingMethods = {
|
|||||||
const eventCount = allEvents.length;
|
const eventCount = allEvents.length;
|
||||||
const historyIncomplete = eventCount > historyActionsCount + 5; // 允许5个事件的误差
|
const historyIncomplete = eventCount > historyActionsCount + 5; // 允许5个事件的误差
|
||||||
|
|
||||||
const needsRebuild = !isAssistantMessage ||
|
const needsRebuild =
|
||||||
|
!isAssistantMessage ||
|
||||||
(isAssistantMessage && (!lastMessage.actions || lastMessage.actions.length === 0)) ||
|
(isAssistantMessage && (!lastMessage.actions || lastMessage.actions.length === 0)) ||
|
||||||
historyIncomplete;
|
historyIncomplete;
|
||||||
|
|
||||||
@ -1349,7 +1360,7 @@ export const taskPollingMethods = {
|
|||||||
// streaming: a.streaming
|
// streaming: a.streaming
|
||||||
// })));
|
// })));
|
||||||
|
|
||||||
const thinkingActions = lastMessage.actions.filter(a => a.type === 'thinking');
|
const thinkingActions = lastMessage.actions.filter((a) => a.type === 'thinking');
|
||||||
// console.log('[TaskPolling] 思考块数量:', thinkingActions.length);
|
// console.log('[TaskPolling] 思考块数量:', thinkingActions.length);
|
||||||
|
|
||||||
if (inThinking && thinkingActions.length > 0) {
|
if (inThinking && thinkingActions.length > 0) {
|
||||||
@ -1383,7 +1394,7 @@ export const taskPollingMethods = {
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
// 恢复文本块状态
|
// 恢复文本块状态
|
||||||
const textActions = lastMessage.actions.filter(a => a.type === 'text');
|
const textActions = lastMessage.actions.filter((a) => a.type === 'text');
|
||||||
// console.log('[TaskPolling] 文本块数量:', textActions.length);
|
// console.log('[TaskPolling] 文本块数量:', textActions.length);
|
||||||
|
|
||||||
if (inText && textActions.length > 0) {
|
if (inText && textActions.length > 0) {
|
||||||
@ -1394,7 +1405,7 @@ export const taskPollingMethods = {
|
|||||||
|
|
||||||
// 注册历史中的工具块到 toolActionIndex
|
// 注册历史中的工具块到 toolActionIndex
|
||||||
// 这样后续的 update_action 事件可以找到对应的块进行状态更新
|
// 这样后续的 update_action 事件可以找到对应的块进行状态更新
|
||||||
const toolActions = lastMessage.actions.filter(a => a.type === 'tool');
|
const toolActions = lastMessage.actions.filter((a) => a.type === 'tool');
|
||||||
// console.log('[TaskPolling] 工具块数量:', toolActions.length);
|
// console.log('[TaskPolling] 工具块数量:', toolActions.length);
|
||||||
|
|
||||||
for (const toolAction of toolActions) {
|
for (const toolAction of toolActions) {
|
||||||
@ -1506,15 +1517,23 @@ export const taskPollingMethods = {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const running = relevant.filter((task: any) => task?.run_in_background && !terminalStatuses.has(task?.status));
|
const running = relevant.filter(
|
||||||
|
(task: any) => task?.run_in_background && !terminalStatuses.has(task?.status)
|
||||||
|
);
|
||||||
const pendingNotice = relevant.filter((task: any) => task?.notice_pending);
|
const pendingNotice = relevant.filter((task: any) => task?.notice_pending);
|
||||||
|
|
||||||
if (running.length || pendingNotice.length) {
|
if (running.length || pendingNotice.length) {
|
||||||
debugLog('[TaskPolling] 恢复子智能体等待状态', {
|
debugLog('[TaskPolling] 恢复子智能体等待状态', {
|
||||||
running: running.length,
|
running: running.length,
|
||||||
pendingNotice: pendingNotice.length,
|
pendingNotice: pendingNotice.length,
|
||||||
runningTasks: running.map((task: any) => ({ task_id: task?.task_id, status: task?.status })),
|
runningTasks: running.map((task: any) => ({
|
||||||
pendingTasks: pendingNotice.map((task: any) => ({ task_id: task?.task_id, status: task?.status }))
|
task_id: task?.task_id,
|
||||||
|
status: task?.status
|
||||||
|
})),
|
||||||
|
pendingTasks: pendingNotice.map((task: any) => ({
|
||||||
|
task_id: task?.task_id,
|
||||||
|
status: task?.status
|
||||||
|
}))
|
||||||
});
|
});
|
||||||
this.waitingForSubAgent = true;
|
this.waitingForSubAgent = true;
|
||||||
this.waitingForBackgroundCommand = false;
|
this.waitingForBackgroundCommand = false;
|
||||||
@ -1527,5 +1546,5 @@ export const taskPollingMethods = {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[TaskPolling] 恢复子智能体等待状态失败:', error);
|
console.error('[TaskPolling] 恢复子智能体等待状态失败:', error);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -36,11 +36,11 @@ export const toolingMethods = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
cleanupStaleToolActions() {
|
cleanupStaleToolActions() {
|
||||||
this.messages.forEach(msg => {
|
this.messages.forEach((msg) => {
|
||||||
if (!msg.actions) {
|
if (!msg.actions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
msg.actions.forEach(action => {
|
msg.actions.forEach((action) => {
|
||||||
if (action.type !== 'tool' || !action.tool) {
|
if (action.type !== 'tool' || !action.tool) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -56,18 +56,16 @@ export const toolingMethods = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
clearPendingTools(reason = 'unspecified') {
|
clearPendingTools(reason = 'unspecified') {
|
||||||
this.messages.forEach(msg => {
|
this.messages.forEach((msg) => {
|
||||||
if (!msg.actions) {
|
if (!msg.actions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
msg.actions.forEach(action => {
|
msg.actions.forEach((action) => {
|
||||||
if (action.type !== 'tool' || !action.tool) {
|
if (action.type !== 'tool' || !action.tool) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const status =
|
const status =
|
||||||
typeof action.tool.status === 'string'
|
typeof action.tool.status === 'string' ? action.tool.status.toLowerCase() : '';
|
||||||
? action.tool.status.toLowerCase()
|
|
||||||
: '';
|
|
||||||
if (!status || ['preparing', 'running', 'pending', 'queued', 'stale'].includes(status)) {
|
if (!status || ['preparing', 'running', 'pending', 'queued', 'stale'].includes(status)) {
|
||||||
action.tool.status = 'cancelled';
|
action.tool.status = 'cancelled';
|
||||||
action.tool.message = action.tool.message || '已停止';
|
action.tool.message = action.tool.message || '已停止';
|
||||||
@ -93,18 +91,18 @@ export const toolingMethods = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
hasPendingToolActions() {
|
hasPendingToolActions() {
|
||||||
const mapHasEntries = map => map && typeof map.size === 'number' && map.size > 0;
|
const mapHasEntries = (map) => map && typeof map.size === 'number' && map.size > 0;
|
||||||
if (mapHasEntries(this.preparingTools) || mapHasEntries(this.activeTools)) {
|
if (mapHasEntries(this.preparingTools) || mapHasEntries(this.activeTools)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!Array.isArray(this.messages)) {
|
if (!Array.isArray(this.messages)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.messages.some(msg => {
|
return this.messages.some((msg) => {
|
||||||
if (!msg || msg.role !== 'assistant' || !Array.isArray(msg.actions)) {
|
if (!msg || msg.role !== 'assistant' || !Array.isArray(msg.actions)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return msg.actions.some(action => {
|
return msg.actions.some((action) => {
|
||||||
if (!action || action.type !== 'tool' || !action.tool) {
|
if (!action || action.type !== 'tool' || !action.tool) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -112,9 +110,7 @@ export const toolingMethods = {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const status =
|
const status =
|
||||||
typeof action.tool.status === 'string'
|
typeof action.tool.status === 'string' ? action.tool.status.toLowerCase() : '';
|
||||||
? action.tool.status.toLowerCase()
|
|
||||||
: '';
|
|
||||||
return !status || ['preparing', 'running', 'pending', 'queued'].includes(status);
|
return !status || ['preparing', 'running', 'pending', 'queued'].includes(status);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -149,7 +145,7 @@ export const toolingMethods = {
|
|||||||
debugLog('[ToolSettings] Snapshot applied', {
|
debugLog('[ToolSettings] Snapshot applied', {
|
||||||
received: categories.length,
|
received: categories.length,
|
||||||
normalized,
|
normalized,
|
||||||
anyEnabled: normalized.some(cat => cat.enabled),
|
anyEnabled: normalized.some((cat) => cat.enabled),
|
||||||
toolExamples: normalized.slice(0, 3)
|
toolExamples: normalized.slice(0, 3)
|
||||||
});
|
});
|
||||||
this.toolSetSettings(normalized);
|
this.toolSetSettings(normalized);
|
||||||
@ -269,9 +265,7 @@ export const toolingMethods = {
|
|||||||
getToolStatusText(tool: any) {
|
getToolStatusText(tool: any) {
|
||||||
const personalization = usePersonalizationStore();
|
const personalization = usePersonalizationStore();
|
||||||
const intentEnabled =
|
const intentEnabled =
|
||||||
personalization?.form?.tool_intent_enabled ??
|
personalization?.form?.tool_intent_enabled ?? personalization?.tool_intent_enabled ?? true;
|
||||||
personalization?.tool_intent_enabled ??
|
|
||||||
true;
|
|
||||||
return baseGetToolStatusText(tool, { intentEnabled });
|
return baseGetToolStatusText(tool, { intentEnabled });
|
||||||
},
|
},
|
||||||
getToolDescription,
|
getToolDescription,
|
||||||
|
|||||||
@ -214,7 +214,10 @@ export const uiMethods = {
|
|||||||
this.isPolicyBlocked('block_virtual_monitor', '虚拟显示器已被管理员禁用');
|
this.isPolicyBlocked('block_virtual_monitor', '虚拟显示器已被管理员禁用');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.chatDisplayMode === 'chat' && this.isPolicyBlocked('block_virtual_monitor', '虚拟显示器已被管理员禁用')) {
|
if (
|
||||||
|
this.chatDisplayMode === 'chat' &&
|
||||||
|
this.isPolicyBlocked('block_virtual_monitor', '虚拟显示器已被管理员禁用')
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const next = this.chatDisplayMode === 'chat' ? 'monitor' : 'chat';
|
const next = this.chatDisplayMode === 'chat' ? 'monitor' : 'chat';
|
||||||
@ -286,7 +289,11 @@ export const uiMethods = {
|
|||||||
try {
|
try {
|
||||||
const resp = await fetch('/api/tutorial-status', { credentials: 'same-origin' });
|
const resp = await fetch('/api/tutorial-status', { credentials: 'same-origin' });
|
||||||
const data = await resp.json().catch(() => ({}));
|
const data = await resp.json().catch(() => ({}));
|
||||||
console.info('[tutorial-prompt] status response:', { ok: resp.ok, status: resp.status, data });
|
console.info('[tutorial-prompt] status response:', {
|
||||||
|
ok: resp.ok,
|
||||||
|
status: resp.status,
|
||||||
|
data
|
||||||
|
});
|
||||||
if (!resp.ok || !data?.success) {
|
if (!resp.ok || !data?.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -485,7 +492,7 @@ export const uiMethods = {
|
|||||||
modelStore.setModel(data.model_key || key);
|
modelStore.setModel(data.model_key || key);
|
||||||
if (data.run_mode) {
|
if (data.run_mode) {
|
||||||
this.runMode = data.run_mode;
|
this.runMode = data.run_mode;
|
||||||
this.thinkingMode = data.thinking_mode ?? (data.run_mode !== 'fast');
|
this.thinkingMode = data.thinking_mode ?? data.run_mode !== 'fast';
|
||||||
} else {
|
} else {
|
||||||
// 前端兼容策略:根据模型特性自动调整运行模式
|
// 前端兼容策略:根据模型特性自动调整运行模式
|
||||||
const currentModel = modelStore.currentModel;
|
const currentModel = modelStore.currentModel;
|
||||||
@ -583,7 +590,8 @@ export const uiMethods = {
|
|||||||
throw new Error(payload.message || payload.error || '切换失败');
|
throw new Error(payload.message || payload.error || '切换失败');
|
||||||
}
|
}
|
||||||
const data = payload.data || {};
|
const data = payload.data || {};
|
||||||
this.thinkingMode = typeof data.thinking_mode === 'boolean' ? data.thinking_mode : mode !== 'fast';
|
this.thinkingMode =
|
||||||
|
typeof data.thinking_mode === 'boolean' ? data.thinking_mode : mode !== 'fast';
|
||||||
this.runMode = data.mode || mode;
|
this.runMode = data.mode || mode;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('切换运行模式失败:', error);
|
console.error('切换运行模式失败:', error);
|
||||||
@ -739,16 +747,16 @@ export const uiMethods = {
|
|||||||
|
|
||||||
this.reviewSubmitting = true;
|
this.reviewSubmitting = true;
|
||||||
try {
|
try {
|
||||||
const { path, char_count } = await this.generateConversationReview(this.reviewSelectedConversationId);
|
const { path, char_count } = await this.generateConversationReview(
|
||||||
|
this.reviewSelectedConversationId
|
||||||
|
);
|
||||||
if (!path) {
|
if (!path) {
|
||||||
throw new Error('未获取到生成的文件路径');
|
throw new Error('未获取到生成的文件路径');
|
||||||
}
|
}
|
||||||
const count = typeof char_count === 'number' ? char_count : 0;
|
const count = typeof char_count === 'number' ? char_count : 0;
|
||||||
this.reviewGeneratedPath = path;
|
this.reviewGeneratedPath = path;
|
||||||
const suggestion =
|
const suggestion =
|
||||||
count && count <= 10000
|
count && count <= 10000 ? '建议直接完整阅读。' : '建议使用 read 工具进行搜索或分段阅读。';
|
||||||
? '建议直接完整阅读。'
|
|
||||||
: '建议使用 read 工具进行搜索或分段阅读。';
|
|
||||||
if (this.reviewSendToModel) {
|
if (this.reviewSendToModel) {
|
||||||
const message = `帮我继续这个任务,对话文件在 ${path},文件长 ${count || '未知'} 字符,${suggestion} 请阅读文件了解后,不要直接继续工作,而是向我汇报你的理解,然后等我做出指示。`;
|
const message = `帮我继续这个任务,对话文件在 ${path},文件长 ${count || '未知'} 字符,${suggestion} 请阅读文件了解后,不要直接继续工作,而是向我汇报你的理解,然后等我做出指示。`;
|
||||||
const sent = await this.sendAutoUserMessage(message);
|
const sent = await this.sendAutoUserMessage(message);
|
||||||
@ -779,7 +787,9 @@ export const uiMethods = {
|
|||||||
this.reviewPreviewError = null;
|
this.reviewPreviewError = null;
|
||||||
this.reviewPreviewLines = [];
|
this.reviewPreviewLines = [];
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/conversations/${conversationId}/review_preview?limit=${this.reviewPreviewLimit}`);
|
const resp = await fetch(
|
||||||
|
`/api/conversations/${conversationId}/review_preview?limit=${this.reviewPreviewLimit}`
|
||||||
|
);
|
||||||
const payload = await resp.json().catch(() => ({}));
|
const payload = await resp.json().catch(() => ({}));
|
||||||
if (!resp.ok || !payload?.success) {
|
if (!resp.ok || !payload?.success) {
|
||||||
const msg = payload?.message || payload?.error || '获取预览失败';
|
const msg = payload?.message || payload?.error || '获取预览失败';
|
||||||
@ -814,7 +824,8 @@ export const uiMethods = {
|
|||||||
if (!this.quickMenuOpen) {
|
if (!this.quickMenuOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const shell = this.getComposerElement('stadiumShellOuter') || this.getComposerElement('compactInputShell');
|
const shell =
|
||||||
|
this.getComposerElement('stadiumShellOuter') || this.getComposerElement('compactInputShell');
|
||||||
if (shell && shell.contains(event.target)) {
|
if (shell && shell.contains(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -845,9 +856,7 @@ export const uiMethods = {
|
|||||||
|
|
||||||
isConversationBlank() {
|
isConversationBlank() {
|
||||||
if (!Array.isArray(this.messages) || !this.messages.length) return true;
|
if (!Array.isArray(this.messages) || !this.messages.length) return true;
|
||||||
return !this.messages.some(
|
return !this.messages.some((msg) => msg && (msg.role === 'user' || msg.role === 'assistant'));
|
||||||
(msg) => msg && (msg.role === 'user' || msg.role === 'assistant')
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
pickWelcomeText() {
|
pickWelcomeText() {
|
||||||
@ -863,9 +872,7 @@ export const uiMethods = {
|
|||||||
refreshBlankHeroState() {
|
refreshBlankHeroState() {
|
||||||
const isBlank = this.isConversationBlank();
|
const isBlank = this.isConversationBlank();
|
||||||
const currentConv = this.currentConversationId || 'temp';
|
const currentConv = this.currentConversationId || 'temp';
|
||||||
const needNewWelcome =
|
const needNewWelcome = !this.blankHeroActive || this.lastBlankConversationId !== currentConv;
|
||||||
!this.blankHeroActive ||
|
|
||||||
this.lastBlankConversationId !== currentConv;
|
|
||||||
|
|
||||||
if (isBlank) {
|
if (isBlank) {
|
||||||
if (needNewWelcome && !this.blankHeroExiting) {
|
if (needNewWelcome && !this.blankHeroExiting) {
|
||||||
@ -1129,7 +1136,9 @@ export const uiMethods = {
|
|||||||
}
|
}
|
||||||
const payload = await resp.json();
|
const payload = await resp.json();
|
||||||
const tasks = Array.isArray(payload?.data) ? payload.data : [];
|
const tasks = Array.isArray(payload?.data) ? payload.data : [];
|
||||||
const runningTask = tasks.find((task: any) => task?.status === 'running' && task?.conversation_id);
|
const runningTask = tasks.find(
|
||||||
|
(task: any) => task?.status === 'running' && task?.conversation_id
|
||||||
|
);
|
||||||
return runningTask?.conversation_id || null;
|
return runningTask?.conversation_id || null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('获取运行中任务失败:', error);
|
console.warn('获取运行中任务失败:', error);
|
||||||
@ -1167,7 +1176,11 @@ export const uiMethods = {
|
|||||||
this.titleReady = true;
|
this.titleReady = true;
|
||||||
this.suppressTitleTyping = false;
|
this.suppressTitleTyping = false;
|
||||||
this.startTitleTyping(this.currentConversationTitle, { animate: false });
|
this.startTitleTyping(this.currentConversationTitle, { animate: false });
|
||||||
history.replaceState({ conversationId: convId }, '', `/${this.stripConversationPrefix(convId)}`);
|
history.replaceState(
|
||||||
|
{ conversationId: convId },
|
||||||
|
'',
|
||||||
|
`/${this.stripConversationPrefix(convId)}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
history.replaceState({}, '', '/new');
|
history.replaceState({}, '', '/new');
|
||||||
this.currentConversationId = null;
|
this.currentConversationId = null;
|
||||||
@ -1346,7 +1359,7 @@ export const uiMethods = {
|
|||||||
// 加载个性化设置
|
// 加载个性化设置
|
||||||
const personalizationStore = usePersonalizationStore();
|
const personalizationStore = usePersonalizationStore();
|
||||||
if (!personalizationStore.loaded && !personalizationStore.loading) {
|
if (!personalizationStore.loaded && !personalizationStore.loading) {
|
||||||
await personalizationStore.fetchPersonalization().catch(err => {
|
await personalizationStore.fetchPersonalization().catch((err) => {
|
||||||
console.warn('加载个性化设置失败:', err);
|
console.warn('加载个性化设置失败:', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -266,7 +266,11 @@ export const uploadMethods = {
|
|||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const rawPath =
|
const rawPath =
|
||||||
item?.path ||
|
item?.path ||
|
||||||
[path, item?.name].filter(Boolean).join('/').replace(/\\/g, '/').replace(/\/{2,}/g, '/');
|
[path, item?.name]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/')
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.replace(/\/{2,}/g, '/');
|
||||||
const type = String(item?.type || '').toLowerCase();
|
const type = String(item?.type || '').toLowerCase();
|
||||||
if (type === 'directory' || type === 'folder') {
|
if (type === 'directory' || type === 'folder') {
|
||||||
queue.push(rawPath);
|
queue.push(rawPath);
|
||||||
@ -319,7 +323,11 @@ export const uploadMethods = {
|
|||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const rawPath =
|
const rawPath =
|
||||||
item?.path ||
|
item?.path ||
|
||||||
[path, item?.name].filter(Boolean).join('/').replace(/\\/g, '/').replace(/\/{2,}/g, '/');
|
[path, item?.name]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/')
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.replace(/\/{2,}/g, '/');
|
||||||
const type = String(item?.type || '').toLowerCase();
|
const type = String(item?.type || '').toLowerCase();
|
||||||
if (type === 'directory' || type === 'folder') {
|
if (type === 'directory' || type === 'folder') {
|
||||||
queue.push(rawPath);
|
queue.push(rawPath);
|
||||||
|
|||||||
@ -18,7 +18,8 @@ export const watchers = {
|
|||||||
this.titleTypingTarget = target;
|
this.titleTypingTarget = target;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const previous = (oldVal && oldVal.trim()) || (this.titleTypingText && this.titleTypingText.trim()) || '';
|
const previous =
|
||||||
|
(oldVal && oldVal.trim()) || (this.titleTypingText && this.titleTypingText.trim()) || '';
|
||||||
const placeholderPrev = !previous || previous === '新对话';
|
const placeholderPrev = !previous || previous === '新对话';
|
||||||
const placeholderTarget = !target || target === '新对话';
|
const placeholderTarget = !target || target === '新对话';
|
||||||
const animate = placeholderPrev && !placeholderTarget; // 仅从空/占位切换到真实标题时动画
|
const animate = placeholderPrev && !placeholderTarget; // 仅从空/占位切换到真实标题时动画
|
||||||
@ -27,7 +28,11 @@ export const watchers = {
|
|||||||
currentConversationId: {
|
currentConversationId: {
|
||||||
immediate: false,
|
immediate: false,
|
||||||
handler(newValue, oldValue) {
|
handler(newValue, oldValue) {
|
||||||
debugLog('currentConversationId 变化', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
|
debugLog('currentConversationId 变化', {
|
||||||
|
oldValue,
|
||||||
|
newValue,
|
||||||
|
skipConversationHistoryReload: this.skipConversationHistoryReload
|
||||||
|
});
|
||||||
traceLog('watch:currentConversationId', {
|
traceLog('watch:currentConversationId', {
|
||||||
oldValue,
|
oldValue,
|
||||||
newValue,
|
newValue,
|
||||||
@ -37,7 +42,11 @@ export const watchers = {
|
|||||||
historyLoadSeq: this.historyLoadSeq
|
historyLoadSeq: this.historyLoadSeq
|
||||||
});
|
});
|
||||||
this.refreshBlankHeroState();
|
this.refreshBlankHeroState();
|
||||||
this.logMessageState('watch:currentConversationId', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload });
|
this.logMessageState('watch:currentConversationId', {
|
||||||
|
oldValue,
|
||||||
|
newValue,
|
||||||
|
skipConversationHistoryReload: this.skipConversationHistoryReload
|
||||||
|
});
|
||||||
if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) {
|
if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -127,13 +127,6 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
}, step);
|
}, step);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasPendingStreamingText = () =>
|
|
||||||
STREAMING_ENABLED &&
|
|
||||||
( !!streamingState.buffer.length ||
|
|
||||||
!!streamingState.pendingCompleteContent ||
|
|
||||||
streamingState.timer !== null ||
|
|
||||||
streamingState.completionTimer !== null);
|
|
||||||
|
|
||||||
const resetPendingToolEvents = () => {
|
const resetPendingToolEvents = () => {
|
||||||
pendingToolEvents.length = 0;
|
pendingToolEvents.length = 0;
|
||||||
};
|
};
|
||||||
@ -193,10 +186,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
) {
|
) {
|
||||||
streamingState.activeMessageIndex = ctx.currentMessageIndex;
|
streamingState.activeMessageIndex = ctx.currentMessageIndex;
|
||||||
}
|
}
|
||||||
if (
|
if (typeof ctx?.currentMessageIndex !== 'number' || ctx.currentMessageIndex < 0) {
|
||||||
typeof ctx?.currentMessageIndex !== 'number' ||
|
|
||||||
ctx.currentMessageIndex < 0
|
|
||||||
) {
|
|
||||||
if (streamingState.activeMessageIndex !== null) {
|
if (streamingState.activeMessageIndex !== null) {
|
||||||
ctx.currentMessageIndex = streamingState.activeMessageIndex;
|
ctx.currentMessageIndex = streamingState.activeMessageIndex;
|
||||||
}
|
}
|
||||||
@ -264,9 +254,8 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
if (action) {
|
if (action) {
|
||||||
action.streaming = false;
|
action.streaming = false;
|
||||||
const fallback =
|
const fallback =
|
||||||
(typeof fullContent === 'string' && fullContent.length
|
(typeof fullContent === 'string' && fullContent.length ? fullContent : action.content) ||
|
||||||
? fullContent
|
'';
|
||||||
: action.content) || '';
|
|
||||||
action.content = fallback;
|
action.content = fallback;
|
||||||
}
|
}
|
||||||
msg.streamingText = '';
|
msg.streamingText = '';
|
||||||
@ -320,9 +309,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const hasPending =
|
const hasPending =
|
||||||
typeof ctx.hasPendingToolActions === 'function'
|
typeof ctx.hasPendingToolActions === 'function' ? ctx.hasPendingToolActions() : false;
|
||||||
? ctx.hasPendingToolActions()
|
|
||||||
: false;
|
|
||||||
if (!hasPending) {
|
if (!hasPending) {
|
||||||
ctx.streamingMessage = false;
|
ctx.streamingMessage = false;
|
||||||
ctx.stopRequested = false;
|
ctx.stopRequested = false;
|
||||||
@ -352,14 +339,21 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
const finalizeStreamingText = (options?: { force?: boolean; allowIncomplete?: boolean }) => {
|
const finalizeStreamingText = (options?: { force?: boolean; allowIncomplete?: boolean }) => {
|
||||||
const forceFlush = !!options?.force;
|
const forceFlush = !!options?.force;
|
||||||
const allowIncomplete = !!options?.allowIncomplete;
|
const allowIncomplete = !!options?.allowIncomplete;
|
||||||
logStreamingDebug('finalizeStreamingText:start', { forceFlush, allowIncomplete, snapshot: snapshotStreamingState() });
|
logStreamingDebug('finalizeStreamingText:start', {
|
||||||
|
forceFlush,
|
||||||
|
allowIncomplete,
|
||||||
|
snapshot: snapshotStreamingState()
|
||||||
|
});
|
||||||
if (!forceFlush) {
|
if (!forceFlush) {
|
||||||
if (streamingState.buffer.length) {
|
if (streamingState.buffer.length) {
|
||||||
logStreamingDebug('finalizeStreamingText:blocked-buffer', snapshotStreamingState());
|
logStreamingDebug('finalizeStreamingText:blocked-buffer', snapshotStreamingState());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!allowIncomplete && !streamingState.apiCompleted) {
|
if (!allowIncomplete && !streamingState.apiCompleted) {
|
||||||
logStreamingDebug('finalizeStreamingText:blocked-api-incomplete', snapshotStreamingState());
|
logStreamingDebug(
|
||||||
|
'finalizeStreamingText:blocked-api-incomplete',
|
||||||
|
snapshotStreamingState()
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,7 +364,9 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
streamingState.buffer.length = 0;
|
streamingState.buffer.length = 0;
|
||||||
if (remainder) {
|
if (remainder) {
|
||||||
applyTextChunk(remainder);
|
applyTextChunk(remainder);
|
||||||
logStreamingDebug('finalizeStreamingText:force-flush-buffer', { flushedChars: remainder.length });
|
logStreamingDebug('finalizeStreamingText:force-flush-buffer', {
|
||||||
|
flushedChars: remainder.length
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pendingText = streamingState.pendingCompleteContent || '';
|
const pendingText = streamingState.pendingCompleteContent || '';
|
||||||
@ -418,11 +414,17 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
|
|
||||||
const scheduleFinalizationAfterDrain = () => {
|
const scheduleFinalizationAfterDrain = () => {
|
||||||
if (streamingState.buffer.length) {
|
if (streamingState.buffer.length) {
|
||||||
logStreamingDebug('scheduleFinalizationAfterDrain:buffer-not-empty', snapshotStreamingState());
|
logStreamingDebug(
|
||||||
|
'scheduleFinalizationAfterDrain:buffer-not-empty',
|
||||||
|
snapshotStreamingState()
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (streamingState.completionTimer !== null) {
|
if (streamingState.completionTimer !== null) {
|
||||||
logStreamingDebug('scheduleFinalizationAfterDrain:already-scheduled', snapshotStreamingState());
|
logStreamingDebug(
|
||||||
|
'scheduleFinalizationAfterDrain:already-scheduled',
|
||||||
|
snapshotStreamingState()
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logStreamingDebug('scheduleFinalizationAfterDrain:scheduled', snapshotStreamingState());
|
logStreamingDebug('scheduleFinalizationAfterDrain:scheduled', snapshotStreamingState());
|
||||||
@ -549,7 +551,6 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
scheduleStreamingFlush();
|
scheduleStreamingFlush();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const scheduleHistoryReload = (delay = 0, options: { force?: boolean } = {}) => {
|
const scheduleHistoryReload = (delay = 0, options: { force?: boolean } = {}) => {
|
||||||
const { force = false } = options;
|
const { force = false } = options;
|
||||||
if (!ctx || typeof ctx.fetchAndDisplayHistory !== 'function') {
|
if (!ctx || typeof ctx.fetchAndDisplayHistory !== 'function') {
|
||||||
@ -688,7 +689,9 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
ctx.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0;
|
ctx.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0;
|
||||||
ctx.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0;
|
ctx.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0;
|
||||||
|
|
||||||
socketLog(`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`);
|
socketLog(
|
||||||
|
`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`
|
||||||
|
);
|
||||||
|
|
||||||
const hasContextTokens = typeof data.current_context_tokens === 'number';
|
const hasContextTokens = typeof data.current_context_tokens === 'number';
|
||||||
if (hasContextTokens && typeof ctx.resourceSetCurrentContextTokens === 'function') {
|
if (hasContextTokens && typeof ctx.resourceSetCurrentContextTokens === 'function') {
|
||||||
@ -705,9 +708,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
// 上下文过长提示
|
// 上下文过长提示
|
||||||
ctx.socket.on('context_warning', (data) => {
|
ctx.socket.on('context_warning', (data) => {
|
||||||
const title = data?.title || '上下文过长';
|
const title = data?.title || '上下文过长';
|
||||||
const message =
|
const message = data?.message || '当前对话上下文接近上限,建议使用压缩功能。';
|
||||||
data?.message ||
|
|
||||||
'当前对话上下文接近上限,建议使用压缩功能。';
|
|
||||||
if (typeof ctx.uiPushToast === 'function') {
|
if (typeof ctx.uiPushToast === 'function') {
|
||||||
ctx.uiPushToast({
|
ctx.uiPushToast({
|
||||||
title,
|
title,
|
||||||
@ -766,7 +767,7 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
|
|
||||||
// 更新对话列表中的标题
|
// 更新对话列表中的标题
|
||||||
if (data.title && Array.isArray(ctx.conversations)) {
|
if (data.title && Array.isArray(ctx.conversations)) {
|
||||||
const conv = ctx.conversations.find(c => c && c.id === data.conversation_id);
|
const conv = ctx.conversations.find((c) => c && c.id === data.conversation_id);
|
||||||
if (conv) {
|
if (conv) {
|
||||||
conv.title = data.title;
|
conv.title = data.title;
|
||||||
}
|
}
|
||||||
@ -926,7 +927,6 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
ctx.conditionalScrollToBottom();
|
ctx.conditionalScrollToBottom();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// AI消息开始(已废弃,改用 REST API 轮询)
|
// AI消息开始(已废弃,改用 REST API 轮询)
|
||||||
ctx.socket.on('ai_message_start', () => {
|
ctx.socket.on('ai_message_start', () => {
|
||||||
// 只处理子智能体模式
|
// 只处理子智能体模式
|
||||||
@ -1363,7 +1363,8 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
if (!message.actions) continue;
|
if (!message.actions) continue;
|
||||||
for (const action of message.actions) {
|
for (const action of message.actions) {
|
||||||
if (action.type !== 'tool') continue;
|
if (action.type !== 'tool') continue;
|
||||||
const matchByExecution = action.tool.executionId && action.tool.executionId === data.id;
|
const matchByExecution =
|
||||||
|
action.tool.executionId && action.tool.executionId === data.id;
|
||||||
const matchByToolId = action.tool.id === data.id;
|
const matchByToolId = action.tool.id === data.id;
|
||||||
const matchByPreparingId = action.id === data.preparing_id;
|
const matchByPreparingId = action.id === data.preparing_id;
|
||||||
if (matchByExecution || matchByToolId || matchByPreparingId) {
|
if (matchByExecution || matchByToolId || matchByPreparingId) {
|
||||||
@ -1380,7 +1381,11 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
if (data.result !== undefined) {
|
if (data.result !== undefined) {
|
||||||
targetAction.tool.result = data.result;
|
targetAction.tool.result = data.result;
|
||||||
}
|
}
|
||||||
if (targetAction.tool && targetAction.tool.name === 'trigger_easter_egg' && data.result !== undefined) {
|
if (
|
||||||
|
targetAction.tool &&
|
||||||
|
targetAction.tool.name === 'trigger_easter_egg' &&
|
||||||
|
data.result !== undefined
|
||||||
|
) {
|
||||||
const eggPromise = ctx.handleEasterEggPayload(data.result);
|
const eggPromise = ctx.handleEasterEggPayload(data.result);
|
||||||
if (eggPromise && typeof eggPromise.catch === 'function') {
|
if (eggPromise && typeof eggPromise.catch === 'function') {
|
||||||
eggPromise.catch((error) => {
|
eggPromise.catch((error) => {
|
||||||
@ -1405,13 +1410,19 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
if (data.arguments) {
|
if (data.arguments) {
|
||||||
targetAction.tool.arguments = data.arguments;
|
targetAction.tool.arguments = data.arguments;
|
||||||
targetAction.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
|
targetAction.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
|
||||||
targetAction.tool.argumentLabel = ctx.buildToolLabel(targetAction.tool.argumentSnapshot);
|
targetAction.tool.argumentLabel = ctx.buildToolLabel(
|
||||||
|
targetAction.tool.argumentSnapshot
|
||||||
|
);
|
||||||
if (data.arguments.intent) {
|
if (data.arguments.intent) {
|
||||||
startIntentTyping(targetAction, data.arguments.intent);
|
startIntentTyping(targetAction, data.arguments.intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.toolRegisterAction(targetAction, data.execution_id || data.id);
|
ctx.toolRegisterAction(targetAction, data.execution_id || data.id);
|
||||||
if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) {
|
if (
|
||||||
|
data.status &&
|
||||||
|
['completed', 'failed', 'error'].includes(data.status) &&
|
||||||
|
!data.awaiting_content
|
||||||
|
) {
|
||||||
ctx.toolUnregisterAction(targetAction);
|
ctx.toolUnregisterAction(targetAction);
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
ctx.preparingTools.delete(data.id);
|
ctx.preparingTools.delete(data.id);
|
||||||
@ -1563,7 +1574,9 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
|
|
||||||
// 显示等待提示
|
// 显示等待提示
|
||||||
if (typeof ctx.appendSystemAction === 'function') {
|
if (typeof ctx.appendSystemAction === 'function') {
|
||||||
const taskList = data.tasks.map((t: any) => `子智能体${t.agent_id} (${t.summary || '无描述'})`).join('、');
|
const taskList = data.tasks
|
||||||
|
.map((t: any) => `子智能体${t.agent_id} (${t.summary || '无描述'})`)
|
||||||
|
.join('、');
|
||||||
ctx.appendSystemAction(`⏳ 等待 ${data.count} 个后台子智能体完成:${taskList}`);
|
ctx.appendSystemAction(`⏳ 等待 ${data.count} 个后台子智能体完成:${taskList}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1687,7 +1700,6 @@ export async function initializeLegacySocket(ctx: any) {
|
|||||||
ctx.addSystemMessage(`命令失败: ${data.message}`);
|
ctx.addSystemMessage(`命令失败: ${data.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Socket初始化失败:', error);
|
console.error('Socket初始化失败:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,9 @@ function wrapCodeBlocks(html: string, isStreaming = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
return html.replace(/<pre><code([^>]*)>([\s\S]*?)<\/code><\/pre>/g, (match, attributes, content) => {
|
return html.replace(
|
||||||
|
/<pre><code([^>]*)>([\s\S]*?)<\/code><\/pre>/g,
|
||||||
|
(match, attributes, content) => {
|
||||||
const langMatch = attributes.match(/class="[^"]*language-(\w+)/);
|
const langMatch = attributes.match(/class="[^"]*language-(\w+)/);
|
||||||
const language = langMatch ? langMatch[1] : 'text';
|
const language = langMatch ? langMatch[1] : 'text';
|
||||||
const blockId = `code-${Date.now()}-${counter++}`;
|
const blockId = `code-${Date.now()}-${counter++}`;
|
||||||
@ -29,7 +31,8 @@ function wrapCodeBlocks(html: string, isStreaming = false) {
|
|||||||
</div>
|
</div>
|
||||||
<pre><code${attributes} data-code-id="${blockId}" data-original-code="${escapedContent}">${content}</code></pre>
|
<pre><code${attributes} data-code-id="${blockId}" data-original-code="${escapedContent}">${content}</code></pre>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderMarkdown(text: string, isStreaming = false) {
|
export function renderMarkdown(text: string, isStreaming = false) {
|
||||||
@ -71,8 +74,10 @@ export function renderMarkdown(text: string, isStreaming = false) {
|
|||||||
if (!isStreaming) {
|
if (!isStreaming) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (typeof Prism !== 'undefined') {
|
if (typeof Prism !== 'undefined') {
|
||||||
const codeBlocks = document.querySelectorAll('.code-block-wrapper pre code:not([data-highlighted])');
|
const codeBlocks = document.querySelectorAll(
|
||||||
codeBlocks.forEach(block => {
|
'.code-block-wrapper pre code:not([data-highlighted])'
|
||||||
|
);
|
||||||
|
codeBlocks.forEach((block) => {
|
||||||
try {
|
try {
|
||||||
Prism.highlightElement(block as HTMLElement);
|
Prism.highlightElement(block as HTMLElement);
|
||||||
block.setAttribute('data-highlighted', 'true');
|
block.setAttribute('data-highlighted', 'true');
|
||||||
@ -83,8 +88,10 @@ export function renderMarkdown(text: string, isStreaming = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof renderMathInElement !== 'undefined') {
|
if (typeof renderMathInElement !== 'undefined') {
|
||||||
const elements = document.querySelectorAll('.text-output .text-content:not(.streaming-text)');
|
const elements = document.querySelectorAll(
|
||||||
elements.forEach(element => {
|
'.text-output .text-content:not(.streaming-text)'
|
||||||
|
);
|
||||||
|
elements.forEach((element) => {
|
||||||
if (element.hasAttribute('data-math-rendered')) return;
|
if (element.hasAttribute('data-math-rendered')) return;
|
||||||
try {
|
try {
|
||||||
renderMathInElement(element as HTMLElement, {
|
renderMathInElement(element as HTMLElement, {
|
||||||
@ -120,7 +127,7 @@ export function renderLatexInRealtime() {
|
|||||||
|
|
||||||
latexRenderTimer = requestAnimationFrame(() => {
|
latexRenderTimer = requestAnimationFrame(() => {
|
||||||
const elements = document.querySelectorAll('.text-output .streaming-text');
|
const elements = document.querySelectorAll('.text-output .streaming-text');
|
||||||
elements.forEach(element => {
|
elements.forEach((element) => {
|
||||||
try {
|
try {
|
||||||
renderMathInElement(element as HTMLElement, {
|
renderMathInElement(element as HTMLElement, {
|
||||||
delimiters: [
|
delimiters: [
|
||||||
|
|||||||
@ -23,7 +23,11 @@ export function startResize(ctx: ResizeContext, panel: PanelKey, event: MouseEve
|
|||||||
|
|
||||||
if (panel === 'right' && ctx.rightCollapsed) {
|
if (panel === 'right' && ctx.rightCollapsed) {
|
||||||
ctx.rightCollapsed = false;
|
ctx.rightCollapsed = false;
|
||||||
if (typeof ctx.rightWidth === 'number' && typeof ctx.minPanelWidth === 'number' && ctx.rightWidth < ctx.minPanelWidth) {
|
if (
|
||||||
|
typeof ctx.rightWidth === 'number' &&
|
||||||
|
typeof ctx.minPanelWidth === 'number' &&
|
||||||
|
ctx.rightWidth < ctx.minPanelWidth
|
||||||
|
) {
|
||||||
ctx.rightWidth = ctx.minPanelWidth;
|
ctx.rightWidth = ctx.minPanelWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,8 +36,7 @@ export function scrollToBottom(ctx: ScrollContext, options?: ScrollOptions) {
|
|||||||
const attempts = [0, 16, 60, 150, 320, 520]; // 多次尝试覆盖布局抖动/异步伸缩
|
const attempts = [0, 16, 60, 150, 320, 520]; // 多次尝试覆盖布局抖动/异步伸缩
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const useSmooth =
|
const useSmooth =
|
||||||
options?.behavior === 'smooth' &&
|
options?.behavior === 'smooth' && typeof (messagesArea as HTMLElement).scrollTo === 'function';
|
||||||
typeof (messagesArea as HTMLElement).scrollTo === 'function';
|
|
||||||
|
|
||||||
const perform = (idx: number) => {
|
const perform = (idx: number) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
@ -97,7 +96,11 @@ export function toggleScrollLock(ctx: ScrollContext) {
|
|||||||
|
|
||||||
// 没有模型输出时:允许点击,但不切换锁定,仅单次滚动到底部
|
// 没有模型输出时:允许点击,但不切换锁定,仅单次滚动到底部
|
||||||
if (!active) {
|
if (!active) {
|
||||||
scrollToBottom(ctx, { ignoreUserScrolling: true, resetUserScrolling: true, behavior: 'smooth' });
|
scrollToBottom(ctx, {
|
||||||
|
ignoreUserScrolling: true,
|
||||||
|
resetUserScrolling: true,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
return ctx.autoScrollEnabled ?? false;
|
return ctx.autoScrollEnabled ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -82,7 +82,7 @@ export const useChatStore = defineStore('chat', {
|
|||||||
thinkingScrollLocks: new Map<string, boolean>()
|
thinkingScrollLocks: new Map<string, boolean>()
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isScrollLocked: state => state.autoScrollEnabled && !state.userScrolling
|
isScrollLocked: (state) => state.autoScrollEnabled && !state.userScrolling
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setStreamingMessage(active: boolean) {
|
setStreamingMessage(active: boolean) {
|
||||||
@ -315,7 +315,8 @@ export const useChatStore = defineStore('chat', {
|
|||||||
}
|
}
|
||||||
if (msg.activeThinkingId) {
|
if (msg.activeThinkingId) {
|
||||||
const found = msg.actions.find(
|
const found = msg.actions.find(
|
||||||
(action: any) => action && action.id === msg.activeThinkingId && action.type === 'thinking'
|
(action: any) =>
|
||||||
|
action && action.id === msg.activeThinkingId && action.type === 'thinking'
|
||||||
);
|
);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found;
|
return found;
|
||||||
|
|||||||
@ -47,7 +47,7 @@ function buildNodes(treeMap: Record<string, any> | undefined): FileNode[] {
|
|||||||
if (!treeMap) {
|
if (!treeMap) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const entries = Object.keys(treeMap).map(name => {
|
const entries = Object.keys(treeMap).map((name) => {
|
||||||
const node = treeMap[name] || {};
|
const node = treeMap[name] || {};
|
||||||
if (node.type === 'folder') {
|
if (node.type === 'folder') {
|
||||||
return {
|
return {
|
||||||
@ -129,7 +129,7 @@ export const useFileStore = defineStore('file', {
|
|||||||
const validFolderPaths = new Set<string>();
|
const validFolderPaths = new Set<string>();
|
||||||
|
|
||||||
const ensureExpansion = (list: FileNode[]) => {
|
const ensureExpansion = (list: FileNode[]) => {
|
||||||
list.forEach(item => {
|
list.forEach((item) => {
|
||||||
if (item.type === 'folder') {
|
if (item.type === 'folder') {
|
||||||
validFolderPaths.add(item.path);
|
validFolderPaths.add(item.path);
|
||||||
if (expanded[item.path] === undefined) {
|
if (expanded[item.path] === undefined) {
|
||||||
@ -141,7 +141,7 @@ export const useFileStore = defineStore('file', {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ensureExpansion(nodes);
|
ensureExpansion(nodes);
|
||||||
Object.keys(expanded).forEach(path => {
|
Object.keys(expanded).forEach((path) => {
|
||||||
if (!validFolderPaths.has(path)) {
|
if (!validFolderPaths.has(path)) {
|
||||||
delete expanded[path];
|
delete expanded[path];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export const useInputStore = defineStore('input', {
|
|||||||
this.selectedImages = next.slice(0, 9);
|
this.selectedImages = next.slice(0, 9);
|
||||||
},
|
},
|
||||||
removeSelectedImage(path: string) {
|
removeSelectedImage(path: string) {
|
||||||
this.selectedImages = this.selectedImages.filter(item => item !== path);
|
this.selectedImages = this.selectedImages.filter((item) => item !== path);
|
||||||
},
|
},
|
||||||
clearSelectedImages() {
|
clearSelectedImages() {
|
||||||
this.selectedImages = [];
|
this.selectedImages = [];
|
||||||
@ -106,7 +106,7 @@ export const useInputStore = defineStore('input', {
|
|||||||
this.selectedVideos = [path];
|
this.selectedVideos = [path];
|
||||||
},
|
},
|
||||||
removeSelectedVideo(path: string) {
|
removeSelectedVideo(path: string) {
|
||||||
this.selectedVideos = this.selectedVideos.filter(item => item !== path);
|
this.selectedVideos = this.selectedVideos.filter((item) => item !== path);
|
||||||
},
|
},
|
||||||
clearSelectedVideos() {
|
clearSelectedVideos() {
|
||||||
this.selectedVideos = [];
|
this.selectedVideos = [];
|
||||||
|
|||||||
@ -72,21 +72,21 @@ export const useModelStore = defineStore('model', {
|
|||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
currentModel(state): ModelOption {
|
currentModel(state): ModelOption {
|
||||||
return state.models.find(m => m.key === state.currentModelKey) || state.models[0];
|
return state.models.find((m) => m.key === state.currentModelKey) || state.models[0];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setModels(models: ModelOption[]) {
|
setModels(models: ModelOption[]) {
|
||||||
if (!Array.isArray(models) || !models.length) return;
|
if (!Array.isArray(models) || !models.length) return;
|
||||||
this.models = models;
|
this.models = models;
|
||||||
const exists = this.models.some(m => m.key === this.currentModelKey);
|
const exists = this.models.some((m) => m.key === this.currentModelKey);
|
||||||
if (!exists && this.models[0]) {
|
if (!exists && this.models[0]) {
|
||||||
this.currentModelKey = this.models[0].key;
|
this.currentModelKey = this.models[0].key;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setModel(key: ModelKey) {
|
setModel(key: ModelKey) {
|
||||||
if (this.currentModelKey === key) return;
|
if (this.currentModelKey === key) return;
|
||||||
const exists = this.models.some(m => m.key === key);
|
const exists = this.models.some((m) => m.key === key);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
this.currentModelKey = key;
|
this.currentModelKey = key;
|
||||||
}
|
}
|
||||||
@ -112,7 +112,8 @@ export const useModelStore = defineStore('model', {
|
|||||||
supportsImage,
|
supportsImage,
|
||||||
supportsVideo,
|
supportsVideo,
|
||||||
contextWindow: typeof item.context_window === 'number' ? item.context_window : null,
|
contextWindow: typeof item.context_window === 'number' ? item.context_window : null,
|
||||||
maxOutputTokens: typeof item.max_output_tokens === 'number' ? item.max_output_tokens : null,
|
maxOutputTokens:
|
||||||
|
typeof item.max_output_tokens === 'number' ? item.max_output_tokens : null,
|
||||||
fastOnly: !!item.fast_only,
|
fastOnly: !!item.fast_only,
|
||||||
supportsThinking: !!item.supports_thinking,
|
supportsThinking: !!item.supports_thinking,
|
||||||
deepOnly: !!item.deep_only
|
deepOnly: !!item.deep_only
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import type { MonitorBubbleOptions, MonitorDriver, MonitorSceneRuntime } from '@/components/chat/monitor/types';
|
import type {
|
||||||
|
MonitorBubbleOptions,
|
||||||
|
MonitorDriver,
|
||||||
|
MonitorSceneRuntime
|
||||||
|
} from '@/components/chat/monitor/types';
|
||||||
import { getSceneProgressLabel } from '@/components/chat/monitor/progressMap';
|
import { getSceneProgressLabel } from '@/components/chat/monitor/progressMap';
|
||||||
import { useConnectionStore } from '@/stores/connection';
|
import { useConnectionStore } from '@/stores/connection';
|
||||||
|
|
||||||
@ -116,7 +120,8 @@ const TOOL_SCENE_MAP: Record<string, string> = {
|
|||||||
terminal_run: 'runCommand'
|
terminal_run: 'runCommand'
|
||||||
};
|
};
|
||||||
|
|
||||||
const randomId = (prefix = 'evt') => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
const randomId = (prefix = 'evt') =>
|
||||||
|
`${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
||||||
|
|
||||||
export const useMonitorStore = defineStore('monitor', {
|
export const useMonitorStore = defineStore('monitor', {
|
||||||
state: (): MonitorState => ({
|
state: (): MonitorState => ({
|
||||||
@ -140,7 +145,7 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isLocked: state => state.playing || state.queue.length > 0
|
isLocked: (state) => state.playing || state.queue.length > 0
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
registerDriver(driver: MonitorDriver) {
|
registerDriver(driver: MonitorDriver) {
|
||||||
@ -179,7 +184,10 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
this.driver?.setDesktopRoots(this.lastTreeSnapshot, { immediate: true });
|
this.driver?.setDesktopRoots(this.lastTreeSnapshot, { immediate: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const folders = tree.filter(node => node && node.type === 'folder').map(node => node.name).filter(Boolean);
|
const folders = tree
|
||||||
|
.filter((node) => node && node.type === 'folder')
|
||||||
|
.map((node) => node.name)
|
||||||
|
.filter(Boolean);
|
||||||
this.lastTreeSnapshot = folders.length ? folders : [...DEFAULT_ROOTS];
|
this.lastTreeSnapshot = folders.length ? folders : [...DEFAULT_ROOTS];
|
||||||
this.driver?.setDesktopRoots(this.lastTreeSnapshot);
|
this.driver?.setDesktopRoots(this.lastTreeSnapshot);
|
||||||
},
|
},
|
||||||
@ -207,7 +215,7 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
this.awaitingTools = {};
|
this.awaitingTools = {};
|
||||||
}
|
}
|
||||||
if (!preservePendingResults) {
|
if (!preservePendingResults) {
|
||||||
Object.values(this.pendingResults).forEach(entry => {
|
Object.values(this.pendingResults).forEach((entry) => {
|
||||||
if (entry?.timeout) {
|
if (entry?.timeout) {
|
||||||
clearTimeout(entry.timeout);
|
clearTimeout(entry.timeout);
|
||||||
}
|
}
|
||||||
@ -222,7 +230,12 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
this.thinkingActive = false;
|
this.thinkingActive = false;
|
||||||
this.pendingProgressScene = null;
|
this.pendingProgressScene = null;
|
||||||
this.progressIndicator = { id: null, label: '', scene: null };
|
this.progressIndicator = { id: null, label: '', scene: null };
|
||||||
this.driver?.resetScene({ desktopRoots: this.lastTreeSnapshot, preserveBubble, preservePointer, preserveWindows });
|
this.driver?.resetScene({
|
||||||
|
desktopRoots: this.lastTreeSnapshot,
|
||||||
|
preserveBubble,
|
||||||
|
preservePointer,
|
||||||
|
preserveWindows
|
||||||
|
});
|
||||||
this.driver?.setManualInteractionEnabled(!this.isLocked);
|
this.driver?.setManualInteractionEnabled(!this.isLocked);
|
||||||
},
|
},
|
||||||
setProgressIndicator(payload: { id: string | null; label: string; scene: string }) {
|
setProgressIndicator(payload: { id: string | null; label: string; scene: string }) {
|
||||||
@ -268,7 +281,10 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
if (id && this.progressIndicator.id && id !== this.progressIndicator.id) {
|
if (id && this.progressIndicator.id && id !== this.progressIndicator.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
monitorProgressDebug('progress-indicator:clear', { id: this.progressIndicator.id, label: this.progressIndicator.label });
|
monitorProgressDebug('progress-indicator:clear', {
|
||||||
|
id: this.progressIndicator.id,
|
||||||
|
label: this.progressIndicator.label
|
||||||
|
});
|
||||||
this.progressIndicator = { id: null, label: '', scene: null };
|
this.progressIndicator = { id: null, label: '', scene: null };
|
||||||
},
|
},
|
||||||
setStatus(label: string) {
|
setStatus(label: string) {
|
||||||
@ -395,7 +411,11 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
monitorProgressDebug('enqueueToolEvent:preview-now', { tool: payload.name, script, id });
|
monitorProgressDebug('enqueueToolEvent:preview-now', { tool: payload.name, script, id });
|
||||||
this.driver.previewSceneProgress(script);
|
this.driver.previewSceneProgress(script);
|
||||||
} else {
|
} else {
|
||||||
monitorProgressDebug('enqueueToolEvent:pending-preview', { tool: payload.name, script, id });
|
monitorProgressDebug('enqueueToolEvent:pending-preview', {
|
||||||
|
tool: payload.name,
|
||||||
|
script,
|
||||||
|
id
|
||||||
|
});
|
||||||
this.pendingProgressScene = script;
|
this.pendingProgressScene = script;
|
||||||
}
|
}
|
||||||
if (this.bubbleActive) {
|
if (this.bubbleActive) {
|
||||||
@ -404,7 +424,11 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
};
|
};
|
||||||
if (this.bubbleActive && recentSpeechGap >= 0 && recentSpeechGap < MIN_SPEECH_VISIBLE_MS) {
|
if (this.bubbleActive && recentSpeechGap >= 0 && recentSpeechGap < MIN_SPEECH_VISIBLE_MS) {
|
||||||
const delay = MIN_SPEECH_VISIBLE_MS - recentSpeechGap;
|
const delay = MIN_SPEECH_VISIBLE_MS - recentSpeechGap;
|
||||||
monitorLifecycleLog('enqueue:delay-preview-for-speech', { delay, recentSpeechGap, tool: payload.name });
|
monitorLifecycleLog('enqueue:delay-preview-for-speech', {
|
||||||
|
delay,
|
||||||
|
recentSpeechGap,
|
||||||
|
tool: payload.name
|
||||||
|
});
|
||||||
setTimeout(doPreview, delay);
|
setTimeout(doPreview, delay);
|
||||||
} else {
|
} else {
|
||||||
doPreview();
|
doPreview();
|
||||||
@ -456,12 +480,12 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let resolver: (value: any) => void = () => {};
|
let resolver: (value: any) => void = () => {};
|
||||||
const promise = new Promise<any>(resolve => {
|
const promise = new Promise<any>((resolve) => {
|
||||||
resolver = resolve;
|
resolver = resolve;
|
||||||
});
|
});
|
||||||
const entry: PendingResultEntry = {
|
const entry: PendingResultEntry = {
|
||||||
promise,
|
promise,
|
||||||
resolve: value => {
|
resolve: (value) => {
|
||||||
if (!entry.settled) {
|
if (!entry.settled) {
|
||||||
entry.settled = true;
|
entry.settled = true;
|
||||||
if (entry.timeout) {
|
if (entry.timeout) {
|
||||||
@ -581,7 +605,11 @@ export const useMonitorStore = defineStore('monitor', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const waitKey =
|
const waitKey =
|
||||||
event.payload?.executionId || event.payload?.execution_id || event.id || event.payload?.id || null;
|
event.payload?.executionId ||
|
||||||
|
event.payload?.execution_id ||
|
||||||
|
event.id ||
|
||||||
|
event.payload?.id ||
|
||||||
|
null;
|
||||||
monitorLifecycleLog('runScript:start', {
|
monitorLifecycleLog('runScript:start', {
|
||||||
script: event.script,
|
script: event.script,
|
||||||
waitKey,
|
waitKey,
|
||||||
|
|||||||
@ -117,8 +117,10 @@ const loadExperimentState = (): ExperimentState => {
|
|||||||
|
|
||||||
// 兼容旧版 stackedBlocksEnabled
|
// 兼容旧版 stackedBlocksEnabled
|
||||||
let blockDisplayMode: BlockDisplayMode = defaultExperimentState().blockDisplayMode;
|
let blockDisplayMode: BlockDisplayMode = defaultExperimentState().blockDisplayMode;
|
||||||
if (typeof parsed?.blockDisplayMode === 'string' &&
|
if (
|
||||||
['traditional', 'stacked', 'minimal'].includes(parsed.blockDisplayMode)) {
|
typeof parsed?.blockDisplayMode === 'string' &&
|
||||||
|
['traditional', 'stacked', 'minimal'].includes(parsed.blockDisplayMode)
|
||||||
|
) {
|
||||||
blockDisplayMode = parsed.blockDisplayMode;
|
blockDisplayMode = parsed.blockDisplayMode;
|
||||||
} else if (typeof parsed?.stackedBlocksEnabled === 'boolean') {
|
} else if (typeof parsed?.stackedBlocksEnabled === 'boolean') {
|
||||||
// 兼容旧版:true -> stacked, false -> traditional
|
// 兼容旧版:true -> stacked, false -> traditional
|
||||||
@ -207,7 +209,9 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
applyPersonalizationData(data: any) {
|
applyPersonalizationData(data: any) {
|
||||||
// 若后端未返回默认模型(旧版本接口),保持当前已选模型而不是回退为 Kimi
|
// 若后端未返回默认模型(旧版本接口),保持当前已选模型而不是回退为 Kimi
|
||||||
const fallbackModel =
|
const fallbackModel =
|
||||||
(this.form && typeof this.form.default_model === 'string' ? this.form.default_model : null) || 'kimi-k2.5';
|
(this.form && typeof this.form.default_model === 'string'
|
||||||
|
? this.form.default_model
|
||||||
|
: null) || 'kimi-k2.5';
|
||||||
this.form = {
|
this.form = {
|
||||||
enabled: !!data.enabled,
|
enabled: !!data.enabled,
|
||||||
auto_generate_title: data.auto_generate_title !== false,
|
auto_generate_title: data.auto_generate_title !== false,
|
||||||
@ -215,8 +219,10 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
skill_hints_enabled: !!data.skill_hints_enabled,
|
skill_hints_enabled: !!data.skill_hints_enabled,
|
||||||
skill_strict_terminal_enabled: !!data.skill_strict_terminal_enabled,
|
skill_strict_terminal_enabled: !!data.skill_strict_terminal_enabled,
|
||||||
skill_strict_sub_agent_enabled: !!data.skill_strict_sub_agent_enabled,
|
skill_strict_sub_agent_enabled: !!data.skill_strict_sub_agent_enabled,
|
||||||
skill_strict_run_command_foreground_enabled: !!data.skill_strict_run_command_foreground_enabled,
|
skill_strict_run_command_foreground_enabled:
|
||||||
skill_strict_run_command_background_enabled: !!data.skill_strict_run_command_background_enabled,
|
!!data.skill_strict_run_command_foreground_enabled,
|
||||||
|
skill_strict_run_command_background_enabled:
|
||||||
|
!!data.skill_strict_run_command_background_enabled,
|
||||||
silent_tool_disable: !!data.silent_tool_disable,
|
silent_tool_disable: !!data.silent_tool_disable,
|
||||||
enhanced_tool_display: data.enhanced_tool_display !== false,
|
enhanced_tool_display: data.enhanced_tool_display !== false,
|
||||||
enabled_skills: Array.isArray(data.enabled_skills)
|
enabled_skills: Array.isArray(data.enabled_skills)
|
||||||
@ -228,21 +234,36 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
profession: data.profession || '',
|
profession: data.profession || '',
|
||||||
tone: data.tone || '',
|
tone: data.tone || '',
|
||||||
considerations: Array.isArray(data.considerations) ? [...data.considerations] : [],
|
considerations: Array.isArray(data.considerations) ? [...data.considerations] : [],
|
||||||
thinking_interval: typeof data.thinking_interval === 'number' ? data.thinking_interval : null,
|
thinking_interval:
|
||||||
disabled_tool_categories: Array.isArray(data.disabled_tool_categories) ? data.disabled_tool_categories.filter((item: unknown) => typeof item === 'string') : [],
|
typeof data.thinking_interval === 'number' ? data.thinking_interval : null,
|
||||||
|
disabled_tool_categories: Array.isArray(data.disabled_tool_categories)
|
||||||
|
? data.disabled_tool_categories.filter((item: unknown) => typeof item === 'string')
|
||||||
|
: [],
|
||||||
default_run_mode:
|
default_run_mode:
|
||||||
typeof data.default_run_mode === 'string' && RUN_MODE_OPTIONS.includes(data.default_run_mode as RunMode)
|
typeof data.default_run_mode === 'string' &&
|
||||||
? data.default_run_mode as RunMode
|
RUN_MODE_OPTIONS.includes(data.default_run_mode as RunMode)
|
||||||
|
? (data.default_run_mode as RunMode)
|
||||||
: null,
|
: null,
|
||||||
default_model: typeof data.default_model === 'string' ? data.default_model : fallbackModel,
|
default_model: typeof data.default_model === 'string' ? data.default_model : fallbackModel,
|
||||||
image_compression: typeof data.image_compression === 'string' ? data.image_compression : 'original',
|
image_compression:
|
||||||
|
typeof data.image_compression === 'string' ? data.image_compression : 'original',
|
||||||
auto_shallow_compress_enabled: !!data.auto_shallow_compress_enabled,
|
auto_shallow_compress_enabled: !!data.auto_shallow_compress_enabled,
|
||||||
auto_deep_compress_enabled: !!data.auto_deep_compress_enabled,
|
auto_deep_compress_enabled: !!data.auto_deep_compress_enabled,
|
||||||
shallow_compress_trigger_tokens: this.normalizeCompressionNumber(data.shallow_compress_trigger_tokens),
|
shallow_compress_trigger_tokens: this.normalizeCompressionNumber(
|
||||||
shallow_compress_keep_recent_tools: this.normalizeCompressionNumber(data.shallow_compress_keep_recent_tools),
|
data.shallow_compress_trigger_tokens
|
||||||
shallow_compress_max_replace_per_round: this.normalizeCompressionNumber(data.shallow_compress_max_replace_per_round),
|
),
|
||||||
shallow_compress_trigger_tool_calls_interval: this.normalizeCompressionNumber(data.shallow_compress_trigger_tool_calls_interval),
|
shallow_compress_keep_recent_tools: this.normalizeCompressionNumber(
|
||||||
deep_compress_trigger_tokens: this.normalizeCompressionNumber(data.deep_compress_trigger_tokens)
|
data.shallow_compress_keep_recent_tools
|
||||||
|
),
|
||||||
|
shallow_compress_max_replace_per_round: this.normalizeCompressionNumber(
|
||||||
|
data.shallow_compress_max_replace_per_round
|
||||||
|
),
|
||||||
|
shallow_compress_trigger_tool_calls_interval: this.normalizeCompressionNumber(
|
||||||
|
data.shallow_compress_trigger_tool_calls_interval
|
||||||
|
),
|
||||||
|
deep_compress_trigger_tokens: this.normalizeCompressionNumber(
|
||||||
|
data.deep_compress_trigger_tokens
|
||||||
|
)
|
||||||
};
|
};
|
||||||
this.clearFeedback();
|
this.clearFeedback();
|
||||||
},
|
},
|
||||||
@ -279,7 +300,9 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
this.toolCategories = payload.tool_categories
|
this.toolCategories = payload.tool_categories
|
||||||
.map((item: { id?: string; label?: string } = {}) => ({
|
.map((item: { id?: string; label?: string } = {}) => ({
|
||||||
id: typeof item.id === 'string' ? item.id : String(item.id ?? ''),
|
id: typeof item.id === 'string' ? item.id : String(item.id ?? ''),
|
||||||
label: (item.label && String(item.label)) || (typeof item.id === 'string' ? item.id : String(item.id ?? ''))
|
label:
|
||||||
|
(item.label && String(item.label)) ||
|
||||||
|
(typeof item.id === 'string' ? item.id : String(item.id ?? ''))
|
||||||
}))
|
}))
|
||||||
.filter((item: { id: string }) => !!item.id);
|
.filter((item: { id: string }) => !!item.id);
|
||||||
} else {
|
} else {
|
||||||
@ -289,7 +312,9 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
this.skillsCatalog = payload.skills_catalog
|
this.skillsCatalog = payload.skills_catalog
|
||||||
.map((item: { id?: string; label?: string; description?: string } = {}) => ({
|
.map((item: { id?: string; label?: string; description?: string } = {}) => ({
|
||||||
id: typeof item.id === 'string' ? item.id : String(item.id ?? ''),
|
id: typeof item.id === 'string' ? item.id : String(item.id ?? ''),
|
||||||
label: (item.label && String(item.label)) || (typeof item.id === 'string' ? item.id : String(item.id ?? '')),
|
label:
|
||||||
|
(item.label && String(item.label)) ||
|
||||||
|
(typeof item.id === 'string' ? item.id : String(item.id ?? '')),
|
||||||
description: typeof item.description === 'string' ? item.description : undefined
|
description: typeof item.description === 'string' ? item.description : undefined
|
||||||
}))
|
}))
|
||||||
.filter((item: { id: string }) => !!item.id);
|
.filter((item: { id: string }) => !!item.id);
|
||||||
@ -348,9 +373,11 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
this.error = '';
|
this.error = '';
|
||||||
try {
|
try {
|
||||||
const shallowTrigger =
|
const shallowTrigger =
|
||||||
this.normalizeCompressionNumber(this.form.shallow_compress_trigger_tokens) ?? DEFAULT_SHALLOW_COMPRESS_TRIGGER_TOKENS;
|
this.normalizeCompressionNumber(this.form.shallow_compress_trigger_tokens) ??
|
||||||
|
DEFAULT_SHALLOW_COMPRESS_TRIGGER_TOKENS;
|
||||||
const deepTrigger =
|
const deepTrigger =
|
||||||
this.normalizeCompressionNumber(this.form.deep_compress_trigger_tokens) ?? DEFAULT_DEEP_COMPRESS_TRIGGER_TOKENS;
|
this.normalizeCompressionNumber(this.form.deep_compress_trigger_tokens) ??
|
||||||
|
DEFAULT_DEEP_COMPRESS_TRIGGER_TOKENS;
|
||||||
if (deepTrigger <= shallowTrigger) {
|
if (deepTrigger <= shallowTrigger) {
|
||||||
throw new Error('深压缩触发上下文必须大于浅压缩触发上下文');
|
throw new Error('深压缩触发上下文必须大于浅压缩触发上下文');
|
||||||
}
|
}
|
||||||
@ -546,7 +573,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
const resp = await fetch('/logout', {
|
const resp = await fetch('/logout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
cache: 'no-store',
|
cache: 'no-store'
|
||||||
});
|
});
|
||||||
console.info('[auth-debug] logout POST status:', resp.status);
|
console.info('[auth-debug] logout POST status:', resp.status);
|
||||||
let payload: any = null;
|
let payload: any = null;
|
||||||
|
|||||||
@ -1,7 +1,16 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
export interface EffectivePolicy {
|
export interface EffectivePolicy {
|
||||||
categories: Record<string, { label: string; tools: string[]; default_enabled?: boolean; locked?: boolean; locked_state?: boolean | null }>;
|
categories: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
label: string;
|
||||||
|
tools: string[];
|
||||||
|
default_enabled?: boolean;
|
||||||
|
locked?: boolean;
|
||||||
|
locked_state?: boolean | null;
|
||||||
|
}
|
||||||
|
>;
|
||||||
forced_category_states: Record<string, boolean | null>;
|
forced_category_states: Record<string, boolean | null>;
|
||||||
disabled_models: string[];
|
disabled_models: string[];
|
||||||
ui_blocks: Record<string, boolean>;
|
ui_blocks: Record<string, boolean>;
|
||||||
|
|||||||
@ -130,8 +130,10 @@ export const useResourceStore = defineStore('resource', {
|
|||||||
const response = await fetch(`/api/conversations/${conversationId}/token-statistics`);
|
const response = await fetch(`/api/conversations/${conversationId}/token-statistics`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success && data.data) {
|
if (data.success && data.data) {
|
||||||
this.currentConversationTokens.cumulative_input_tokens = data.data.total_input_tokens || 0;
|
this.currentConversationTokens.cumulative_input_tokens =
|
||||||
this.currentConversationTokens.cumulative_output_tokens = data.data.total_output_tokens || 0;
|
data.data.total_input_tokens || 0;
|
||||||
|
this.currentConversationTokens.cumulative_output_tokens =
|
||||||
|
data.data.total_output_tokens || 0;
|
||||||
this.currentConversationTokens.cumulative_total_tokens = data.data.total_tokens || 0;
|
this.currentConversationTokens.cumulative_total_tokens = data.data.total_tokens || 0;
|
||||||
if (typeof data.data.current_context_tokens === 'number') {
|
if (typeof data.data.current_context_tokens === 'number') {
|
||||||
this.currentContextTokens = data.data.current_context_tokens;
|
this.currentContextTokens = data.data.current_context_tokens;
|
||||||
@ -151,7 +153,9 @@ export const useResourceStore = defineStore('resource', {
|
|||||||
this.projectStorage.limit_bytes = project.limit_bytes ?? null;
|
this.projectStorage.limit_bytes = project.limit_bytes ?? null;
|
||||||
this.projectStorage.limit_label =
|
this.projectStorage.limit_label =
|
||||||
project.limit_label ||
|
project.limit_label ||
|
||||||
(project.limit_bytes ? `${(project.limit_bytes / (1024 * 1024)).toFixed(0)} MB` : '未限制');
|
(project.limit_bytes
|
||||||
|
? `${(project.limit_bytes / (1024 * 1024)).toFixed(0)} MB`
|
||||||
|
: '未限制');
|
||||||
if (project.limit_bytes) {
|
if (project.limit_bytes) {
|
||||||
const pct =
|
const pct =
|
||||||
typeof project.usage_percent === 'number'
|
typeof project.usage_percent === 'number'
|
||||||
@ -177,8 +181,12 @@ export const useResourceStore = defineStore('resource', {
|
|||||||
if (stats && typeof stats.timestamp === 'number') {
|
if (stats && typeof stats.timestamp === 'number') {
|
||||||
const currentSample: ContainerSample = {
|
const currentSample: ContainerSample = {
|
||||||
timestamp: stats.timestamp,
|
timestamp: stats.timestamp,
|
||||||
rx_bytes: stats.net_io && typeof stats.net_io.rx_bytes === 'number' ? stats.net_io.rx_bytes : null,
|
rx_bytes:
|
||||||
tx_bytes: stats.net_io && typeof stats.net_io.tx_bytes === 'number' ? stats.net_io.tx_bytes : null
|
stats.net_io && typeof stats.net_io.rx_bytes === 'number'
|
||||||
|
? stats.net_io.rx_bytes
|
||||||
|
: null,
|
||||||
|
tx_bytes:
|
||||||
|
stats.net_io && typeof stats.net_io.tx_bytes === 'number' ? stats.net_io.tx_bytes : null
|
||||||
};
|
};
|
||||||
const last = this.lastContainerSample;
|
const last = this.lastContainerSample;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { debugLog } from '../app/methods/common';
|
import { debugLog } from '../app/methods/common';
|
||||||
|
|
||||||
const debugNotifyLog = (..._args: any[]) => {};
|
const debugNotifyLog = (...args: any[]) => {
|
||||||
|
void args;
|
||||||
|
};
|
||||||
const keyNotifyLog = (...args: any[]) => {
|
const keyNotifyLog = (...args: any[]) => {
|
||||||
console.log(...args);
|
console.log(...args);
|
||||||
};
|
};
|
||||||
@ -17,12 +19,12 @@ export const useTaskStore = defineStore('task', {
|
|||||||
pollingError: null as string | null,
|
pollingError: null as string | null,
|
||||||
pollingErrorCount: 0, // 新增错误计数
|
pollingErrorCount: 0, // 新增错误计数
|
||||||
taskCreatedAt: null as number | null,
|
taskCreatedAt: null as number | null,
|
||||||
taskUpdatedAt: null as number | null,
|
taskUpdatedAt: null as number | null
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
hasActiveTask: (state) => state.currentTaskId !== null && state.taskStatus === 'running',
|
hasActiveTask: (state) => state.currentTaskId !== null && state.taskStatus === 'running',
|
||||||
isTaskCompleted: (state) => ['succeeded', 'failed', 'canceled'].includes(state.taskStatus),
|
isTaskCompleted: (state) => ['succeeded', 'failed', 'canceled'].includes(state.taskStatus)
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@ -43,7 +45,7 @@ export const useTaskStore = defineStore('task', {
|
|||||||
const response = await fetch('/api/tasks', {
|
const response = await fetch('/api/tasks', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
message,
|
message,
|
||||||
@ -52,8 +54,9 @@ export const useTaskStore = defineStore('task', {
|
|||||||
conversation_id: conversationId,
|
conversation_id: conversationId,
|
||||||
model_key: options.model_key ?? undefined,
|
model_key: options.model_key ?? undefined,
|
||||||
run_mode: options.run_mode ?? undefined,
|
run_mode: options.run_mode ?? undefined,
|
||||||
thinking_mode: typeof options.thinking_mode === 'boolean' ? options.thinking_mode : undefined,
|
thinking_mode:
|
||||||
}),
|
typeof options.thinking_mode === 'boolean' ? options.thinking_mode : undefined
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -128,7 +131,13 @@ export const useTaskStore = defineStore('task', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const interesting = data.events.filter((e: any) =>
|
const interesting = data.events.filter((e: any) =>
|
||||||
['user_message', 'sub_agent_waiting', 'task_complete', 'task_stopped', 'error'].includes(e?.type)
|
[
|
||||||
|
'user_message',
|
||||||
|
'sub_agent_waiting',
|
||||||
|
'task_complete',
|
||||||
|
'task_stopped',
|
||||||
|
'error'
|
||||||
|
].includes(e?.type)
|
||||||
);
|
);
|
||||||
if (interesting.length > 0) {
|
if (interesting.length > 0) {
|
||||||
keyNotifyLog('[DEBUG_NOTIFY_KEY][task] events', {
|
keyNotifyLog('[DEBUG_NOTIFY_KEY][task] events', {
|
||||||
@ -228,7 +237,7 @@ export const useTaskStore = defineStore('task', {
|
|||||||
this.isPolling = true;
|
this.isPolling = true;
|
||||||
|
|
||||||
// 如果没有传入 eventHandler,从根实例获取
|
// 如果没有传入 eventHandler,从根实例获取
|
||||||
const handler = eventHandler || ((window as any).__taskEventHandler);
|
const handler = eventHandler || (window as any).__taskEventHandler;
|
||||||
|
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
console.error('[Task] 没有事件处理器,无法启动轮询');
|
console.error('[Task] 没有事件处理器,无法启动轮询');
|
||||||
@ -245,7 +254,14 @@ export const useTaskStore = defineStore('task', {
|
|||||||
}, 150);
|
}, 150);
|
||||||
},
|
},
|
||||||
|
|
||||||
resumeTask(taskId: string, options: { status?: 'running' | 'pending' | 'succeeded' | 'failed' | 'canceled'; resetOffset?: boolean; eventHandler?: (event: any) => void } = {}) {
|
resumeTask(
|
||||||
|
taskId: string,
|
||||||
|
options: {
|
||||||
|
status?: 'running' | 'pending' | 'succeeded' | 'failed' | 'canceled';
|
||||||
|
resetOffset?: boolean;
|
||||||
|
eventHandler?: (event: any) => void;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
if (!taskId) {
|
if (!taskId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -290,7 +306,7 @@ export const useTaskStore = defineStore('task', {
|
|||||||
debugLog('[Task] 取消任务:', this.currentTaskId);
|
debugLog('[Task] 取消任务:', this.currentTaskId);
|
||||||
|
|
||||||
const response = await fetch(`/api/tasks/${this.currentTaskId}/cancel`, {
|
const response = await fetch(`/api/tasks/${this.currentTaskId}/cancel`, {
|
||||||
method: 'POST',
|
method: 'POST'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -324,7 +340,8 @@ export const useTaskStore = defineStore('task', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查找运行中的任务
|
// 查找运行中的任务
|
||||||
const runningTask = result.data.find((task: any) =>
|
const runningTask = result.data.find(
|
||||||
|
(task: any) =>
|
||||||
task.status === 'running' &&
|
task.status === 'running' &&
|
||||||
(!conversationId || task.conversation_id === conversationId)
|
(!conversationId || task.conversation_id === conversationId)
|
||||||
);
|
);
|
||||||
@ -345,7 +362,8 @@ export const useTaskStore = defineStore('task', {
|
|||||||
const detailResult = await detailResponse.json();
|
const detailResult = await detailResponse.json();
|
||||||
if (detailResult.success && detailResult.data.events) {
|
if (detailResult.success && detailResult.data.events) {
|
||||||
// 设置为当前事件数量,只获取新事件
|
// 设置为当前事件数量,只获取新事件
|
||||||
this.lastEventIndex = detailResult.data.next_offset || detailResult.data.events.length;
|
this.lastEventIndex =
|
||||||
|
detailResult.data.next_offset || detailResult.data.events.length;
|
||||||
debugLog('[Task] 设置起始偏移量:', this.lastEventIndex);
|
debugLog('[Task] 设置起始偏移量:', this.lastEventIndex);
|
||||||
} else {
|
} else {
|
||||||
this.lastEventIndex = 0;
|
this.lastEventIndex = 0;
|
||||||
@ -387,6 +405,6 @@ export const useTaskStore = defineStore('task', {
|
|||||||
this.lastEventIndex = 0;
|
this.lastEventIndex = 0;
|
||||||
this.taskStatus = 'idle';
|
this.taskStatus = 'idle';
|
||||||
this.pollingError = null;
|
this.pollingError = null;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -51,7 +51,7 @@ export const useToolStore = defineStore('tool', {
|
|||||||
if (action.tool && action.tool.executionId) {
|
if (action.tool && action.tool.executionId) {
|
||||||
keys.add(action.tool.executionId);
|
keys.add(action.tool.executionId);
|
||||||
}
|
}
|
||||||
keys.forEach(key => {
|
keys.forEach((key) => {
|
||||||
if (key !== undefined && key !== null) {
|
if (key !== undefined && key !== null) {
|
||||||
this.toolActionIndex.set(String(key), action);
|
this.toolActionIndex.set(String(key), action);
|
||||||
}
|
}
|
||||||
@ -67,12 +67,16 @@ export const useToolStore = defineStore('tool', {
|
|||||||
keysToRemove.push(key);
|
keysToRemove.push(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keysToRemove.forEach(key => this.toolActionIndex.delete(key));
|
keysToRemove.forEach((key) => this.toolActionIndex.delete(key));
|
||||||
if (action.tool && action.tool.name) {
|
if (action.tool && action.tool.name) {
|
||||||
this.releaseToolAction(action.tool.name, action);
|
this.releaseToolAction(action.tool.name, action);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
findToolAction(id?: string | number | null, preparingId?: string | number | null, executionId?: string | number | null) {
|
findToolAction(
|
||||||
|
id?: string | number | null,
|
||||||
|
preparingId?: string | number | null,
|
||||||
|
executionId?: string | number | null
|
||||||
|
) {
|
||||||
const candidates = [executionId, id, preparingId];
|
const candidates = [executionId, id, preparingId];
|
||||||
for (const candidate of candidates) {
|
for (const candidate of candidates) {
|
||||||
if (candidate === undefined || candidate === null) {
|
if (candidate === undefined || candidate === null) {
|
||||||
|
|||||||
@ -3,7 +3,11 @@ import { useModelStore } from './model';
|
|||||||
|
|
||||||
export type TutorialPlacement = 'auto' | 'top' | 'right' | 'bottom' | 'left' | 'center';
|
export type TutorialPlacement = 'auto' | 'top' | 'right' | 'bottom' | 'left' | 'center';
|
||||||
export type TutorialStepMode = 'info' | 'must_click';
|
export type TutorialStepMode = 'info' | 'must_click';
|
||||||
export type TutorialCondition = 'model_supports_media' | 'is_app_shell' | 'is_mobile_viewport' | 'not_mobile_viewport';
|
export type TutorialCondition =
|
||||||
|
| 'model_supports_media'
|
||||||
|
| 'is_app_shell'
|
||||||
|
| 'is_mobile_viewport'
|
||||||
|
| 'not_mobile_viewport';
|
||||||
|
|
||||||
export interface TutorialStep {
|
export interface TutorialStep {
|
||||||
id: string;
|
id: string;
|
||||||
@ -30,18 +34,119 @@ const DEFAULT_STEPS: TutorialStep[] = [
|
|||||||
mode: 'info',
|
mode: 'info',
|
||||||
placement: 'center'
|
placement: 'center'
|
||||||
},
|
},
|
||||||
{ id: 'sidebar-conversations-open', title: '展开对话记录', description: '下一步会自动点击展开对话记录。', target: '[data-tutorial="conversation-menu"]', mode: 'info', autoClick: true, placement: 'right', condition: 'not_mobile_viewport' },
|
{
|
||||||
{ id: 'sidebar-conversations-close', title: '折叠对话记录', description: '下一步会自动点击折叠按钮收起对话记录。', target: '[data-tutorial="conversation-collapse"]', mode: 'info', autoClick: true, placement: 'right', condition: 'not_mobile_viewport' },
|
id: 'sidebar-conversations-open',
|
||||||
{ id: 'sidebar-new-chat', title: '新建对话', description: '下一步会自动新建并进入一个对话。', target: '[data-tutorial="quick-new-conversation"]', mode: 'info', autoClick: true, placement: 'right', condition: 'not_mobile_viewport' },
|
title: '展开对话记录',
|
||||||
{ id: 'sidebar-workspace-toggle', title: '工作区折叠', description: '显示/隐藏左侧工作区面板。', target: '[data-tutorial="workspace-toggle"]', mode: 'info', placement: 'right', condition: 'not_mobile_viewport' },
|
description: '下一步会自动点击展开对话记录。',
|
||||||
{ id: 'sidebar-monitor-toggle', title: '虚拟显示器', description: '切换到虚拟显示器模式。', target: '[data-tutorial="monitor-toggle"]', mode: 'info', placement: 'right', condition: 'not_mobile_viewport' },
|
target: '[data-tutorial="conversation-menu"]',
|
||||||
{ id: 'workspace-panel-switch', title: '工作区面板切换', description: '下一步会自动展开面板切换菜单。', target: '[data-tutorial="panel-menu-toggle"]', mode: 'info', autoClick: true, placement: 'right', condition: 'not_mobile_viewport' },
|
mode: 'info',
|
||||||
{ id: 'workspace-panel-options', title: '四合一面板', description: '这里可以切换文件 / 待办 / 子智能体 / 后台指令。', target: '[data-tutorial="panel-menu"]', mode: 'info', placement: 'right', condition: 'not_mobile_viewport' },
|
autoClick: true,
|
||||||
{ id: 'workspace-mode-indicator', title: '思考模式', description: '点击切换快速 / 思考 / 深度思考。', target: '[data-tutorial="run-mode-indicator"]', mode: 'info', placement: 'right', condition: 'not_mobile_viewport' },
|
placement: 'right',
|
||||||
{ id: 'workspace-connection-indicator', title: '连接状态指示灯', description: '绿色表示连接正常;红色表示与后端断开连接。', target: '[data-tutorial="connection-indicator"]', mode: 'info', placement: 'right', condition: 'not_mobile_viewport' },
|
condition: 'not_mobile_viewport'
|
||||||
{ id: 'header-model-selector', title: '模型与模式选择', description: '下一步会自动展开模型与运行模式弹窗。', target: '[data-tutorial="header-model-selector"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'not_mobile_viewport' },
|
},
|
||||||
{ id: 'header-model-options', title: '模型列表', description: '这里是可用模型列表。', target: '[data-tutorial="header-model-options"]', mode: 'info', placement: 'bottom', condition: 'not_mobile_viewport' },
|
{
|
||||||
{ id: 'header-runmode-options', title: '运行模式列表', description: '这里可切换快速 / 思考 / 深度思考。', target: '[data-tutorial="header-runmode-options"]', mode: 'info', placement: 'bottom', condition: 'not_mobile_viewport' },
|
id: 'sidebar-conversations-close',
|
||||||
|
title: '折叠对话记录',
|
||||||
|
description: '下一步会自动点击折叠按钮收起对话记录。',
|
||||||
|
target: '[data-tutorial="conversation-collapse"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sidebar-new-chat',
|
||||||
|
title: '新建对话',
|
||||||
|
description: '下一步会自动新建并进入一个对话。',
|
||||||
|
target: '[data-tutorial="quick-new-conversation"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sidebar-workspace-toggle',
|
||||||
|
title: '工作区折叠',
|
||||||
|
description: '显示/隐藏左侧工作区面板。',
|
||||||
|
target: '[data-tutorial="workspace-toggle"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sidebar-monitor-toggle',
|
||||||
|
title: '虚拟显示器',
|
||||||
|
description: '切换到虚拟显示器模式。',
|
||||||
|
target: '[data-tutorial="monitor-toggle"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'workspace-panel-switch',
|
||||||
|
title: '工作区面板切换',
|
||||||
|
description: '下一步会自动展开面板切换菜单。',
|
||||||
|
target: '[data-tutorial="panel-menu-toggle"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'workspace-panel-options',
|
||||||
|
title: '四合一面板',
|
||||||
|
description: '这里可以切换文件 / 待办 / 子智能体 / 后台指令。',
|
||||||
|
target: '[data-tutorial="panel-menu"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'workspace-mode-indicator',
|
||||||
|
title: '思考模式',
|
||||||
|
description: '点击切换快速 / 思考 / 深度思考。',
|
||||||
|
target: '[data-tutorial="run-mode-indicator"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'workspace-connection-indicator',
|
||||||
|
title: '连接状态指示灯',
|
||||||
|
description: '绿色表示连接正常;红色表示与后端断开连接。',
|
||||||
|
target: '[data-tutorial="connection-indicator"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'header-model-selector',
|
||||||
|
title: '模型与模式选择',
|
||||||
|
description: '下一步会自动展开模型与运行模式弹窗。',
|
||||||
|
target: '[data-tutorial="header-model-selector"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'header-model-options',
|
||||||
|
title: '模型列表',
|
||||||
|
description: '这里是可用模型列表。',
|
||||||
|
target: '[data-tutorial="header-model-options"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'header-runmode-options',
|
||||||
|
title: '运行模式列表',
|
||||||
|
description: '这里可切换快速 / 思考 / 深度思考。',
|
||||||
|
target: '[data-tutorial="header-runmode-options"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'header-menu-close',
|
id: 'header-menu-close',
|
||||||
title: '关闭模型菜单',
|
title: '关闭模型菜单',
|
||||||
@ -52,52 +157,422 @@ const DEFAULT_STEPS: TutorialStep[] = [
|
|||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
condition: 'not_mobile_viewport'
|
condition: 'not_mobile_viewport'
|
||||||
},
|
},
|
||||||
{ id: 'mobile-menu-open-conversation', title: '打开菜单', description: '下一步会自动打开左上角菜单。', target: '[data-tutorial="mobile-menu-trigger"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
{
|
||||||
{ id: 'mobile-menu-conversation', title: '对话记录', description: '下一步会自动进入对话记录。', target: '[data-tutorial="mobile-menu-conversation"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
id: 'mobile-menu-open-conversation',
|
||||||
{ id: 'mobile-conversation-close', title: '关闭对话记录', description: '下一步会自动关闭对话记录面板。', target: '[data-tutorial="conversation-collapse"]', mode: 'info', autoClick: true, placement: 'right', condition: 'is_mobile_viewport' },
|
title: '打开菜单',
|
||||||
{ id: 'mobile-menu-open-workspace', title: '再次打开菜单', description: '下一步会自动打开菜单。', target: '[data-tutorial="mobile-menu-trigger"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
description: '下一步会自动打开左上角菜单。',
|
||||||
{ id: 'mobile-menu-workspace', title: '工作文件', description: '下一步会自动进入工作文件。', target: '[data-tutorial="mobile-menu-workspace"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
target: '[data-tutorial="mobile-menu-trigger"]',
|
||||||
{ id: 'mobile-workspace-panel-switch', title: '工作区面板切换', description: '下一步会自动展开切换弹窗。', target: '[data-tutorial="panel-menu-toggle"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
mode: 'info',
|
||||||
{ id: 'mobile-workspace-panel-options', title: '切换弹窗选项', description: '这里可切换文件 / 待办 / 子智能体 / 后台指令。', target: '[data-tutorial="panel-menu"]', mode: 'info', placement: 'bottom', condition: 'is_mobile_viewport' },
|
autoClick: true,
|
||||||
{ id: 'mobile-workspace-close', title: '关闭工作文件', description: '下一步会自动关闭工作文件面板。', target: '[data-tutorial="mobile-workspace-close"]', mode: 'info', autoClick: true, placement: 'right', condition: 'is_mobile_viewport' },
|
placement: 'bottom',
|
||||||
{ id: 'mobile-menu-open-newchat', title: '再次打开菜单', description: '下一步会自动打开菜单。', target: '[data-tutorial="mobile-menu-trigger"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
condition: 'is_mobile_viewport'
|
||||||
{ id: 'mobile-menu-new-chat', title: '新建对话', description: '下一步会自动新建对话。', target: '[data-tutorial="mobile-menu-new-chat"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
},
|
||||||
{ id: 'mobile-model-selector-open', title: '模型与思考模式', description: '下一步会自动展开手机端模型/思考模式选择。', target: '[data-tutorial="header-model-selector-mobile"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
{
|
||||||
{ id: 'mobile-model-options', title: '模型列表', description: '这里是手机端可选模型。', target: '[data-tutorial="header-model-options"]', mode: 'info', placement: 'bottom', condition: 'is_mobile_viewport' },
|
id: 'mobile-menu-conversation',
|
||||||
{ id: 'mobile-runmode-options', title: '思考模式列表', description: '这里可切换快速 / 思考 / 深度思考。', target: '[data-tutorial="header-runmode-options"]', mode: 'info', placement: 'bottom', condition: 'is_mobile_viewport' },
|
title: '对话记录',
|
||||||
{ id: 'mobile-model-selector-close', title: '关闭选择菜单', description: '下一步会自动点击空白区域关闭菜单。', target: '.model-mode-dropdown', mode: 'info', autoOutsideClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
description: '下一步会自动进入对话记录。',
|
||||||
{ id: 'open-quick-menu', title: '打开 + 菜单', description: '将自动为你展开快捷菜单。', target: '[data-tutorial="quick-menu-open"]', mode: 'info', autoClick: true, placement: 'top' },
|
target: '[data-tutorial="mobile-menu-conversation"]',
|
||||||
{ id: 'quick-upload', title: '上传文件', description: '上传本地文件到当前工作区。', target: '[data-tutorial="quick-upload"]', mode: 'info', placement: 'left' },
|
mode: 'info',
|
||||||
{ id: 'quick-review', title: '对话回顾', description: '回顾并压缩上下文。', target: '[data-tutorial="quick-review"]', mode: 'info', placement: 'left' },
|
autoClick: true,
|
||||||
{ id: 'quick-send-image', title: '发送图片', description: '当前模型支持时,可发送图片给 AI 分析。', target: '[data-tutorial="quick-send-image"]', mode: 'info', placement: 'left', condition: 'model_supports_media' },
|
placement: 'bottom',
|
||||||
{ id: 'quick-send-video', title: '发送视频', description: '当前模型支持时,可发送视频给 AI 分析。', target: '[data-tutorial="quick-send-video"]', mode: 'info', placement: 'left', condition: 'model_supports_media' },
|
condition: 'is_mobile_viewport'
|
||||||
{ id: 'quick-tool-disable', title: '工具禁用', description: '临时禁用工具分类,控制模型行为范围。', target: '[data-tutorial="quick-tool-menu"]', mode: 'info', placement: 'left' },
|
},
|
||||||
{ id: 'quick-settings', title: '设置菜单', description: '这里可快速访问常用功能。', target: '[data-tutorial="quick-settings-menu"]', mode: 'info', placement: 'left' },
|
{
|
||||||
{ id: 'open-settings-submenu', title: '打开设置子菜单', description: '下一步会自动展开设置子菜单。', target: '[data-tutorial="quick-settings-menu"]', mode: 'info', autoClick: true, placement: 'left' },
|
id: 'mobile-conversation-close',
|
||||||
{ id: 'open-token-panel', title: '打开用量统计', description: '下一步会自动打开用量统计面板。', target: '[data-tutorial="settings-token-panel"]', mode: 'info', autoClick: true, placement: 'left' },
|
title: '关闭对话记录',
|
||||||
{ id: 'token-panel', title: 'Token 统计面板', description: '这里可以查看上下文、额度和资源统计。', target: '[data-tutorial="token-drawer"]', mode: 'info', placement: 'left' },
|
description: '下一步会自动关闭对话记录面板。',
|
||||||
{ id: 'close-token-panel', title: '关闭统计面板', description: '下一步会自动关闭统计面板。', target: '[data-tutorial="token-close"]', mode: 'info', autoClick: true, placement: 'left' },
|
target: '[data-tutorial="conversation-collapse"]',
|
||||||
{ id: 'open-personal-space', title: '打开个人空间', description: '下一步会自动打开个人空间。', target: '[data-tutorial="open-personal-space"]', mode: 'info', autoClick: true, placement: 'right', condition: 'not_mobile_viewport' },
|
mode: 'info',
|
||||||
{ id: 'mobile-menu-open-personal', title: '打开菜单', description: '下一步会自动打开菜单。', target: '[data-tutorial="mobile-menu-trigger"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
autoClick: true,
|
||||||
{ id: 'mobile-menu-personal', title: '进入个人空间', description: '下一步会自动进入个人空间。', target: '[data-tutorial="mobile-menu-personal"]', mode: 'info', autoClick: true, placement: 'bottom', condition: 'is_mobile_viewport' },
|
placement: 'right',
|
||||||
{ id: 'personal-overview', title: '个人空间总览', description: '这里是个性化设置与高级配置中心。', target: '[data-tutorial="personal-card"]', mode: 'info', placement: 'right' },
|
condition: 'is_mobile_viewport'
|
||||||
{ id: 'tab-preferences', title: '个性化设置', description: '下一步自动切换到“个性化设置”。', target: '[data-tutorial="personal-tab-preferences"]', mode: 'info', autoClick: true, placement: 'right' },
|
},
|
||||||
{ id: 'page-preferences', title: '个性化设置内容', description: '可配置昵称、语气、职业和必备信息。', target: '[data-tutorial="personal-content-shell"]', mode: 'info', placement: 'right' },
|
{
|
||||||
{ id: 'tab-model', title: '模型偏好', description: '下一步自动切换到“模型偏好”。', target: '[data-tutorial="personal-tab-model"]', mode: 'info', autoClick: true, placement: 'right' },
|
id: 'mobile-menu-open-workspace',
|
||||||
{ id: 'page-model', title: '模型偏好内容', description: '可设置默认模型、默认运行模式和思考频率。', target: '[data-tutorial="personal-content-shell"]', mode: 'info', placement: 'right' },
|
title: '再次打开菜单',
|
||||||
{ id: 'tab-usage', title: '用量统计', description: '下一步自动切换到“用量统计”。', target: '[data-tutorial="personal-tab-usage"]', mode: 'info', autoClick: true, placement: 'right' },
|
description: '下一步会自动打开菜单。',
|
||||||
{ id: 'page-usage', title: '用量统计内容', description: '可查看累计 Token、对话数和工具调用数据。', target: '[data-tutorial="personal-content-shell"]', mode: 'info', placement: 'right' },
|
target: '[data-tutorial="mobile-menu-trigger"]',
|
||||||
{ id: 'tab-app-update', title: '软件更新', description: '移动端将自动切换到“软件更新”。', target: '[data-tutorial="personal-tab-app-update"]', mode: 'info', autoClick: true, placement: 'right', condition: 'is_app_shell' },
|
mode: 'info',
|
||||||
{ id: 'page-app-update', title: '软件更新内容', description: '这里会展示最新版本和更新说明。', target: '[data-tutorial="personal-content-shell"]', mode: 'info', placement: 'right', condition: 'is_app_shell' },
|
autoClick: true,
|
||||||
{ id: 'tab-behavior', title: '模型行为', description: '下一步自动切换到“模型行为”。', target: '[data-tutorial="personal-tab-behavior"]', mode: 'info', autoClick: true, placement: 'right' },
|
placement: 'bottom',
|
||||||
{ id: 'page-behavior', title: '模型行为内容', description: '可配置工具显示、压缩策略等高级选项。', target: '[data-tutorial="personal-content-shell"]', mode: 'info', placement: 'right' },
|
condition: 'is_mobile_viewport'
|
||||||
{ id: 'tab-skills', title: 'Skills', description: '下一步自动切换到“Skills”。', target: '[data-tutorial="personal-tab-skills"]', mode: 'info', autoClick: true, placement: 'right' },
|
},
|
||||||
{ id: 'page-skills', title: 'Skills 内容', description: '可启用/禁用技能并同步到工作区。', target: '[data-tutorial="personal-content-shell"]', mode: 'info', placement: 'right' },
|
{
|
||||||
{ id: 'tab-image', title: '图片压缩', description: '下一步自动切换到“图片压缩”。', target: '[data-tutorial="personal-tab-image"]', mode: 'info', autoClick: true, placement: 'right' },
|
id: 'mobile-menu-workspace',
|
||||||
{ id: 'page-image', title: '图片压缩内容', description: '可选择原图、1080p、720p、540p 压缩策略。', target: '[data-tutorial="personal-content-shell"]', mode: 'info', placement: 'right' },
|
title: '工作文件',
|
||||||
{ id: 'tab-theme', title: '主题切换', description: '下一步自动切换到“主题切换”。', target: '[data-tutorial="personal-tab-theme"]', mode: 'info', autoClick: true, placement: 'right' },
|
description: '下一步会自动进入工作文件。',
|
||||||
{ id: 'page-theme', title: '主题切换内容', description: '可在经典、明亮、夜间主题间切换。', target: '[data-tutorial="personal-content-shell"]', mode: 'info', placement: 'right' },
|
target: '[data-tutorial="mobile-menu-workspace"]',
|
||||||
{ id: 'close-personal-space', title: '关闭个人空间', description: '下一步自动关闭个人空间并结束导览。', target: '[data-tutorial="personal-close"]', mode: 'info', autoClick: true, placement: 'bottom' },
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-workspace-panel-switch',
|
||||||
|
title: '工作区面板切换',
|
||||||
|
description: '下一步会自动展开切换弹窗。',
|
||||||
|
target: '[data-tutorial="panel-menu-toggle"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-workspace-panel-options',
|
||||||
|
title: '切换弹窗选项',
|
||||||
|
description: '这里可切换文件 / 待办 / 子智能体 / 后台指令。',
|
||||||
|
target: '[data-tutorial="panel-menu"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-workspace-close',
|
||||||
|
title: '关闭工作文件',
|
||||||
|
description: '下一步会自动关闭工作文件面板。',
|
||||||
|
target: '[data-tutorial="mobile-workspace-close"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-menu-open-newchat',
|
||||||
|
title: '再次打开菜单',
|
||||||
|
description: '下一步会自动打开菜单。',
|
||||||
|
target: '[data-tutorial="mobile-menu-trigger"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-menu-new-chat',
|
||||||
|
title: '新建对话',
|
||||||
|
description: '下一步会自动新建对话。',
|
||||||
|
target: '[data-tutorial="mobile-menu-new-chat"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-model-selector-open',
|
||||||
|
title: '模型与思考模式',
|
||||||
|
description: '下一步会自动展开手机端模型/思考模式选择。',
|
||||||
|
target: '[data-tutorial="header-model-selector-mobile"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-model-options',
|
||||||
|
title: '模型列表',
|
||||||
|
description: '这里是手机端可选模型。',
|
||||||
|
target: '[data-tutorial="header-model-options"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-runmode-options',
|
||||||
|
title: '思考模式列表',
|
||||||
|
description: '这里可切换快速 / 思考 / 深度思考。',
|
||||||
|
target: '[data-tutorial="header-runmode-options"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-model-selector-close',
|
||||||
|
title: '关闭选择菜单',
|
||||||
|
description: '下一步会自动点击空白区域关闭菜单。',
|
||||||
|
target: '.model-mode-dropdown',
|
||||||
|
mode: 'info',
|
||||||
|
autoOutsideClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'open-quick-menu',
|
||||||
|
title: '打开 + 菜单',
|
||||||
|
description: '将自动为你展开快捷菜单。',
|
||||||
|
target: '[data-tutorial="quick-menu-open"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'top'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quick-upload',
|
||||||
|
title: '上传文件',
|
||||||
|
description: '上传本地文件到当前工作区。',
|
||||||
|
target: '[data-tutorial="quick-upload"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quick-review',
|
||||||
|
title: '对话回顾',
|
||||||
|
description: '回顾并压缩上下文。',
|
||||||
|
target: '[data-tutorial="quick-review"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quick-send-image',
|
||||||
|
title: '发送图片',
|
||||||
|
description: '当前模型支持时,可发送图片给 AI 分析。',
|
||||||
|
target: '[data-tutorial="quick-send-image"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'left',
|
||||||
|
condition: 'model_supports_media'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quick-send-video',
|
||||||
|
title: '发送视频',
|
||||||
|
description: '当前模型支持时,可发送视频给 AI 分析。',
|
||||||
|
target: '[data-tutorial="quick-send-video"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'left',
|
||||||
|
condition: 'model_supports_media'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quick-tool-disable',
|
||||||
|
title: '工具禁用',
|
||||||
|
description: '临时禁用工具分类,控制模型行为范围。',
|
||||||
|
target: '[data-tutorial="quick-tool-menu"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'quick-settings',
|
||||||
|
title: '设置菜单',
|
||||||
|
description: '这里可快速访问常用功能。',
|
||||||
|
target: '[data-tutorial="quick-settings-menu"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'open-settings-submenu',
|
||||||
|
title: '打开设置子菜单',
|
||||||
|
description: '下一步会自动展开设置子菜单。',
|
||||||
|
target: '[data-tutorial="quick-settings-menu"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'open-token-panel',
|
||||||
|
title: '打开用量统计',
|
||||||
|
description: '下一步会自动打开用量统计面板。',
|
||||||
|
target: '[data-tutorial="settings-token-panel"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'token-panel',
|
||||||
|
title: 'Token 统计面板',
|
||||||
|
description: '这里可以查看上下文、额度和资源统计。',
|
||||||
|
target: '[data-tutorial="token-drawer"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'close-token-panel',
|
||||||
|
title: '关闭统计面板',
|
||||||
|
description: '下一步会自动关闭统计面板。',
|
||||||
|
target: '[data-tutorial="token-close"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'open-personal-space',
|
||||||
|
title: '打开个人空间',
|
||||||
|
description: '下一步会自动打开个人空间。',
|
||||||
|
target: '[data-tutorial="open-personal-space"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'not_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-menu-open-personal',
|
||||||
|
title: '打开菜单',
|
||||||
|
description: '下一步会自动打开菜单。',
|
||||||
|
target: '[data-tutorial="mobile-menu-trigger"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mobile-menu-personal',
|
||||||
|
title: '进入个人空间',
|
||||||
|
description: '下一步会自动进入个人空间。',
|
||||||
|
target: '[data-tutorial="mobile-menu-personal"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom',
|
||||||
|
condition: 'is_mobile_viewport'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'personal-overview',
|
||||||
|
title: '个人空间总览',
|
||||||
|
description: '这里是个性化设置与高级配置中心。',
|
||||||
|
target: '[data-tutorial="personal-card"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-preferences',
|
||||||
|
title: '个性化设置',
|
||||||
|
description: '下一步自动切换到“个性化设置”。',
|
||||||
|
target: '[data-tutorial="personal-tab-preferences"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page-preferences',
|
||||||
|
title: '个性化设置内容',
|
||||||
|
description: '可配置昵称、语气、职业和必备信息。',
|
||||||
|
target: '[data-tutorial="personal-content-shell"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-model',
|
||||||
|
title: '模型偏好',
|
||||||
|
description: '下一步自动切换到“模型偏好”。',
|
||||||
|
target: '[data-tutorial="personal-tab-model"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page-model',
|
||||||
|
title: '模型偏好内容',
|
||||||
|
description: '可设置默认模型、默认运行模式和思考频率。',
|
||||||
|
target: '[data-tutorial="personal-content-shell"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-usage',
|
||||||
|
title: '用量统计',
|
||||||
|
description: '下一步自动切换到“用量统计”。',
|
||||||
|
target: '[data-tutorial="personal-tab-usage"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page-usage',
|
||||||
|
title: '用量统计内容',
|
||||||
|
description: '可查看累计 Token、对话数和工具调用数据。',
|
||||||
|
target: '[data-tutorial="personal-content-shell"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-app-update',
|
||||||
|
title: '软件更新',
|
||||||
|
description: '移动端将自动切换到“软件更新”。',
|
||||||
|
target: '[data-tutorial="personal-tab-app-update"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'is_app_shell'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page-app-update',
|
||||||
|
title: '软件更新内容',
|
||||||
|
description: '这里会展示最新版本和更新说明。',
|
||||||
|
target: '[data-tutorial="personal-content-shell"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right',
|
||||||
|
condition: 'is_app_shell'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-behavior',
|
||||||
|
title: '模型行为',
|
||||||
|
description: '下一步自动切换到“模型行为”。',
|
||||||
|
target: '[data-tutorial="personal-tab-behavior"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page-behavior',
|
||||||
|
title: '模型行为内容',
|
||||||
|
description: '可配置工具显示、压缩策略等高级选项。',
|
||||||
|
target: '[data-tutorial="personal-content-shell"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-skills',
|
||||||
|
title: 'Skills',
|
||||||
|
description: '下一步自动切换到“Skills”。',
|
||||||
|
target: '[data-tutorial="personal-tab-skills"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page-skills',
|
||||||
|
title: 'Skills 内容',
|
||||||
|
description: '可启用/禁用技能并同步到工作区。',
|
||||||
|
target: '[data-tutorial="personal-content-shell"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-image',
|
||||||
|
title: '图片压缩',
|
||||||
|
description: '下一步自动切换到“图片压缩”。',
|
||||||
|
target: '[data-tutorial="personal-tab-image"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page-image',
|
||||||
|
title: '图片压缩内容',
|
||||||
|
description: '可选择原图、1080p、720p、540p 压缩策略。',
|
||||||
|
target: '[data-tutorial="personal-content-shell"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tab-theme',
|
||||||
|
title: '主题切换',
|
||||||
|
description: '下一步自动切换到“主题切换”。',
|
||||||
|
target: '[data-tutorial="personal-tab-theme"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'page-theme',
|
||||||
|
title: '主题切换内容',
|
||||||
|
description: '可在经典、明亮、夜间主题间切换。',
|
||||||
|
target: '[data-tutorial="personal-content-shell"]',
|
||||||
|
mode: 'info',
|
||||||
|
placement: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'close-personal-space',
|
||||||
|
title: '关闭个人空间',
|
||||||
|
description: '下一步自动关闭个人空间并结束导览。',
|
||||||
|
target: '[data-tutorial="personal-close"]',
|
||||||
|
mode: 'info',
|
||||||
|
autoClick: true,
|
||||||
|
placement: 'bottom'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'done',
|
id: 'done',
|
||||||
title: '教程完成!',
|
title: '教程完成!',
|
||||||
@ -251,7 +726,10 @@ export const useTutorialStore = defineStore('tutorial', {
|
|||||||
skipHiddenSteps(direction: 'forward' | 'backward') {
|
skipHiddenSteps(direction: 'forward' | 'backward') {
|
||||||
if (!this.running) return;
|
if (!this.running) return;
|
||||||
if (direction === 'forward') {
|
if (direction === 'forward') {
|
||||||
while (this.currentIndex < this.steps.length && !this.shouldIncludeStep(this.steps[this.currentIndex])) {
|
while (
|
||||||
|
this.currentIndex < this.steps.length &&
|
||||||
|
!this.shouldIncludeStep(this.steps[this.currentIndex])
|
||||||
|
) {
|
||||||
this.currentIndex += 1;
|
this.currentIndex += 1;
|
||||||
}
|
}
|
||||||
if (this.currentIndex >= this.steps.length) {
|
if (this.currentIndex >= this.steps.length) {
|
||||||
|
|||||||
@ -200,7 +200,9 @@ export const useUiStore = defineStore('ui', {
|
|||||||
closable: options.closable !== false,
|
closable: options.closable !== false,
|
||||||
timeoutId: null
|
timeoutId: null
|
||||||
};
|
};
|
||||||
const duration = Object.prototype.hasOwnProperty.call(options, 'duration') ? options.duration : 4000;
|
const duration = Object.prototype.hasOwnProperty.call(options, 'duration')
|
||||||
|
? options.duration
|
||||||
|
: 4000;
|
||||||
if (duration !== null) {
|
if (duration !== null) {
|
||||||
entry.timeoutId = setTimeout(() => this.dismissToast(id), duration);
|
entry.timeoutId = setTimeout(() => this.dismissToast(id), duration);
|
||||||
}
|
}
|
||||||
@ -208,7 +210,7 @@ export const useUiStore = defineStore('ui', {
|
|||||||
return id;
|
return id;
|
||||||
},
|
},
|
||||||
updateToast(id: number, patch: ToastOptions = {}) {
|
updateToast(id: number, patch: ToastOptions = {}) {
|
||||||
const entry = this.toastQueue.find(item => item.id === id);
|
const entry = this.toastQueue.find((item) => item.id === id);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -235,7 +237,7 @@ export const useUiStore = defineStore('ui', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissToast(id: number) {
|
dismissToast(id: number) {
|
||||||
const index = this.toastQueue.findIndex(item => item.id === id);
|
const index = this.toastQueue.findIndex((item) => item.id === id);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,7 +92,11 @@ export const useUploadStore = defineStore('upload', {
|
|||||||
},
|
},
|
||||||
async uploadFile(
|
async uploadFile(
|
||||||
file: File,
|
file: File,
|
||||||
options: { manageUploading?: boolean; refreshFileTree?: boolean; skipPolicyCheck?: boolean } = {}
|
options: {
|
||||||
|
manageUploading?: boolean;
|
||||||
|
refreshFileTree?: boolean;
|
||||||
|
skipPolicyCheck?: boolean;
|
||||||
|
} = {}
|
||||||
): Promise<UploadResult | null> {
|
): Promise<UploadResult | null> {
|
||||||
const { manageUploading = true, refreshFileTree = true, skipPolicyCheck = false } = options;
|
const { manageUploading = true, refreshFileTree = true, skipPolicyCheck = false } = options;
|
||||||
if (!file || (manageUploading && this.uploading)) {
|
if (!file || (manageUploading && this.uploading)) {
|
||||||
|
|||||||
@ -182,11 +182,7 @@ export function getToolStatusText(tool: any, opts?: { intentEnabled?: boolean })
|
|||||||
};
|
};
|
||||||
return runningMap[readType] || '正在读取文件...';
|
return runningMap[readType] || '正在读取文件...';
|
||||||
}
|
}
|
||||||
const label =
|
const label = RUNNING_STATUS_TEXTS[tool.name] || tool.display_name || tool.name || '';
|
||||||
RUNNING_STATUS_TEXTS[tool.name] ||
|
|
||||||
tool.display_name ||
|
|
||||||
tool.name ||
|
|
||||||
'';
|
|
||||||
return label ? label : '调用工具中';
|
return label ? label : '调用工具中';
|
||||||
}
|
}
|
||||||
if (tool.status === 'completed') {
|
if (tool.status === 'completed') {
|
||||||
@ -283,9 +279,7 @@ export function formatSearchDomains(filters: ToolPayload): string {
|
|||||||
if (!Array.isArray(domains) || domains.length === 0) {
|
if (!Array.isArray(domains) || domains.length === 0) {
|
||||||
return '未限定网站';
|
return '未限定网站';
|
||||||
}
|
}
|
||||||
const normalized = domains
|
const normalized = domains.map((item) => String(item || '').trim()).filter(Boolean);
|
||||||
.map(item => String(item || '').trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
return normalized.length ? normalized.join(', ') : '未限定网站';
|
return normalized.length ? normalized.join(', ') : '未限定网站';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export function buildQuotaResetSummary(quota: UsageQuotaMap): string {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
['fast', 'thinking', 'search'].forEach(type => {
|
['fast', 'thinking', 'search'].forEach((type) => {
|
||||||
const entry = quota?.[type];
|
const entry = quota?.[type];
|
||||||
if (entry && entry.reset_at && normalizeNumber(entry.count) > 0) {
|
if (entry && entry.reset_at && normalizeNumber(entry.count) > 0) {
|
||||||
parts.push(`${quotaTypeLabel(type)} ${formatResetTime(entry.reset_at)}`);
|
parts.push(`${quotaTypeLabel(type)} ${formatResetTime(entry.reset_at)}`);
|
||||||
@ -133,7 +133,11 @@ export function isQuotaExceeded(quota: UsageQuotaMap, type: string): boolean {
|
|||||||
return normalizeNumber(entry.count) >= limit;
|
return normalizeNumber(entry.count) >= limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildQuotaToastMessage(type: string, quota: UsageQuotaMap, resetAt?: NumericLike): string {
|
export function buildQuotaToastMessage(
|
||||||
|
type: string,
|
||||||
|
quota: UsageQuotaMap,
|
||||||
|
resetAt?: NumericLike
|
||||||
|
): string {
|
||||||
const entry = quota?.[type];
|
const entry = quota?.[type];
|
||||||
const referenceResetAt = typeof resetAt !== 'undefined' ? resetAt : entry?.reset_at;
|
const referenceResetAt = typeof resetAt !== 'undefined' ? resetAt : entry?.reset_at;
|
||||||
return `${quotaTypeLabel(type)} 配额已用完,将在 ${formatResetTime(referenceResetAt)} 重置`;
|
return `${quotaTypeLabel(type)} 配额已用完,将在 ${formatResetTime(referenceResetAt)} 重置`;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user