@aibolabs/presentation-workbench 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/background-tasks.js +11 -0
- package/bundle.mjs +2 -2
- package/capability.js +7 -5
- package/conversation.js +46 -43
- package/git.js +32 -27
- package/i18n.generated.js +64 -0
- package/i18n.js +7 -0
- package/inspector.js +19 -16
- package/markdown.d.ts +1 -0
- package/markdown.js +23 -1
- package/metadata.js +17 -12
- package/navigation.js +13 -11
- package/package.json +1 -1
- package/rich-text.js +9 -7
- package/timeline-model.d.ts +1 -1
- package/timeline-model.js +10 -8
- package/timeline.js +25 -19
- package/workbench.js +13 -11
package/README.md
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
宿主动作目录转换成视觉树,不引用宿主代码、DOM、框架或业务执行端口。
|
|
5
5
|
|
|
6
6
|
当前提供 `renderNavigation`、`renderConversation`、`renderGit`、`renderInspector`,
|
|
7
|
-
|
|
7
|
+
参数为 `(state, actions, locale?)`。
|
|
8
|
+
`renderCapability(state, actions, renderSemantic, locale?)`
|
|
8
9
|
接收皮肤自己的语义 renderer。
|
|
9
10
|
调用方拥有样式;模块只生成语义结构、可访问性属性及当前目录中的动作绑定。
|
|
10
|
-
0.2.0 随双皮肤 0.3.0 交付导航、会话、Git、Inspector 与能力视图的完整工作台;当前版本 0.2.
|
|
11
|
+
0.2.0 随双皮肤 0.3.0 交付导航、会话、Git、Inspector 与能力视图的完整工作台;当前版本 0.2.4 随双皮肤 0.4.3 交付。
|
|
11
12
|
执行权限、附件状态、诊断、会话绑定、三种布局、草稿以及跨默认呈现的焦点/滚动
|
|
12
13
|
恢复均由宿主提供数据与权限;包只决定呈现。macOS arm64 原生生命周期与
|
|
13
14
|
Chromium 浏览器交互已有验收记录,未宣称其他 OS 或所有屏幕阅读器通过认证。
|
|
@@ -61,3 +62,12 @@ Shift/Ctrl+Enter 换行。没有对应动作时回车不提交,输入法组合
|
|
|
61
62
|
|
|
62
63
|
桌面会话历史与编辑区独立滚动,附件详情可展开;默认视图与外置视图按消息标识
|
|
63
64
|
恢复位置。外置核心语义不冒充内置专业阅读,存在专业偏好时显示通用降级提示。
|
|
65
|
+
|
|
66
|
+
界面语言由宿主 `PresentationInput.locale` 提供,缺省按简体中文兼容旧宿主。
|
|
67
|
+
导航、布局、时间线、执行权限与 Markdown 操作显式接收语言参数;原始用户、Agent、
|
|
68
|
+
路径、代码和插件字段保持原文。语言不会改变 key、动作 token 或上下文。
|
|
69
|
+
独立皮肤的控件与语义视图同样使用这个字段。会话 Composer、Git、Inspector 与能力页也接入相同词条及语言参数。
|
|
70
|
+
|
|
71
|
+
`i18n.generated.js` 从仓库的规范词条和纯翻译器打包生成,使 npm tarball 无需读取
|
|
72
|
+
仓库其他目录;不是手工维护的第二份词条。修改规范词条后运行
|
|
73
|
+
`node scripts/build-presentation-i18n.mjs`,测试检查生成产物同步。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {presentationTranslator} from './i18n.js';
|
|
2
|
+
export const backgroundTaskLabels = {running:'运行中',completed:'已完成',failed:'失败',stopped:'已停止',unknown:'状态未知'};
|
|
3
|
+
export function parseBackgroundTask(item,locale='zh-CN') {
|
|
4
|
+
if (item.toolName !== 'background_task') return null;
|
|
5
|
+
try {
|
|
6
|
+
const task = JSON.parse(item.content);
|
|
7
|
+
if (!task || !['id','rootTurnId','name','command','activity'].every(key => typeof task[key] === 'string') || !Object.hasOwn(backgroundTaskLabels, task.status)) return null;
|
|
8
|
+
if (task.status === 'running' && item.status !== 'streaming') return {...task,status:'unknown',activity:presentationTranslator(locale)('background.disconnected')};
|
|
9
|
+
return task;
|
|
10
|
+
} catch { return null; }
|
|
11
|
+
}
|
package/bundle.mjs
CHANGED
|
@@ -4,9 +4,9 @@ import {fileURLToPath} from 'node:url';
|
|
|
4
4
|
/** Bundle the fixed workbench entry and installed dependencies for the Worker. */
|
|
5
5
|
export async function workbenchSource() {
|
|
6
6
|
const result = await build({
|
|
7
|
-
stdin: {contents: "export {renderWorkbench} from './workbench.js';", resolveDir: fileURLToPath(new URL('.', import.meta.url))},
|
|
7
|
+
stdin: {contents: "export {renderWorkbench} from './workbench.js';export {presentationTranslator} from './i18n.js';", resolveDir: fileURLToPath(new URL('.', import.meta.url))},
|
|
8
8
|
bundle: true, write: false, format: 'iife', globalName: 'aiboWorkbenchModule',
|
|
9
9
|
platform: 'browser', target: 'es2022', minify: true,
|
|
10
10
|
});
|
|
11
|
-
return result.outputFiles[0].text + '\nself.aiboWorkbench=aiboWorkbenchModule.renderWorkbench;\n';
|
|
11
|
+
return result.outputFiles[0].text + '\nself.aiboWorkbench=aiboWorkbenchModule.renderWorkbench;self.aiboTranslate=aiboWorkbenchModule.presentationTranslator;\n';
|
|
12
12
|
}
|
package/capability.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
import {presentationTranslator} from './i18n.js';
|
|
1
2
|
import {node,button,section,text,actionFor} from './tree.js';
|
|
2
3
|
/** The skin supplies its semantic renderer; only host-bound semantic actions are forwarded. */
|
|
3
|
-
export function renderCapability(state,actions,renderSemantic){
|
|
4
|
+
export function renderCapability(state,actions,renderSemantic,locale='zh-CN'){
|
|
5
|
+
const t=presentationTranslator(locale);
|
|
4
6
|
const find=(operation,...args)=>actionFor(actions,operation,...args);
|
|
5
7
|
const controls=[];
|
|
6
|
-
for(const [operation,label] of [['close','
|
|
8
|
+
for(const [operation,label] of [['close',t('external.closeCapabilityView')],['reload',t('workbench.reload')],['toggleLayout',t('workbench.changeLayout')],['toggleReading',state.view.enhanced?t('workbench.genericReading'):t('external.trySpecialized')]]){const action=find(operation);if(action)controls.push(button('capability:'+operation,label,action));}
|
|
7
9
|
const children=[node('nav','capability:catalog',null,state.catalog.map(item=>button('capability:open:'+item.installationId+':'+item.contributionId,item.title,find('open',item.installationId,item.contributionId),{title:item.issue??item.title})))];
|
|
8
10
|
if(state.selected){
|
|
9
|
-
children.push(node('h2','capability:selected-title',state.selected.title),node('nav','capability:controls',null,controls),text('capability:error',state.view.error),state.view.restoring?text('capability:restoring','
|
|
11
|
+
children.push(node('h2','capability:selected-title',state.selected.title),node('nav','capability:controls',null,controls),text('capability:error',state.view.error),state.view.restoring?text('capability:restoring',t('external.restoringView')):null);
|
|
10
12
|
if(state.view.snapshot){
|
|
11
13
|
const snapshot=state.view.snapshot;
|
|
12
|
-
if(snapshot.view.kind==='detail'&&state.view.enhanced)children.push(node('p','capability:reading-fallback','
|
|
14
|
+
if(snapshot.view.kind==='detail'&&state.view.enhanced)children.push(node('p','capability:reading-fallback',t('workbench.genericFallback'),[],{role:'status'}));
|
|
13
15
|
const semanticActions=actions.filter(action=>action.operation==='semantic').map(entry=>{const action=JSON.parse(entry.args[0]);return {token:entry.token,action,label:snapshot.actions.find(item=>item.id===action.actionId)?.label??action.actionId};});
|
|
14
16
|
children.push(renderSemantic({snapshot,actions:semanticActions}));
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
|
-
return section('capability','
|
|
19
|
+
return section('capability',t('external.capabilityView'),children);
|
|
18
20
|
}
|
package/conversation.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
import {presentationTranslator} from './i18n.js';
|
|
2
|
+
import {formatNumber} from './i18n.generated.js';
|
|
1
3
|
import {splitMessageAttachments} from './message-attachments.js';
|
|
2
4
|
import {node,button,field,section,text,actionFor} from './tree.js';
|
|
3
5
|
import {renderExecutionProfile,renderMessageAttachment,renderSessionMetadata} from './metadata.js';
|
|
4
6
|
import {splitSessionReferences} from './session-references.js';
|
|
5
7
|
import {renderTimeline} from './timeline.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
export function renderConversation(state,actions,locale='zh-CN'){
|
|
9
|
+
const t=presentationTranslator(locale);
|
|
10
|
+
const labels={pauseGoal:t('goal.pause'),resumeGoal:t('goal.resume'),clearGoal:t('goal.clear'),send:t('composer.send'),stop:t('history.stop'),retry:t('app.retry'),queueSteer:t('composer.sendNow'),queueFollowUp:t('composer.queue'),clearQueue:t('external.clearQueue'),resumeQueue:t('queue.resume'),addAttachments:t('external.addAttachments'),addDirectory:t('composer.addFolder'),loadOlder:t('external.loadOlderMessages'),fork:t('external.forkSession'),loadModels:t('external.refreshModels'),compact:t('commands.compact'),openTree:t('tree.title'),closeTree:t('tree.close'),refreshTree:t('external.refreshTree'),submitAnswers:t('input.submit'),cancelAnswers:t('external.cancelAnswers')};
|
|
8
11
|
const find=(operation,...args)=>actionFor(actions,operation,...args);
|
|
9
12
|
const controls=(operations)=>operations.flatMap(operation=>{const action=actions.find(a=>a.operation===operation&&!a.args.length);return action?[button('conversation:action:'+operation,labels[operation],action)]:[]});
|
|
10
|
-
const children=[node('header','conversation:header',null,[node('h1','conversation:title',state.session?.label??'
|
|
11
|
-
if(state.session?.historyOnly)children.push(text('conversation:history-only','
|
|
12
|
-
children.push(renderSessionMetadata(state.session,'conversation:session-metadata'));
|
|
13
|
-
const messages=renderTimeline(state.timelineVisibleCount>0?state.timeline.slice(-state.timelineVisibleCount):[],actions,state.groupSystemItems===true,state.attachments);
|
|
14
|
-
children.push({...node('section','conversation:timeline',null,[...controls(['loadOlder']),...messages],{'aria-label':'
|
|
15
|
-
if(state.retryReason||state.retryPrompt)children.push(section('conversation:retry','
|
|
13
|
+
const children=[node('header','conversation:header',null,[node('h1','conversation:title',state.session?.label??t('external.selectSession')),text('conversation:activity',state.activityLabel),node('nav','conversation:tools',null,controls(['fork','compact','openTree']))])];
|
|
14
|
+
if(state.session?.historyOnly)children.push(text('conversation:history-only',t('composer.historyOnly')));
|
|
15
|
+
children.push(renderSessionMetadata(state.session,'conversation:session-metadata',locale));
|
|
16
|
+
const messages=renderTimeline(state.timelineVisibleCount>0?state.timeline.slice(-state.timelineVisibleCount):[],actions,state.groupSystemItems===true,state.attachments,locale);
|
|
17
|
+
children.push({...node('section','conversation:timeline',null,[...controls(['loadOlder']),...messages],{'aria-label':t('external.sessionMessages')}),className:'timeline'});
|
|
18
|
+
if(state.retryReason||state.retryPrompt)children.push(section('conversation:retry',t('app.retry'),[text('retry:reason',state.retryReason),text('retry:prompt',state.retryPrompt),...controls(['retry'])]));
|
|
16
19
|
for(const request of state.userInputRequests){
|
|
17
20
|
const questions=request.questions.map(question=>{
|
|
18
21
|
const prefix='question:'+request.requestId+':'+question.id;
|
|
@@ -23,65 +26,65 @@ export function renderConversation(state,actions){
|
|
|
23
26
|
});
|
|
24
27
|
return section(prefix,question.header??question.question,[question.header?text(prefix+':prompt',question.question):null,...options,(question.isOther||!question.options.length)?field(prefix+':answer',question.question,value,find('answer',request.requestId,question.id),true):null]);
|
|
25
28
|
});
|
|
26
|
-
children.push(section('request:'+request.requestId,'
|
|
29
|
+
children.push(section('request:'+request.requestId,t('external.needAnswers'),[...questions,...['submitAnswers','cancelAnswers'].map(operation=>{const action=find(operation,request.requestId);return action?button('request:'+request.requestId+':'+operation,labels[operation],action):null;})]));
|
|
27
30
|
}
|
|
28
31
|
for(const request of (state.approvalRequests??[]).filter(request=>request.sessionId===state.session?.id)){
|
|
29
32
|
const prefix='approval:'+request.requestId;
|
|
30
33
|
// Reject kinds come first, matching the default workbench; decisions answer providers without options.
|
|
31
|
-
const choices=request.options.length?[...request.options].sort((a,b)=>a.kind===b.kind?0:a.kind==='reject'?-1:1).map(option=>['option',option.id,option.label??(option.kind==='allow'?'
|
|
32
|
-
children.push(section(prefix,'
|
|
34
|
+
const choices=request.options.length?[...request.options].sort((a,b)=>a.kind===b.kind?0:a.kind==='reject'?-1:1).map(option=>['option',option.id,option.label??(option.kind==='allow'?t('approval.allow'):t('approval.reject'))]):request.availableDecisions.map(decision=>['decision',decision,decision==='accept'?t('approval.allow'):t('approval.reject')]);
|
|
35
|
+
children.push(section(prefix,t('approval.required'),[text(prefix+':kind',request.kind),request.command?text(prefix+':command',request.command):null,request.cwd?text(prefix+':cwd',request.cwd):null,
|
|
33
36
|
...choices.map(([kind,value,label])=>{const action=find('resolveApproval',request.requestId,kind,value);return action?button(prefix+':'+kind+':'+value,label,action):null;})]));
|
|
34
37
|
}
|
|
35
|
-
if(state.queue && (state.queue.items?.length || state.queue.steering.length || state.queue.followUp.length))children.push(section('conversation:queue','
|
|
36
|
-
state.queue.paused?text('queue:paused','
|
|
37
|
-
...(state.queue.items?.length?state.queue.items.map(item=>section('queue:item:'+item.id,item.status==='sending'?'
|
|
38
|
+
if(state.queue && (state.queue.items?.length || state.queue.steering.length || state.queue.followUp.length))children.push(section('conversation:queue',t('external.pendingMessages'),[
|
|
39
|
+
state.queue.paused?text('queue:paused',t('queue.paused')):null,
|
|
40
|
+
...(state.queue.items?.length?state.queue.items.map(item=>section('queue:item:'+item.id,item.status==='sending'?t('external.sendingMessage'):item.status==='uncertain'?t('external.deliveryUnknown'):item.status==='failed'?t('queue.failed'):t('external.waitingToSend'),[
|
|
38
41
|
text('queue:text:'+item.id,splitSessionReferences(item.text).body.split('[AIBO_CONTEXT_ATTACHMENTS]')[0].trim()),
|
|
39
42
|
...splitMessageAttachments(item.text,state.attachments).attachments.map(attachment=>renderMessageAttachment(attachment,'queue:'+item.id+':attachment:'+attachment.id)),
|
|
40
43
|
item.error?text('queue:error:'+item.id,item.error):null,
|
|
41
|
-
(!state.running || state.session?.capabilities.includes('queue.steer')) ? button('queue:send:'+item.id,'
|
|
42
|
-
button('queue:remove:'+item.id,'
|
|
43
|
-
])):[...state.queue.steering.map((value,i)=>text('queue:steer:'+i,'
|
|
44
|
+
(!state.running || state.session?.capabilities.includes('queue.steer')) ? button('queue:send:'+item.id,t('composer.sendNow'),find('sendQueuedMessage',item.id)) : null,
|
|
45
|
+
button('queue:remove:'+item.id,t('git.deleted'),find('removeQueuedMessage',item.id)),
|
|
46
|
+
])):[...state.queue.steering.map((value,i)=>text('queue:steer:'+i,t('external.steeringMessage',{text:value}))),...state.queue.followUp.map((value,i)=>text('queue:follow:'+i,t('external.followUpMessage',{text:value})))]),
|
|
44
47
|
...controls(['resumeQueue','clearQueue'])]));
|
|
45
48
|
if(state.session){
|
|
46
|
-
const draftField=field('conversation:draft','
|
|
49
|
+
const draftField=field('conversation:draft',t('external.message'),state.draft,find('draft'),true);
|
|
47
50
|
const submit=find(state.running?'queueFollowUp':'send');
|
|
48
51
|
draftField.children[1].submitOnEnter=true;
|
|
49
52
|
if(submit)draftField.children[1].primaryEnter=submit.token;
|
|
50
53
|
const goal = state.goal && state.goal.status !== 'cleared' ? {...node('details','conversation:goal',null,[
|
|
51
54
|
node('summary','goal:summary',state.goal.objective),
|
|
52
|
-
text('goal:status',state.goal.status === 'active' ? (state.running ? '
|
|
53
|
-
text('goal:budget',state.goal.tokenBudget == null ? null :
|
|
55
|
+
text('goal:status',state.goal.status === 'active' ? (state.running ? t('goal.status.activeRunning') : t('goal.status.activeIdle')) : state.goal.status === 'paused' && state.running ? t('goal.status.pausedRunning') : ({paused:t('goal.status.paused'),completed:t('goal.status.completed'),blocked:t('goal.status.blocked'),usageLimited:t('goal.status.usageLimited'),budgetLimited:t('goal.status.budgetLimited')})[state.goal.status] ?? t('goal.status.unknown')),
|
|
56
|
+
text('goal:budget',state.goal.tokenBudget == null ? null : t('external.goalBudget',{used:formatNumber(locale,state.goal.tokensUsed??0),budget:formatNumber(locale,state.goal.tokenBudget)})),
|
|
54
57
|
...controls(['pauseGoal','resumeGoal','clearGoal']),
|
|
55
|
-
],{open:true,'aria-label':'
|
|
56
|
-
const composer=[goal,draftField,text('conversation:shortcut','
|
|
58
|
+
],{open:true,'aria-label':t('goal.current')}),className:'goal-bar'} : null;
|
|
59
|
+
const composer=[goal,draftField,text('conversation:shortcut',t('external.composerShortcut',{action:state.running?t('composer.queue'):t('composer.send')})),state.draftFailed?node('p','conversation:draft-error',t('external.draftSaveFailed'),[],{role:'alert'}):null];
|
|
57
60
|
|
|
58
61
|
composer.push(node('nav','conversation:composer-tools',null,controls(['addAttachments','addDirectory','send','stop','queueSteer','queueFollowUp'])));
|
|
59
62
|
const pendingAttachments=state.attachments.filter(item=>item.turnId===null && item.sessionId===state.session.id);
|
|
60
|
-
const attachmentList=node('ul','conversation:attachment-list',null,pendingAttachments.map(item=>node('li','attachment:'+item.id,null,[renderMessageAttachment(item,'composer:attachment:'+item.id),button('attachment:remove:'+item.id,'
|
|
61
|
-
if(pendingAttachments.length)composer.push(node('details','conversation:attachments',null,[node('summary','conversation:attachments:summary','
|
|
63
|
+
const attachmentList=node('ul','conversation:attachment-list',null,pendingAttachments.map(item=>node('li','attachment:'+item.id,null,[renderMessageAttachment(item,'composer:attachment:'+item.id),button('attachment:remove:'+item.id,t('external.removeAttachment',{name:item.displayName??item.path}),find('removeAttachment',item.id))])));
|
|
64
|
+
if(pendingAttachments.length)composer.push(node('details','conversation:attachments',null,[node('summary','conversation:attachments:summary',t('external.attachmentsCount',{count:pendingAttachments.length})),attachmentList],{open:true}));
|
|
62
65
|
const mention=/(?:^|\s)@[^\s]*$/.test(state.draft);
|
|
63
66
|
const slash=state.draft.match(/^\/([^\s]*)$/);
|
|
64
67
|
const categoryOf=command=>command.category??(command.source==='skill'?'skill':command.source==='extension'||command.source==='prompt'?'extension':'agent');
|
|
65
68
|
const matches=slash?state.agentCommands.filter(command=>command.enabled!==false&&[command.name,...(command.aliases??[]),command.description??''].join(' ').toLocaleLowerCase().includes(slash[1].toLocaleLowerCase())):state.agentCommands;
|
|
66
|
-
const groups=slash?[
|
|
69
|
+
const groups=slash?['all','agent','skill','extension'].map(id=>[id,t('composer.category.'+id)]).map(([id,label])=>({id,label,commands:matches.filter(command=>id==='all'||categoryOf(command)===id)})):[];
|
|
67
70
|
const commands=slash?[...new Map(groups.flatMap(group=>group.commands).map(command=>[command.name,command])).values()]:matches;
|
|
68
71
|
const pathButtons=state.workspacePathSuggestions.slice(0,24).map(item=>button('path:'+item.path,item.path+(item.isDirectory?'/':''),find('selectPath',item.path),{role:'option'}));
|
|
69
72
|
const commandButtons=commands.map(command=>button('command:'+command.name,'/'+command.name,find('selectCommand',command.name),{title:command.description??command.name,role:slash?'option':'button'}));
|
|
70
|
-
const sessionButtons=(state.sessionSuggestions??[]).map(item=>button('session-reference:'+item.id,'
|
|
71
|
-
const mentionGroups=mention?[
|
|
73
|
+
const sessionButtons=(state.sessionSuggestions??[]).map(item=>button('session-reference:'+item.id,t('external.sessionSuggestion',{label:item.label,agent:item.agent,archived:item.archived?t('external.archivedSuffix'):''}),find('selectSessionReference',item.id),{role:'option'}));
|
|
74
|
+
const mentionGroups=mention?['all','files','folders','sessions'].map(id=>[id,t('composer.category.'+id)]).map(([id,label])=>({id,label,buttons:id==='sessions'?sessionButtons:id==='files'?pathButtons.filter((_,index)=>!state.workspacePathSuggestions[index].isDirectory):id==='folders'?pathButtons.filter((_,index)=>state.workspacePathSuggestions[index].isDirectory):[...sessionButtons,...pathButtons]})) : [];
|
|
72
75
|
const mentionButtons=mention?[...new Map(mentionGroups.flatMap(group=>group.buttons).map(item=>[item.key,item])).values()]:[];
|
|
73
|
-
if(mention)composer.push(node('div','conversation:mentions',null,[node('div','mentions:categories',null,mentionGroups.map(group=>node('button','mentions:category:'+group.id,group.label+' ('+group.buttons.length+')',[],{type:'button','aria-pressed':String(group.id==='all')})),{role:'group','aria-label':'
|
|
74
|
-
if(slash||state.agentCommands.length)composer.push(slash?node('div','conversation:commands',null,[node('div','commands:categories',null,groups.map(group=>node('button','commands:category:'+group.id,group.label+' ('+group.commands.length+')',[],{type:'button','aria-pressed':String(group.id==='all')})),{role:'group','aria-label':'
|
|
76
|
+
if(mention)composer.push(node('div','conversation:mentions',null,[node('div','mentions:categories',null,mentionGroups.map(group=>node('button','mentions:category:'+group.id,group.label+' ('+group.buttons.length+')',[],{type:'button','aria-pressed':String(group.id==='all')})),{role:'group','aria-label':t('composer.referenceCategories')}),node('div','conversation:mention-options',null,mentionButtons,{role:'listbox','aria-label':t('composer.referenceSuggestions')})],{role:'group','aria-label':t('external.mentionCompletion')}));
|
|
77
|
+
if(slash||state.agentCommands.length)composer.push(slash?node('div','conversation:commands',null,[node('div','commands:categories',null,groups.map(group=>node('button','commands:category:'+group.id,group.label+' ('+group.commands.length+')',[],{type:'button','aria-pressed':String(group.id==='all')})),{role:'group','aria-label':t('composer.commandCategories')}),node('div','conversation:command-options',null,commandButtons,{role:'listbox','aria-label':t('composer.commandSuggestions')})],{role:'group','aria-label':t('external.commandCompletion')}):node('details','conversation:commands',null,[node('summary','commands:title',t('inspector.command')),...commandButtons]));
|
|
75
78
|
const suggestionButtons=mention?mentionButtons:slash?commandButtons:[];
|
|
76
79
|
if(suggestionButtons.length||mention||slash&&groups.length)draftField.children[1].suggestions={listKey:mention?'conversation:mentions':'conversation:commands',keys:suggestionButtons.filter(button=>button.events?.click).map(button=>button.key),confirmWithTab:false,confirmWithPrimary:mention,...(mention?{categories:mentionGroups.map(group=>({key:'mentions:category:'+group.id,options:group.buttons.filter(button=>button.events?.click).map(button=>button.key)}))}:slash?{categories:groups.map(group=>({key:'commands:category:'+group.id,options:group.commands.filter(command=>find('selectCommand',command.name)).map(command=>'command:'+command.name)}))}:{})};
|
|
77
|
-
const matrixModels=(state.modelCatalog?.models??[]).map(model=>section('model:'+model.reference,model.label,[text('model:description:'+model.reference,model.description),button('model:default:'+model.reference,'
|
|
80
|
+
const matrixModels=(state.modelCatalog?.models??[]).map(model=>section('model:'+model.reference,model.label,[text('model:description:'+model.reference,model.description),button('model:default:'+model.reference,t('external.defaultReasoning'),find('selectModel',model.reference,null),{'aria-pressed':String(state.modelCatalog?.current?.reference===model.reference&&!state.modelConfiguration.selectedReasoningEffort)}),...model.reasoningEfforts.map(effort=>button('model:effort:'+model.reference+':'+effort.id,effort.label,find('selectModel',model.reference,effort.id),{'aria-pressed':String(state.modelCatalog?.current?.reference===model.reference&&state.modelConfiguration.selectedReasoningEffort===effort.id),title:effort.description??effort.label}))]));
|
|
78
81
|
const sequential=state.modelCatalog?.parameterScope==='current-model';
|
|
79
82
|
const currentModel=state.modelCatalog?.current;
|
|
80
83
|
const currentEfforts=currentModel?.reasoningEfforts??[];
|
|
81
84
|
const selectedEffort=currentEfforts.find(option=>option.id===state.modelConfiguration.currentReasoningEffort);
|
|
82
85
|
const models=sequential?[
|
|
83
|
-
section('models:selection','
|
|
84
|
-
section('models:reasoning','
|
|
86
|
+
section('models:selection',t('composer.model'),(state.modelCatalog?.models??[]).map(model=>button('model:select:'+model.reference,model.label,find('selectModel',model.reference,null),{'aria-pressed':String(currentModel?.reference===model.reference),title:model.description??model.label}))),
|
|
87
|
+
section('models:reasoning',t('composer.reasoning'),currentEfforts.length?currentEfforts.map(effort=>button('model:effort:'+currentModel.reference+':'+effort.id,effort.label,find('selectModel',currentModel.reference,effort.id),{'aria-pressed':String(state.modelConfiguration.selectedReasoningEffort===effort.id),title:effort.description??effort.label})):[text('models:no-reasoning',state.modelCatalogLoading?t('composer.updating'):t('composer.noReasoning'))]),
|
|
85
88
|
]:matrixModels;
|
|
86
89
|
const fastTier=state.modelCatalog?.current?.serviceTiers?.find(tier=>tier.label?.trim().toLowerCase()==='fast');
|
|
87
90
|
const fastAction=fastTier?find('selectServiceTier',state.modelCatalog?.currentServiceTier===fastTier.id?'default':fastTier.id):null;
|
|
@@ -89,26 +92,26 @@ export function renderConversation(state,actions){
|
|
|
89
92
|
const contextOptions=state.modelCatalog?.current?.contextWindows??[];
|
|
90
93
|
const contextCurrent=state.modelCatalog?.currentContextWindow;
|
|
91
94
|
const contextKnown=contextOptions.some(option=>option.id===contextCurrent);
|
|
92
|
-
const contextSelect=node('label','models:context-label',null,[text('models:context-title','
|
|
93
|
-
...node('select','models:context',null,[...(!contextKnown?[node('option','models:context:unknown',contextOptions.length?'
|
|
95
|
+
const contextSelect=node('label','models:context-label',null,[text('models:context-title',t('side.context')),{
|
|
96
|
+
...node('select','models:context',null,[...(!contextKnown?[node('option','models:context:unknown',contextOptions.length?t('context.noCurrent'):t('context.unsupported'),[],{value:'',disabled:true,selected:true})]:[]),...contextOptions.map(option=>node('option','models:context:'+option.id,option.label,[],{value:option.id,selected:option.id===contextCurrent}))],{'aria-label':t('context.size'),disabled:!contextAction,value:contextKnown?contextCurrent:''}),
|
|
94
97
|
...(contextAction?{events:{change:contextAction.token}}:{}),
|
|
95
98
|
}]);
|
|
96
|
-
composer.push(node('details','conversation:models' ,null,[node('summary','models:title',(currentModel?.label??'
|
|
97
|
-
composer.push(node('details','conversation:access',null,[node('summary','access:title','
|
|
99
|
+
composer.push(node('details','conversation:models' ,null,[node('summary','models:title',(currentModel?.label??t('composer.model'))+(selectedEffort?' · '+selectedEffort.label:'')),...controls(['loadModels']),fastAction?button('models:fast','⚡ '+fastTier.label,fastAction,{'aria-pressed':String(state.modelCatalog?.currentServiceTier===fastTier.id),title:fastTier.description??fastTier.label}):null,contextSelect,state.modelCatalogLoading?text('models:loading',t('external.loadingModels')):null,...models]));
|
|
100
|
+
composer.push(node('details','conversation:access',null,[node('summary','access:title',t('composer.sessionSettings')),...actions.filter(a=>a.operation==='selectAccess').map(action=>button('access:'+action.args[0],state.executionProfile?.sessionControls?.find(option=>option.id===action.args[0])?.label??action.args[0],action)),renderExecutionProfile(state.executionProfile,'conversation:execution-profile',locale)]));
|
|
98
101
|
if(state.usage){
|
|
99
102
|
const usage=state.usage;
|
|
100
103
|
const percent=usage.contextUsed!==null&&usage.contextLimit?Math.min(100,Math.round(usage.contextUsed/usage.contextLimit*100)):null;
|
|
101
|
-
const limits=(usage.limits??[]).map(limit=>(limit.label??(limit.windowMinutes?limit.windowMinutes
|
|
102
|
-
const credits=usage.credits?.unlimited?'
|
|
103
|
-
composer.push(node('p','conversation:usage',[percent===null?usage.contextUsed===null?null:'
|
|
104
|
+
const limits=[...(usage.limits??[]),...(usage.unknownLimits??[]).map(limit=>({...limit,usedPercent:null}))].map(limit=>t('external.quotaLine',{label:limit.label??(limit.windowMinutes?t('external.minutes',{count:limit.windowMinutes}):t('usage.plan')),amount:limit.usedPercent===null?t('usage.unknownQuota'):t('external.quotaRemaining',{percent:Math.max(0,100-Math.round(limit.usedPercent))}),observation:limit.observedAt!==undefined?t(limit.resetsAt===null?'external.lastObservedUnknownReset':'external.lastObserved'):''}));
|
|
105
|
+
const credits=usage.credits?.unlimited?t('usage.unlimitedCredits'):usage.credits?.balance?'Credits '+usage.credits.balance:null;
|
|
106
|
+
composer.push(node('p','conversation:usage',[percent===null?usage.contextUsed===null?null:t('external.contextTokens',{count:formatNumber(locale,usage.contextUsed)}):t('external.contextPercent',{percent,estimated:usage.contextEstimated?t('usage.estimatedDetail'):''}),usage.total===null?null:'Token '+usage.total,usage.plan?.toUpperCase(),...limits,credits].filter(Boolean).join(' · '),[],{'aria-label':t('usage.accessibility')}));
|
|
104
107
|
}
|
|
105
|
-
children.push(section('conversation:composer','
|
|
108
|
+
children.push(section('conversation:composer',t('external.composeMessage'),composer));
|
|
106
109
|
}
|
|
107
110
|
if(state.treeOpen){
|
|
108
111
|
const branch=nodes=>nodes.map(item=>node('li','tree:'+item.id,null,[button('tree:select:'+item.id,item.label??item.summary??item.type,find('selectTreeNode',item.id),{'aria-current':state.tree?.leafId===item.id?'true':'false'}),node('ul','tree:children:'+item.id,null,branch(item.children))]));
|
|
109
|
-
children.push(section('conversation:tree','
|
|
112
|
+
children.push(section('conversation:tree',t('tree.title'),[...controls(['closeTree','refreshTree']),text('tree:status',state.treeNavigationStatus),node('ul','tree:roots',null,branch(state.tree?.tree??[]))]));
|
|
110
113
|
}
|
|
111
114
|
const composer=children.find(child=>child?.key==='conversation:composer');
|
|
112
|
-
const history={...node('section','conversation:history',null,children.filter(child=>child?.key!=='conversation:composer'),{'aria-label':'
|
|
113
|
-
return {...node('main','conversation',null,[history,composer],{'aria-label':'
|
|
115
|
+
const history={...node('section','conversation:history',null,children.filter(child=>child?.key!=='conversation:composer'),{'aria-label':t('search.sessionHistory'),tabindex:'0'}),className:'conversation-history'};
|
|
116
|
+
return {...node('main','conversation',null,[history,composer],{'aria-label':t('scope.session')}),className:'conversation'};
|
|
114
117
|
}
|
package/git.js
CHANGED
|
@@ -1,59 +1,64 @@
|
|
|
1
|
+
import {presentationTranslator} from './i18n.js';
|
|
2
|
+
import {formatDateTime} from './i18n.generated.js';
|
|
1
3
|
import {node,button,field,section,text,actionFor} from './tree.js';
|
|
2
|
-
export function renderGit(state,actions){
|
|
4
|
+
export function renderGit(state,actions,locale='zh-CN'){
|
|
5
|
+
const t=presentationTranslator(locale);
|
|
6
|
+
const kinds={added:t('git.added'),modified:t('git.modified'),deleted:t('git.deleted'),renamed:t('git.renamed')};
|
|
7
|
+
const kindLabel=kind=>Object.hasOwn(kinds,kind)?kinds[kind]:kind;
|
|
3
8
|
const find=(operation,...args)=>actionFor(actions,operation,...args);
|
|
4
9
|
const act=(operation,label,...args)=>{const action=find(operation,...args);return action?button('git:action:'+operation+':'+JSON.stringify(args),label,action):null;};
|
|
5
10
|
const children=[];
|
|
6
11
|
const repositories=state.repositories;
|
|
7
12
|
const current=repositories?.find(repo=>repo.id===state.repositoryId);
|
|
8
13
|
if(repositories){
|
|
9
|
-
children.push(text('git:repository-target',current?`${current.name} · ${current.relativePath}${current.externalRoot?'
|
|
10
|
-
if(repositories.length>1||state.repositoryId===null)children.push(node('details','git:repository-picker',null,[node('summary','git:repository-picker-title',current?'
|
|
11
|
-
if(state.discoveryLimited)children.push(text('git:discovery-limited','
|
|
14
|
+
children.push(text('git:repository-target',current?`${current.name} · ${current.relativePath}${current.externalRoot?t('git.externalRootSuffix'):''}`:t('repository.all')));
|
|
15
|
+
if(repositories.length>1||state.repositoryId===null)children.push(node('details','git:repository-picker',null,[node('summary','git:repository-picker-title',current?t('external.switchRepository'):t('external.selectRepositoryHint')),field('git:repository-search',t('external.searchRepositories'),state.repositorySearch,find('repositorySearch')),act('selectRepository',t('repository.all'),null),...repositories.filter(repo=>`${repo.name} ${repo.relativePath}`.toLowerCase().includes((state.repositorySearch??'').toLowerCase())).map(repo=>act('selectRepository',`${repo.name} · ${repo.relativePath}`,repo.id))],state.repositoryPickerOpen?{open:true}:{}));
|
|
16
|
+
if(state.discoveryLimited)children.push(text('git:discovery-limited',t('git.discoveryLimited')),act('continueDiscovery',t('git.continueDiscovery')));
|
|
12
17
|
for(const [index,warning] of (state.discoveryWarnings??[]).entries())children.push(text('git:discovery-warning:'+index,warning));
|
|
13
18
|
}
|
|
14
19
|
if(repositories&&state.repositoryId===null){
|
|
15
|
-
children.push(act('refresh','
|
|
16
|
-
if(state.loading)children.push(node('p','git:loading','
|
|
17
|
-
if(!repositories.length&&!state.loading)children.push(text('git:no-repositories','
|
|
20
|
+
children.push(act('refresh',t('external.refreshRepositories')),act('selectSection',t('git.history'),'history'),text('git:error',state.error));
|
|
21
|
+
if(state.loading)children.push(node('p','git:loading',t('external.loadingRepositories'),[],{role:'status'}));
|
|
22
|
+
if(!repositories.length&&!state.loading)children.push(text('git:no-repositories',t('external.noRepositories')));
|
|
18
23
|
const group=repo=>{
|
|
19
24
|
const prefix='git:repository:'+repo.id;
|
|
20
25
|
const groupAct=(...args)=>{const result=act(...args);return result?{...result,key:prefix+':'+result.key}:null;};
|
|
21
26
|
const files=repo.changes?.files??[];
|
|
22
|
-
const content=[groupAct('toggleRepository',`${repo.name} · ${repo.changes?.branch??'
|
|
27
|
+
const content=[groupAct('toggleRepository',`${repo.name} · ${repo.changes?.branch??t('git.detachedHead')} · ${files.length}`,repo.id),text(prefix+':path',repo.relativePath+(repo.kind==='submodule'?t('git.submoduleSuffix'):repo.kind==='worktree'?t('git.worktreeSuffix'):'')+(repo.externalRoot?t('git.externalRootSuffix'):''))];
|
|
23
28
|
if(!(state.collapsedRepositories??[]).includes(repo.id)){
|
|
24
29
|
content.push(text(prefix+':error',repo.error??repo.changes?.captureError));
|
|
25
|
-
if(!repo.changes&&!repo.error)content.push(text(prefix+':loading','
|
|
26
|
-
for(const [scope,title] of [['staged','
|
|
30
|
+
if(!repo.changes&&!repo.error)content.push(text(prefix+':loading',t('app.loadingChanges')));
|
|
31
|
+
for(const [scope,title] of [['staged',t('external.stagedChanges')],['unstaged',t('git.changed')]]){
|
|
27
32
|
const entries=files.filter(file=>scope==='staged'?file.staged&&!file.conflicted:file.unstaged||file.untracked||file.conflicted);
|
|
28
|
-
if(entries.length)content.push(section(prefix+':'+scope,title,entries.map(file=>node('div',prefix+':'+scope+':'+file.path,null,[text(prefix+':'+scope+':path:'+file.path,`${file.untracked?'?':file.conflicted?'!':file.kind} ${file.path}`),groupAct('repositoryDiff','
|
|
33
|
+
if(entries.length)content.push(section(prefix+':'+scope,title,entries.map(file=>node('div',prefix+':'+scope+':'+file.path,null,[text(prefix+':'+scope+':path:'+file.path,`${file.untracked?'?':file.conflicted?'!':kindLabel(file.kind)} ${file.path}`),groupAct('repositoryDiff',t('semantic.inspect'),repo.id,file.path,scope),groupAct(scope==='staged'?'repositoryUnstage':'repositoryStage',scope==='staged'?t('git.unstage'):t('git.stage'),repo.id,file.path)]))));
|
|
29
34
|
}
|
|
30
|
-
content.push(groupAct('repositoryStageAll','
|
|
35
|
+
content.push(groupAct('repositoryStageAll',t('git.stageAll'),repo.id),groupAct('repositoryUnstageAll',t('git.unstageEverything'),repo.id),groupAct('selectRepository',files.some(file=>file.staged)?t('git.commitEllipsis'):t('git.openRepository'),repo.id));
|
|
31
36
|
}
|
|
32
|
-
return node('section',prefix,null,content,{'aria-label'
|
|
37
|
+
return node('section',prefix,null,content,{'aria-label':t('external.repositoryLabel',{name:repo.name,path:repo.relativePath})});
|
|
33
38
|
};
|
|
34
39
|
const clean=repositories.filter(repo=>!repo.error&&repo.changes?.captureStatus==='captured'&&!repo.changes.files.length);
|
|
35
40
|
children.push(...repositories.filter(repo=>!clean.includes(repo)).map(group));
|
|
36
|
-
if(clean.length)children.push(node('details','git:clean-repositories',null,[node('summary','git:clean-repositories-title'
|
|
41
|
+
if(clean.length)children.push(node('details','git:clean-repositories',null,[node('summary','git:clean-repositories-title',t('external.cleanRepositories',{count:clean.length})),...clean.map(group)]));
|
|
37
42
|
}else{
|
|
38
|
-
const single=[node('nav','git:tools',null,[act('refresh','
|
|
39
|
-
if(state.changes?.captureStatus&&state.changes.captureStatus!=='captured')single.push(node('p','git:capture-error',state.changes.captureError??'
|
|
40
|
-
if(state.changes?.captureStatus==='captured'&&!state.changes.files.length)single.push(text('git:clean','
|
|
41
|
-
if(state.loading||state.metadataLoading)single.push(node('p','git:loading','
|
|
43
|
+
const single=[node('nav','git:tools',null,[act('refresh',t('app.refreshChanges')),act('refreshMetadata',t('external.refreshHistory')),act('requestReview',t('external.requestReview')),act('selectSection',t('app.workspaceChanges'),'changes'),act('selectSection',t('git.history'),'history')]),text('git:branch',state.changes?.branch),text('git:error',state.error),text('git:metadata-error',state.metadataError)];
|
|
44
|
+
if(state.changes?.captureStatus&&state.changes.captureStatus!=='captured')single.push(node('p','git:capture-error',state.changes.captureError??t('external.gitStateUnavailable'),[],{role:'status'}));
|
|
45
|
+
if(state.changes?.captureStatus==='captured'&&!state.changes.files.length)single.push(text('git:clean',t('external.cleanWorkspace')));
|
|
46
|
+
if(state.loading||state.metadataLoading)single.push(node('p','git:loading',t('external.loadingGit'),[],{role:'status'}));
|
|
42
47
|
if(state.draft.gitSection==='changes'){
|
|
43
|
-
single.push(node('ul','git:files',null,(state.changes?.files??[]).map(file=>node('li','git:file:'+file.path,null,[text('git:path:'+file.path,file.path),text('git:kind:'+file.path,`${file.kind}${file.conflicted?'
|
|
44
|
-
single.push(act('stageAll','
|
|
48
|
+
single.push(node('ul','git:files',null,(state.changes?.files??[]).map(file=>node('li','git:file:'+file.path,null,[text('git:path:'+file.path,file.path),text('git:kind:'+file.path,`${kindLabel(file.kind)}${file.conflicted?t('external.conflictSuffix'):''}${file.staged?t('external.stagedSuffix'):''}${file.untracked?t('external.untrackedSuffix'):''}`),act('openDiff',t('external.viewStagedDiff'),file.path,'staged'),act('openDiff',t('external.viewWorkingDiff'),file.path,'unstaged'),act('stageFile',t('git.stage'),file.path),act('unstageFile',t('git.unstage'),file.path)]))));
|
|
49
|
+
single.push(act('stageAll',t('git.stageAll')),act('unstageAll',t('git.unstageEverything')),field('git:commit-message',current?t('external.commitTarget',{name:current.name,branch:state.changes?.branch??t('git.detachedHead')}):t('external.commitMessage'),state.draft.commitMessage,find('commitMessage'),true),act('commit',t('execution.operation.git.commit')));
|
|
45
50
|
}else{
|
|
46
|
-
single.push(node('ul','git:history',null,state.history.map(commit=>node('li','git:commit:'+commit.hash,null,[act('selectCommit',`${commit.shortHash} ${commit.subject}`,commit.hash),text('git:author:'+commit.hash,`${commit.author} · ${commit.authoredAt}`)]))));
|
|
51
|
+
single.push(node('ul','git:history',null,state.history.map(commit=>node('li','git:commit:'+commit.hash,null,[act('selectCommit',`${commit.shortHash} ${commit.subject}`,commit.hash),text('git:author:'+commit.hash,`${commit.author} · ${formatDateTime(locale,commit.authoredAt,{dateStyle:'medium',timeStyle:'short'})}`)]))));
|
|
47
52
|
if(state.historyLoadMoreError)single.push(node('p','git:history-more-error',state.historyLoadMoreError,[],{role:'alert'}));
|
|
48
|
-
single.push(state.historyLoadingMore?text('git:history-more-loading','
|
|
49
|
-
if(state.commitFiles)single.push(node('ul','git:commit-files',null,state.commitFiles.files.map(file=>node('li','git:commit-file:'+file.path,null,[act('openCommitDiff',file.path,state.commitFiles.commit,file.path)]))),act('loadMoreCommitFiles','
|
|
53
|
+
single.push(state.historyLoadingMore?text('git:history-more-loading',t('git.loadingMoreCommits')):state.historyHasMore?act('loadMoreHistory',t('git.loadMoreCommits')):null);
|
|
54
|
+
if(state.commitFiles)single.push(node('ul','git:commit-files',null,state.commitFiles.files.map(file=>node('li','git:commit-file:'+file.path,null,[act('openCommitDiff',file.path,state.commitFiles.commit,file.path)]))),act('loadMoreCommitFiles',t('external.loadMoreFiles'),state.commitFiles.commit));
|
|
50
55
|
}
|
|
51
|
-
single.push(node('details','git:branches',null,[node('summary','git:branches-title','
|
|
52
|
-
if(state.remoteStatus)single.push(section('git:remote','
|
|
53
|
-
single.push(node('details','git:stashes',null,[node('summary','git:stash-title','
|
|
56
|
+
single.push(node('details','git:branches',null,[node('summary','git:branches-title',t('external.branches')),field('git:branch-draft',t('git.newBranch'),state.draft.branchDraft,find('branchDraft')),act('createBranch',t('execution.operation.git.create-branch')),...state.branches.map(branch=>node('p','git:branch:'+branch.name,null,[text('git:branch-name:'+branch.name,branch.name+(branch.current?t('external.currentSuffix'):'')),act('checkoutBranch',t('execution.operation.git.checkout'),branch.name)]))]));
|
|
57
|
+
if(state.remoteStatus)single.push(section('git:remote',t('external.remote'),[text('git:upstream',state.remoteStatus.upstream),text('git:distance',t('external.remoteDistance',{ahead:state.remoteStatus.ahead,behind:state.remoteStatus.behind})),act('fetch',t('external.fetch')),act('pull',t('git.pull')),act('push',t('git.push'))]));
|
|
58
|
+
single.push(node('details','git:stashes',null,[node('summary','git:stash-title',t('git.stash')),act('saveStash',t('external.saveStash')),...state.stashes.map(stash=>node('p','git:stash:'+stash.reference,null,[text('git:stash-message:'+stash.reference,stash.message),act('applyStash',t('external.applyStash',{reference:stash.reference}),stash.reference)]))]));
|
|
54
59
|
children.push(...single);
|
|
55
60
|
}
|
|
56
61
|
const preview=state.preview;
|
|
57
|
-
if(preview.selectedPath||preview.fileDiff||preview.error||preview.loading)children.push(section('git:preview',preview.contextLabel??preview.selectedPath??'
|
|
62
|
+
if(preview.selectedPath||preview.fileDiff||preview.error||preview.loading)children.push(section('git:preview',preview.contextLabel??preview.selectedPath??t('external.diffPreview'),[act('closeDiff',t('external.closeDiff')),preview.loading?text('git:preview-loading',t('external.loadingDiff')):null,text('git:preview-error',preview.error),text('git:preview-reason',preview.fileDiff?.reason),preview.fileDiff?node('pre','git:preview-content',preview.fileDiff.diff,[],{tabindex:'0'}):null,preview.fileDiff?.truncated?text('git:preview-truncated',t('external.diffTruncated')):null]));
|
|
58
63
|
return section('git','Git',children);
|
|
59
64
|
}
|