fix(quickdock): 详情面板补回子智能体 Tokens 显示,列表统一最新在底部

- 详情面板头部恢复上下文 token 显示(数据源=列表轮询自带的 current_context_tokens,
  即旧工作区面板被移除前所用字段),格式 x.xk Tokens,快照防关闭动画闪烁
- 子智能体/后台指令列表前端统一按 created_at 升序(后端均返回倒序),
  快捷窗口四窗口一致最新条目在底部
- 文件窗口加 hydrated 守卫:移动端↔桌面端重挂时水合渲染一律静态,
  修复 editedFilesLive 残留 true 导致全部行重播进入动画
This commit is contained in:
JOJO 2026-07-29 11:45:08 +08:00
parent 4a8c38d24a
commit 91347d5245
5 changed files with 76 additions and 2 deletions

View File

@ -146,11 +146,19 @@ async function playEnter(row: Row, delay = 0) {
}, 380);
}
// QuickDock
// v-if immediate watch editedFilesLive
// socket truesocket
// 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]));
// /

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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) {