CLI(bun 跑源码即生效): - boot:loadBootData 一次性加载模型清单(含 context_window)/个性化默认 (含压缩设置)/path 授权/会话列表;新会话 unshift 标 current - /session:Enter 真实加载(timeline.reset + getSessionHistory 渲染历史文本) - /new:对齐 web /new 页面语义——空对话草稿不立即建会话,首条消息 createTask 不带 cid 由服务端惰性创建,响应回 id 后插入列表标 current - /context + 状态栏:token_update 事件(运行期事件流)+ 打开面板时查询 新端点 GET /api/runtime/sessions/<cid>/token-stats;百分比算法对齐 web InputComposer(自动压缩阈值优先于模型窗口) - /model:Enter = 本地生效 + createTask 带 model_key/run_mode/thinking_mode 覆盖(会话级即时)+ savePersonalization patch 存默认(effort default 存 null) - /path:增删后 savePathAuths 全量提交两组(服务端 deny 校验保留) - /agents /tasks /workflow /rewind:打开时拉真实列表(sub_agents/ background_commands/workflows/versioning-checkpoints) - 发送链路统一 sendWithSettings 携带 /model 生效值(含排队队列) 后端: - 8 端点装饰器 api_login_required → api_login_or_host_token_required (personalization×2 / path-authorization×2 / sub_agents / background_commands / workflows / versioning-checkpoints),web session 行为不变 - headless_app 用 add_url_rule 共享上述视图函数(URL 与 full 一致); sub_agents/background_commands 支持 query 显式传 conversation_id (Bearer 装配的 terminal 无当前对话概念) - RuntimeService 新增 get_session_token_stats;gateway_api 挂 token-stats 路由 - headless 404 兜底:非 /api/ 的 GET 路径回落告知页(对齐 SPA fallback) 验证:tsc ✅ py_compile ✅ 冒烟 6/6 ✅ headless 装配 54 路由 ✅ localconfig 无头实测读到真实配置 ✅;真实服务端到端待用户重启 8091 实机验收 Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
44 lines
1.8 KiB
TypeScript
44 lines
1.8 KiB
TypeScript
// /context 面板:交互区只读展示上下文用量统计
|
||
// 数据:ContextStats 数字原始值(token_update 事件 + 打开时查询双源刷新);
|
||
// 百分比算法对齐 web InputComposer(开自动深度压缩用触发阈值,否则模型上下文窗口)。
|
||
import { RGBA, TextAttributes } from '@opentui/core';
|
||
import { T } from '../components';
|
||
import { PanelFrame } from './frame';
|
||
import { contextUsageLimitFor, formatCompactTokens, type ContextStats } from '../data';
|
||
import { t } from '../i18n';
|
||
|
||
const FG = RGBA.defaultForeground();
|
||
const DIM = TextAttributes.DIM;
|
||
|
||
export function ContextPanel({ width, stats, model }: { width: number; stats: ContextStats; model: string }) {
|
||
const limit = contextUsageLimitFor(model);
|
||
const pct =
|
||
limit > 0 ? Math.max(0, Math.min(100, Math.round((stats.currentTokens / limit) * 100))) : 0;
|
||
const hitRate =
|
||
stats.totalInput > 0 ? `${Math.round((stats.cacheInput / stats.totalInput) * 100)}%` : '—';
|
||
const currentText =
|
||
limit > 0
|
||
? `${formatCompactTokens(stats.currentTokens)} / ${formatCompactTokens(limit)}(${pct}%)`
|
||
: `${formatCompactTokens(stats.currentTokens)} / —`;
|
||
|
||
const rows: Array<[string, string]> = [
|
||
[t('context.current'), currentText],
|
||
[t('context.totalInput'), formatCompactTokens(stats.totalInput)],
|
||
[t('context.totalOutput'), formatCompactTokens(stats.totalOutput)],
|
||
[t('context.cacheInput'), formatCompactTokens(stats.cacheInput)],
|
||
[t('context.cacheHitRate'), hitRate],
|
||
];
|
||
return (
|
||
<PanelFrame title={t('context.title')} width={width}>
|
||
{rows.map(([label, value]) => (
|
||
<box key={label} flexDirection="row">
|
||
<T fg={FG}>{` ${label} `}</T>
|
||
<T fg={FG} attributes={DIM}>
|
||
{value}
|
||
</T>
|
||
</box>
|
||
))}
|
||
</PanelFrame>
|
||
);
|
||
}
|