fix(quickdock): 详情面板补回子智能体 Tokens 显示,列表统一最新在底部
- 详情面板头部恢复上下文 token 显示(数据源=列表轮询自带的 current_context_tokens, 即旧工作区面板被移除前所用字段),格式 x.xk Tokens,快照防关闭动画闪烁 - 子智能体/后台指令列表前端统一按 created_at 升序(后端均返回倒序), 快捷窗口四窗口一致最新条目在底部 - 文件窗口加 hydrated 守卫:移动端↔桌面端重挂时水合渲染一律静态, 修复 editedFilesLive 残留 true 导致全部行重播进入动画
This commit is contained in:
parent
4a8c38d24a
commit
91347d5245
@ -146,11 +146,19 @@ async function playEnter(row: Row, delay = 0) {
|
||||
}, 380);
|
||||
}
|
||||
|
||||
// 首次(重)挂载的水合渲染一律静态:移动端↔桌面端切换时组件随 QuickDock
|
||||
// 整体 v-if 卸载重挂,immediate watch 立即以现有数据触发;此时 editedFilesLive
|
||||
// 可能是任务期 socket 事件残留的 true(socket 是最后一个写入者,之后没有
|
||||
// REST 回填重置它,与待办不同),不拦截会把全部行误判为「运行期新增」重播
|
||||
// 进入动画。只有挂载后 store 的新更新才允许走 live 动画路径。
|
||||
let hydrated = false;
|
||||
|
||||
watch(
|
||||
editedFiles,
|
||||
(list) => {
|
||||
// live=true 来自任务期实时事件(播动画);false 来自加载/bootstrap(静态呈现)
|
||||
const live = quickDock.editedFilesLive;
|
||||
const live = quickDock.editedFilesLive && hydrated;
|
||||
hydrated = true;
|
||||
const byPath = new Map(list.map((item) => [item.path, item]));
|
||||
|
||||
// 移除已消失的行(文件被删除 / 对话切换清空)
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
<span class="qd-detail__dot" :class="`is-${stateClass}`"></span>
|
||||
<span class="qd-detail__title" :title="title">{{ title }}</span>
|
||||
<span class="qd-detail__badge" :class="`is-${stateClass}`">{{ statusText }}</span>
|
||||
<span v-if="tokensText" class="qd-detail__tokens" :title="tokensTitle">{{ tokensText }}</span>
|
||||
<button class="qd-detail__close" title="关闭" @click="close">
|
||||
<svg viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
@ -207,6 +208,51 @@ const statusText = computed(() => {
|
||||
return '已结束';
|
||||
});
|
||||
|
||||
/* ---------------- 子智能体上下文 token(数据源:/api/sub_agents 列表轮询) ---------------- */
|
||||
|
||||
/** 实时 token:列表 5s 轮询携带 current_context_tokens(与被移除的旧工作区面板同一字段) */
|
||||
const liveTokens = computed(() => {
|
||||
const target = effectiveDetail.value;
|
||||
if (!target || target.kind !== 'agent') {
|
||||
return 0;
|
||||
}
|
||||
const agent = subAgentStore.subAgents.find((a) => a.task_id === target.id);
|
||||
const tokens = Number(agent?.current_context_tokens);
|
||||
return Number.isFinite(tokens) && tokens > 0 ? tokens : 0;
|
||||
});
|
||||
/** 关闭离开动画期间列表可能被替换取空,沿用最后快照避免闪烁(同 lastStatus) */
|
||||
const lastTokens = ref(0);
|
||||
watch(
|
||||
liveTokens,
|
||||
(val) => {
|
||||
if (val > 0) {
|
||||
lastTokens.value = val;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const currentTokens = computed(() => liveTokens.value || lastTokens.value);
|
||||
|
||||
/** 单位用 Tokens:>=1000 显示 x.xk Tokens,否则 n Tokens */
|
||||
function formatCtxTokens(tokens: number): string {
|
||||
if (!tokens || tokens <= 0) {
|
||||
return '';
|
||||
}
|
||||
if (tokens >= 1000) {
|
||||
return `${(tokens / 1000).toFixed(1)}k Tokens`;
|
||||
}
|
||||
return `${tokens} Tokens`;
|
||||
}
|
||||
|
||||
const tokensText = computed(() => {
|
||||
if (effectiveDetail.value?.kind !== 'agent') {
|
||||
return '';
|
||||
}
|
||||
return formatCtxTokens(currentTokens.value);
|
||||
});
|
||||
|
||||
const tokensTitle = computed(() => `上下文 ${currentTokens.value.toLocaleString()} tokens`);
|
||||
|
||||
/* ---------------- 子智能体时间线(逻辑借鉴 SubAgentActivityDialog) ---------------- */
|
||||
|
||||
function normalizeStatus(status?: string) {
|
||||
|
||||
@ -552,6 +552,15 @@
|
||||
color: var(--state-danger);
|
||||
}
|
||||
|
||||
.qd-detail__tokens {
|
||||
flex: none;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
color: var(--text-tertiary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.qd-detail__close {
|
||||
flex: none;
|
||||
margin-left: auto;
|
||||
|
||||
@ -68,7 +68,11 @@ export const useBackgroundCommandStore = defineStore('backgroundCommand', {
|
||||
}
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
this.commands = Array.isArray(data.data) ? data.data : [];
|
||||
const commands: BackgroundCommand[] = Array.isArray(data.data) ? data.data : [];
|
||||
// 统一按创建时间升序(最新在末尾):后端返回倒序,
|
||||
// 快捷窗口设计要求最新条目出现在列表底部(与待办/子智能体/文件一致)。
|
||||
commands.sort((a, b) => (Number(a?.created_at) || 0) - (Number(b?.created_at) || 0));
|
||||
this.commands = commands;
|
||||
const activeId = this.activeCommand?.command_id;
|
||||
if (activeId) {
|
||||
const latest = this.commands.find((item) => item.command_id === activeId);
|
||||
|
||||
@ -11,6 +11,7 @@ interface SubAgent {
|
||||
notice_pending?: boolean;
|
||||
display_name?: string;
|
||||
current_context_tokens?: number;
|
||||
created_at?: number | string;
|
||||
}
|
||||
|
||||
interface SubAgentActivityEntry {
|
||||
@ -76,6 +77,12 @@ export const useSubAgentStore = defineStore('subAgent', {
|
||||
const agents = conversationStore.multiAgentMode
|
||||
? (Array.isArray(data.agents) ? data.agents : [])
|
||||
: (Array.isArray(data.data) ? data.data : []);
|
||||
// 统一按创建时间升序(最新在末尾):传统模式后端返回倒序,
|
||||
// 快捷窗口设计要求最新条目出现在列表底部(与待办/后台指令/文件一致)。
|
||||
agents.sort(
|
||||
(a: SubAgent, b: SubAgent) =>
|
||||
(Number(a?.created_at) || 0) - (Number(b?.created_at) || 0)
|
||||
);
|
||||
this.subAgents = agents;
|
||||
const activeTaskId = this.activeAgent?.task_id;
|
||||
if (activeTaskId) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user