Skip to content

Commit 78f5980

Browse files
committed
feat(web): add context asset picker
1 parent 5547209 commit 78f5980

3 files changed

Lines changed: 122 additions & 7 deletions

File tree

apps/web/static/app.js

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ let reportsCache = [];
3939
let reportFilterState = {q:'', status:'all', type:'all'};
4040
let activeReportId = '';
4141
let commandAssetsLoaded = false;
42+
let contextPackAssetFilterState = {q:'', type:'all'};
4243
const CONTEXT_PACK_STORAGE_KEY = 'dap_context_pack_v1';
4344
let contextPack = loadContextPack();
4445

@@ -137,7 +138,7 @@ function compactTags(values,limit=4){
137138
return list.length?list.map(v=>tag(v)).join(''):'<span class="muted">暂无标签</span>';
138139
}
139140
function defaultContextPack(){
140-
return {name:'默认工作包',instructions:'',agentId:'',datasetIds:[],reportIds:[],traceIds:[],sessionId:'',toolMode:'auto',evidenceDepth:'standard',updatedAt:''};
141+
return {name:'默认工作包',instructions:'',agentId:'',datasetIds:[],knowledgeBaseIds:[],reportIds:[],traceIds:[],sessionId:'',toolMode:'auto',evidenceDepth:'standard',updatedAt:''};
141142
}
142143
function normalizeIdList(list,limit=6){
143144
return [...new Set(asList(list).map(v=>String(v||'').trim()).filter(Boolean))].slice(0,limit);
@@ -149,6 +150,7 @@ function normalizeContextPack(pack={}){
149150
next.instructions=String(next.instructions||'').slice(0,1200);
150151
next.agentId=String(next.agentId||'').slice(0,80);
151152
next.datasetIds=normalizeIdList(next.datasetIds,6);
153+
next.knowledgeBaseIds=normalizeIdList(next.knowledgeBaseIds,6);
152154
next.reportIds=normalizeIdList(next.reportIds,4);
153155
next.traceIds=normalizeIdList(next.traceIds,6);
154156
next.sessionId=String(next.sessionId||'').slice(0,90);
@@ -167,7 +169,7 @@ function loadContextPack(){
167169
}
168170
function contextPackHasContent(pack=contextPack){
169171
const p=normalizeContextPack(pack);
170-
return Boolean(p.instructions.trim()||p.agentId||p.datasetIds.length||p.reportIds.length||p.traceIds.length||p.sessionId);
172+
return Boolean(p.instructions.trim()||p.agentId||p.datasetIds.length||p.knowledgeBaseIds.length||p.reportIds.length||p.traceIds.length||p.sessionId);
171173
}
172174
function asList(value){
173175
if(Array.isArray(value)) return value;
@@ -184,6 +186,7 @@ function assetName(list,id,fallback='-'){
184186
}
185187
function datasetName(id){return assetName(datasets,id,id||'-')}
186188
function metricName(id){return assetName(metrics,id,id||'-')}
189+
function knowledgeBaseName(id){return assetName(knowledgeBasesCache,id,id||'-')}
187190
function cellHtml(key,value){
188191
if(value===null || value===undefined || value==='') return '<span class="muted">-</span>';
189192
if(['status','risk','risk_level','severity','mode','data_classification','refresh_mode'].includes(key)) return statusTag(value);
@@ -1074,6 +1077,11 @@ function openReportCommand(reportId){
10741077
showPage('reports');
10751078
setTimeout(()=>openReportDetail(reportId,null),180);
10761079
}
1080+
function openKnowledgeCommand(kbId){
1081+
knowledgeFilterState={q:'',backend:'all',type:'all'};
1082+
showPage('knowledge');
1083+
setTimeout(()=>selectKnowledgeBase(kbId),180);
1084+
}
10771085
function openSessionCommand(sessionId){
10781086
showPage('chat');
10791087
setTimeout(()=>loadChatSession(sessionId),180);
@@ -1085,6 +1093,7 @@ function buildCommandItems(q){
10851093
items.push(...agents.map(a=>({kind:'agent',title:a.name,description:`试用 ${displayValue(a.type)} · ${displayValue(a.risk_level||'low')}`,keywords:[a.id,a.type,a.description,a.adapter_id].join(' '),run:()=>openAgentCommand(a.id)})));
10861094
items.push(...datasets.map(d=>({kind:'dataset',title:d.name,description:`打开数据画像 · ${d.physical_table||d.id}`,keywords:[d.id,d.business_domain,d.description,d.data_classification].join(' '),run:()=>openDatasetCommand(d.id)})));
10871095
items.push(...metrics.map(m=>({kind:'metric',title:m.name,description:`解释指标口径 · ${m.code||m.id}`,keywords:[m.id,m.code,m.formula,m.dataset_id].join(' '),run:()=>openMetricCommand(m)})));
1096+
items.push(...knowledgeBasesCache.slice(0,80).map(k=>({kind:'knowledge',title:k.name||k.id,description:`打开知识库 · ${displayValue(k.backend_type||'mock')} · ${displayValue(k.type||'document')}`,keywords:[k.id,k.name,k.backend_type,k.type,k.adapter_id,k.description].join(' '),run:()=>openKnowledgeCommand(k.id)})));
10881097
items.push(...reportsCache.slice(0,80).map(r=>({kind:'report_asset',title:r.title||r.id,description:`打开报告 Canvas · ${reportTypeLabel(r.report_type)} · ${displayValue(r.status||'draft')}`,keywords:[r.id,r.report_type,r.status,r.owner_id,r.agent_id,r.created_at,r.updated_at].join(' '),run:()=>openReportCommand(r.id)})));
10891098
items.push(...chatSessions.slice(0,80).map(s=>({kind:'session',title:sessionTitle(s),description:`恢复会话 · ${displayValue(s.status||'active')} · ${timeText(s.updated_at)}`,keywords:[s.id,s.title,s.agent_id,s.status,s.created_at,s.updated_at].join(' '),run:()=>openSessionCommand(s.id)})));
10901099
const prompts=['本月收入最高的渠道有哪些?','按区域统计本月收入','客户工单根因分布是什么?','解释收入指标口径','给我生成一个经营总览面板','帮我创建一个 Codex 任务,开发面板导出功能'];
@@ -1444,14 +1453,15 @@ function contextPackCounts(){
14441453
const p=normalizeContextPack(contextPack);
14451454
return {
14461455
datasets:p.datasetIds.length,
1456+
knowledge:p.knowledgeBaseIds.length,
14471457
reports:p.reportIds.length,
14481458
traces:p.traceIds.length,
14491459
instructions:p.instructions.trim()?1:0
14501460
};
14511461
}
14521462
function contextPackSummaryLabel(){
14531463
const counts=contextPackCounts();
1454-
const total=counts.datasets+counts.reports+counts.traces+counts.instructions+(contextPack.agentId?1:0)+(contextPack.sessionId?1:0);
1464+
const total=counts.datasets+counts.knowledge+counts.reports+counts.traces+counts.instructions+(contextPack.agentId?1:0)+(contextPack.sessionId?1:0);
14551465
return total?`${total} 项上下文`:'未捕获';
14561466
}
14571467
function contextPackSummaryDetail(){
@@ -1460,6 +1470,7 @@ function contextPackSummaryDetail(){
14601470
if(counts.instructions) parts.push('指令');
14611471
if(contextPack.agentId) parts.push('Agent');
14621472
if(counts.datasets) parts.push(`${counts.datasets} 数据集`);
1473+
if(counts.knowledge) parts.push(`${counts.knowledge} 知识库`);
14631474
if(counts.reports) parts.push(`${counts.reports} 报告`);
14641475
if(counts.traces) parts.push(`${counts.traces} Trace`);
14651476
if(contextPack.sessionId) parts.push('会话');
@@ -1477,21 +1488,90 @@ function contextPackPills(){
14771488
const pills=[];
14781489
if(p.agentId) pills.push(['Agent',contextPackAgentName()]);
14791490
p.datasetIds.forEach(id=>pills.push(['数据集',datasetName(id)]));
1491+
p.knowledgeBaseIds.forEach(id=>pills.push(['知识库',knowledgeBaseName(id)]));
14801492
p.reportIds.forEach(id=>pills.push(['报告',contextPackReportTitle(id)]));
14811493
p.traceIds.forEach(id=>pills.push(['Trace',id]));
14821494
if(p.sessionId) pills.push(['会话',p.sessionId]);
1483-
return pills.length?`<div class="context-pack-pills">${pills.map(([label,value])=>`<span><small>${esc(label)}</small><b>${esc(short(value,34))}</b></span>`).join('')}</div>`:`<p class="context-pack-empty">捕获当前会话后,这里会显示 Agent、数据集、Trace、报告和会话线索。</p>`;
1495+
return pills.length?`<div class="context-pack-pills">${pills.map(([label,value])=>`<span><small>${esc(label)}</small><b>${esc(short(value,34))}</b></span>`).join('')}</div>`:`<p class="context-pack-empty">捕获当前会话或添加资产后,这里会显示 Agent、数据集、知识库、Trace、报告和会话线索。</p>`;
14841496
}
14851497
function contextPackStatusText(){
14861498
return contextPack.updatedAt?`已更新 ${timeText(contextPack.updatedAt)} · 本地浏览器工作包`:'本地浏览器工作包 · 未写入服务端';
14871499
}
1500+
function contextPackAssetItems(){
1501+
const q=(contextPackAssetFilterState.q||'').trim().toLowerCase();
1502+
const type=contextPackAssetFilterState.type||'all';
1503+
const items=[
1504+
...datasets.map(d=>({
1505+
kind:'dataset',
1506+
label:'数据集',
1507+
id:d.id,
1508+
title:d.name||d.id,
1509+
detail:[d.business_domain,d.physical_table,displayValue(d.data_classification||'internal')].filter(Boolean).join(' · '),
1510+
keywords:[d.id,d.name,d.physical_table,d.business_domain,d.description,d.data_classification].join(' ')
1511+
})),
1512+
...knowledgeBasesCache.map(k=>({
1513+
kind:'knowledge',
1514+
label:'知识库',
1515+
id:k.id,
1516+
title:k.name||k.id,
1517+
detail:[displayValue(k.backend_type||'mock'),displayValue(k.type||'document'),k.adapter_id].filter(Boolean).join(' · '),
1518+
keywords:[k.id,k.name,k.backend_type,k.type,k.adapter_id,k.description].join(' ')
1519+
})),
1520+
...reportsCache.slice(0,80).map(r=>({
1521+
kind:'report',
1522+
label:'报告',
1523+
id:r.id,
1524+
title:r.title||r.id,
1525+
detail:[reportTypeLabel(r.report_type),displayValue(r.status||'draft'),timeText(r.updated_at||r.created_at)].filter(Boolean).join(' · '),
1526+
keywords:[r.id,r.title,r.report_type,r.status,r.owner_id,r.agent_id,r.created_at,r.updated_at].join(' ')
1527+
}))
1528+
];
1529+
return items
1530+
.filter(item=>(type==='all'||item.kind===type)&&(!q||[item.id,item.title,item.detail,item.keywords].join(' ').toLowerCase().includes(q)))
1531+
.slice(0,8);
1532+
}
1533+
function contextPackAssetInPack(kind,id,pack=normalizeContextPack(contextPack)){
1534+
if(kind==='dataset') return pack.datasetIds.includes(id);
1535+
if(kind==='knowledge') return pack.knowledgeBaseIds.includes(id);
1536+
if(kind==='report') return pack.reportIds.includes(id);
1537+
return false;
1538+
}
1539+
function contextPackAssetListHtml(){
1540+
const items=contextPackAssetItems();
1541+
const pack=normalizeContextPack(contextPack);
1542+
if(!items.length) return emptyState('没有匹配资产','换一个关键词或资产类型。');
1543+
return `<div class="context-asset-list">${items.map(item=>{
1544+
const selected=contextPackAssetInPack(item.kind,item.id,pack);
1545+
return `<article class="context-asset-item ${selected?'selected':''}">
1546+
<div><small>${esc(item.label)}</small><b>${esc(short(item.title,38))}</b><span>${esc(short(item.detail||item.id,54))}</span></div>
1547+
<button class="report-action ${selected?'muted-action':''}" ${selected?'disabled':''} onclick="addContextAssetToPack('${jsArg(item.kind)}','${jsArg(item.id)}',this)">${selected?'已加入':'加入'}</button>
1548+
</article>`;
1549+
}).join('')}</div>`;
1550+
}
1551+
function renderContextPackAssetPicker(){
1552+
const type=contextPackAssetFilterState.type||'all';
1553+
return `<div class="context-pack-picker">
1554+
<div class="context-pack-filter">
1555+
<label><span>资产</span><input id="contextPackAssetSearch" value="${esc(contextPackAssetFilterState.q||'')}" placeholder="搜索数据集、知识库、报告" oninput="contextPackAssetFilterState.q=this.value;renderContextPackAssetList()"/></label>
1556+
<label><span>类型</span><select id="contextPackAssetType" onchange="contextPackAssetFilterState.type=this.value;renderContextPackAssetList()">
1557+
${[['all','全部'],['dataset','数据集'],['knowledge','知识库'],['report','报告']].map(([value,label])=>`<option value="${value}" ${type===value?'selected':''}>${label}</option>`).join('')}
1558+
</select></label>
1559+
</div>
1560+
<div id="contextPackAssetList">${contextPackAssetListHtml()}</div>
1561+
</div>`;
1562+
}
1563+
function renderContextPackAssetList(){
1564+
const list=document.getElementById('contextPackAssetList');
1565+
if(list) list.innerHTML=contextPackAssetListHtml();
1566+
}
14881567
function renderContextPackPanel(){
14891568
const active=contextPackHasContent();
14901569
return `<section id="contextPackPanel" class="context-pack-card ${active?'active':''}" aria-label="工作包">
14911570
<div class="context-pack-head"><div><span>Context Pack</span><b>${esc(contextPack.name||'默认工作包')}</b></div>${active?tag('active','green'):tag('empty')}</div>
14921571
<label class="field-label" for="contextPackInstructions">工作指令</label>
14931572
<textarea id="contextPackInstructions" rows="4" placeholder="写下这组工作要长期遵循的口径、范围或偏好。" oninput="updateContextPackInstructions(this.value)">${esc(contextPack.instructions)}</textarea>
14941573
${contextPackPills()}
1574+
${renderContextPackAssetPicker()}
14951575
<div class="context-pack-actions">
14961576
<button class="report-action" onclick="captureContextPack()">捕获当前</button>
14971577
<button class="report-action" onclick="applyContextPackToChat()">应用</button>
@@ -1554,6 +1634,19 @@ function addReportToContextPack(reportId,btn){
15541634
persistContextPack({toast:'报告已加入工作包'});
15551635
setBusy(btn,false);
15561636
}
1637+
function addKnowledgeToContextPack(knowledgeBaseId,btn){
1638+
if(!knowledgeBaseId) return;
1639+
setBusy(btn,true);
1640+
contextPack.knowledgeBaseIds=normalizeIdList([knowledgeBaseId,...contextPack.knowledgeBaseIds],6);
1641+
contextPack.updatedAt=new Date().toISOString();
1642+
persistContextPack({toast:'知识库已加入工作包'});
1643+
setBusy(btn,false);
1644+
}
1645+
function addContextAssetToPack(kind,id,btn){
1646+
if(kind==='dataset') return addDatasetToContextPack(id,btn);
1647+
if(kind==='knowledge') return addKnowledgeToContextPack(id,btn);
1648+
if(kind==='report') return addReportToContextPack(id,btn);
1649+
}
15571650
function addTraceToContextPack(traceId,btn){
15581651
if(!traceId) return;
15591652
setBusy(btn,true);
@@ -1577,6 +1670,7 @@ function contextPackPrompt(){
15771670
if(p.instructions.trim()) lines.push('', `工作指令:${p.instructions.trim()}`);
15781671
if(p.agentId) lines.push(`Agent:${contextPackAgentName()||p.agentId}`);
15791672
if(p.datasetIds.length) lines.push(`数据集:${p.datasetIds.map(datasetName).join('、')}`);
1673+
if(p.knowledgeBaseIds.length) lines.push(`知识库:${p.knowledgeBaseIds.map(knowledgeBaseName).join('、')}`);
15801674
if(p.reportIds.length) lines.push(`报告:${p.reportIds.map(id=>short(contextPackReportTitle(id),42)).join('、')}`);
15811675
if(p.traceIds.length) lines.push(`Trace:${p.traceIds.join('、')}`);
15821676
if(p.sessionId) lines.push(`来源会话:${p.sessionId}`);
@@ -1589,6 +1683,7 @@ function contextPackCanvasMarkdown(){
15891683
if(p.instructions.trim()) lines.push('## 工作指令',p.instructions.trim(),'');
15901684
if(p.agentId) lines.push('## Agent',`- ${contextPackAgentName()||p.agentId}`,'');
15911685
if(p.datasetIds.length) lines.push('## 数据集',...p.datasetIds.map(id=>`- ${datasetName(id)} (${id})`),'');
1686+
if(p.knowledgeBaseIds.length) lines.push('## 知识库',...p.knowledgeBaseIds.map(id=>`- ${knowledgeBaseName(id)} (${id})`),'');
15921687
if(p.reportIds.length) lines.push('## 报告',...p.reportIds.map(id=>`- ${contextPackReportTitle(id)} (${id})`),'');
15931688
if(p.traceIds.length) lines.push('## Trace',...p.traceIds.map(id=>`- ${id}`),'');
15941689
if(p.sessionId) lines.push('## 来源会话',`- ${p.sessionId}`,'');
@@ -1601,6 +1696,7 @@ function contextPackPayload(){
16011696
instructions:p.instructions,
16021697
agent_id:p.agentId||null,
16031698
dataset_ids:p.datasetIds,
1699+
knowledge_base_ids:p.knowledgeBaseIds,
16041700
report_ids:p.reportIds,
16051701
trace_ids:p.traceIds,
16061702
session_id:p.sessionId||null,
@@ -1940,12 +2036,14 @@ async function refreshChatSessions(){
19402036
renderSessionList();
19412037
}
19422038
async function refreshCommandAssets(){
1943-
const [sessions,reports]=await Promise.all([
2039+
const [sessions,reports,knowledgeBases]=await Promise.all([
19442040
api('/api/sessions').catch(()=>chatSessions),
1945-
api('/api/reports').catch(()=>reportsCache)
2041+
api('/api/reports').catch(()=>reportsCache),
2042+
api('/api/knowledge-bases').catch(()=>knowledgeBasesCache)
19462043
]);
19472044
chatSessions=sessions||[];
19482045
reportsCache=reports||[];
2046+
knowledgeBasesCache=knowledgeBases||[];
19492047
commandAssetsLoaded=true;
19502048
}
19512049
async function updateChatSession(id,payload,btn){
@@ -2946,6 +3044,7 @@ function knowledgeDetailHtml(kb,versions=[]){
29463044
const actions=contextActionStrip([
29473045
{label:'用知识库提问',onclick:`setChatDraft('${jsArg(`基于知识库“${kbName}”回答:请说明它适合支撑哪些业务问题,并给出可追问方向。`)}','${jsArg(preferredAgent)}')`},
29483046
{label:'查看语义资产',onclick:`openSemanticFiltered('${jsArg(kbName)}','')`},
3047+
{label:'加入工作包',onclick:`addKnowledgeToContextPack('${jsArg(kb.id)}',this)`},
29493048
{label:'查看审计',onclick:`openAuditFiltered('${jsArg(kb.id)}')`},
29503049
{label:'创建接入任务',onclick:`setCodexDraft('${jsArg(`完善知识库接入:${kbName}`)}','${jsArg(`检查知识库 ${kb.id} 的 Adapter 绑定、版本展示、Agent 引用和审计链路,保持权限、Trace 和外部知识源边界不退化。`)}')`}
29513050
]);

apps/web/static/styles.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,19 @@ button.ghost,.prompt-pill{background:#fff;color:#2d2d2a;border-color:#e1ded6}
624624
.context-pack-pills small{font-size:10px;color:#7d786d;font-weight:900;text-transform:uppercase}
625625
.context-pack-pills b{font-size:12px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
626626
.context-pack-empty{margin:0;color:#7d786d;font-size:12px;line-height:1.45}
627+
.context-pack-picker{display:grid;gap:8px;border-top:1px solid var(--line);padding-top:9px}
628+
.context-pack-filter{display:grid;grid-template-columns:minmax(0,1fr) 92px;gap:7px;align-items:end}
629+
.context-pack-filter label{display:grid;gap:4px;min-width:0}
630+
.context-pack-filter span{color:#7d786d;font-size:10px;font-weight:900;text-transform:uppercase}
631+
.context-pack-filter input,.context-pack-filter select{min-height:34px;border-radius:12px;font-size:12px;background:#fff}
632+
.context-asset-list{display:grid;gap:6px;max-height:282px;overflow:auto;padding-right:2px}
633+
.context-asset-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;background:#fff;border:1px solid var(--line);border-radius:13px;padding:8px;min-width:0}
634+
.context-asset-item.selected{background:#f6fbf7;border-color:#cfe8dc}
635+
.context-asset-item div{display:grid;gap:2px;min-width:0}
636+
.context-asset-item small{color:#7d786d;font-size:10px;font-weight:900;text-transform:uppercase}
637+
.context-asset-item b{font-size:12px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
638+
.context-asset-item span{color:#6b6b62;font-size:11px;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
639+
.context-asset-item .report-action{min-width:58px;padding:6px 8px;font-size:11px}
627640
.context-pack-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}
628641
.context-pack-actions .report-action{min-width:0;padding:7px 8px;border-radius:999px;font-size:12px}
629642
.context-pack-status{color:#7d786d;line-height:1.4}
@@ -700,6 +713,9 @@ button.ghost,.prompt-pill{background:#fff;color:#2d2d2a;border-color:#e1ded6}
700713
.chat-context-bar{grid-template-columns:1fr}
701714
.composer-tools{grid-template-columns:1fr}
702715
.context-pack-actions{grid-template-columns:1fr 1fr}
716+
.context-pack-filter{grid-template-columns:1fr}
717+
.context-asset-item{grid-template-columns:1fr}
718+
.context-asset-item .report-action{width:100%}
703719
.canvas-select-tools{grid-template-columns:1fr 1fr}
704720
.canvas-select-tools span{grid-column:1 / -1;white-space:normal}
705721
.canvas-actions{grid-template-columns:1fr 1fr}

0 commit comments

Comments
 (0)