Skip to content

Commit c53ff23

Browse files
committed
feat(web): add context pack presets
1 parent ca76e44 commit c53ff23

3 files changed

Lines changed: 101 additions & 1 deletion

File tree

apps/web/static/app.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ let activeReportId = '';
4444
let commandAssetsLoaded = false;
4545
let contextPackAssetFilterState = {q:'', type:'all'};
4646
const CONTEXT_PACK_STORAGE_KEY = 'dap_context_pack_v1';
47+
const CONTEXT_PACK_PRESETS_STORAGE_KEY = 'dap_context_pack_presets_v1';
4748
let contextPack = loadContextPack();
49+
let contextPackPresets = loadContextPackPresets();
50+
let activeContextPackPresetId = '';
4851

4952
async function api(path, opts={}){
5053
const headers = Object.assign({'Content-Type':'application/json'}, opts.headers || {});
@@ -172,6 +175,32 @@ function loadContextPack(){
172175
return defaultContextPack();
173176
}
174177
}
178+
function contextPackPresetId(){
179+
return `pack_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,7)}`;
180+
}
181+
function normalizeContextPackPreset(preset={}){
182+
const pack=normalizeContextPack(preset.pack||preset);
183+
const name=short(String(preset.name||pack.name||'工作包预设').trim()||'工作包预设',40);
184+
pack.name=name;
185+
return {
186+
id:String(preset.id||contextPackPresetId()).slice(0,80),
187+
name,
188+
pack,
189+
updatedAt:String(preset.updatedAt||pack.updatedAt||new Date().toISOString()).slice(0,40)
190+
};
191+
}
192+
function loadContextPackPresets(){
193+
try{
194+
const stored=localStorage.getItem(CONTEXT_PACK_PRESETS_STORAGE_KEY);
195+
const list=stored?JSON.parse(stored):[];
196+
return Array.isArray(list)?list.map(normalizeContextPackPreset).slice(0,12):[];
197+
}catch(e){
198+
return [];
199+
}
200+
}
201+
function persistContextPackPresets(){
202+
try{localStorage.setItem(CONTEXT_PACK_PRESETS_STORAGE_KEY,JSON.stringify(contextPackPresets));}catch(e){}
203+
}
175204
function contextPackHasContent(pack=contextPack){
176205
const p=normalizeContextPack(pack);
177206
return Boolean(p.instructions.trim()||p.agentId||p.datasetIds.length||p.knowledgeBaseIds.length||p.reportIds.length||p.traceIds.length||p.sessionId||(p.includeCanvas&&chatCanvasValue().trim()));
@@ -1752,6 +1781,19 @@ function contextPackPills(){
17521781
function contextPackStatusText(){
17531782
return contextPack.updatedAt?`已更新 ${timeText(contextPack.updatedAt)} · 本地浏览器工作包`:'本地浏览器工作包 · 未写入服务端';
17541783
}
1784+
function renderContextPackPresetBar(){
1785+
const options=contextPackPresets.map(p=>`<option value="${esc(p.id)}" ${p.id===activeContextPackPresetId?'selected':''}>${esc(p.name)} · ${esc(timeText(p.updatedAt))}</option>`).join('');
1786+
return `<div class="context-pack-presets" aria-label="工作包预设">
1787+
<label><span>项目预设</span><select id="contextPackPresetSelect" onchange="activeContextPackPresetId=this.value">
1788+
<option value="">选择本地预设</option>${options}
1789+
</select></label>
1790+
<div>
1791+
<button type="button" onclick="saveContextPackPreset(this)">保存</button>
1792+
<button type="button" onclick="loadContextPackPreset()">加载</button>
1793+
<button type="button" onclick="deleteContextPackPreset(this)">删除</button>
1794+
</div>
1795+
</div>`;
1796+
}
17551797
function contextPackAssetItems(){
17561798
const q=(contextPackAssetFilterState.q||'').trim().toLowerCase();
17571799
const type=contextPackAssetFilterState.type||'all';
@@ -1824,6 +1866,7 @@ function renderContextPackPanel(){
18241866
const p=normalizeContextPack(contextPack);
18251867
return `<section id="contextPackPanel" class="context-pack-card ${active?'active':''}" aria-label="工作包">
18261868
<div class="context-pack-head"><div><span>Context Pack</span><b>${esc(contextPack.name||'默认工作包')}</b></div>${active?tag('active','green'):tag('empty')}</div>
1869+
${renderContextPackPresetBar()}
18271870
<label class="field-label" for="contextPackInstructions">工作指令</label>
18281871
<textarea id="contextPackInstructions" rows="4" placeholder="写下这组工作要长期遵循的口径、范围或偏好。" oninput="updateContextPackInstructions(this.value)">${esc(contextPack.instructions)}</textarea>
18291872
<div class="context-pack-memory" aria-label="工作包记忆边界">
@@ -1860,6 +1903,54 @@ function persistContextPack(opts={}){
18601903
}
18611904
if(opts.toast) toast(opts.toast);
18621905
}
1906+
function selectedContextPackPreset(){
1907+
const select=document.getElementById('contextPackPresetSelect');
1908+
const id=select?.value || activeContextPackPresetId || '';
1909+
return contextPackPresets.find(p=>p.id===id)||null;
1910+
}
1911+
function saveContextPackPreset(btn){
1912+
setBusy(btn,true);
1913+
try{
1914+
const existing=selectedContextPackPreset();
1915+
const baseName=existing?.name || contextPack.name || '默认工作包';
1916+
const name=window.prompt(existing?'更新工作包预设名称':'保存工作包预设',baseName);
1917+
if(name===null) return;
1918+
const clean=short(name.trim(),40);
1919+
if(!clean) return toast('预设名称不能为空');
1920+
contextPack=normalizeContextPack(Object.assign({},contextPack,{name:clean,updatedAt:new Date().toISOString()}));
1921+
const preset=normalizeContextPackPreset({id:existing?.id||contextPackPresetId(),name:clean,pack:contextPack,updatedAt:new Date().toISOString()});
1922+
contextPackPresets=[preset,...contextPackPresets.filter(p=>p.id!==preset.id)].slice(0,12);
1923+
activeContextPackPresetId=preset.id;
1924+
persistContextPackPresets();
1925+
persistContextPack({toast:existing?'工作包预设已更新':'工作包预设已保存'});
1926+
}finally{
1927+
setBusy(btn,false);
1928+
}
1929+
}
1930+
function loadContextPackPreset(id=''){
1931+
const targetId=id || document.getElementById('contextPackPresetSelect')?.value || activeContextPackPresetId;
1932+
const preset=contextPackPresets.find(p=>p.id===targetId);
1933+
if(!preset) return toast('请选择要加载的工作包预设');
1934+
activeContextPackPresetId=preset.id;
1935+
contextPack=normalizeContextPack(Object.assign({},preset.pack,{name:preset.name,updatedAt:new Date().toISOString()}));
1936+
persistContextPack({toast:`已加载工作包预设:${preset.name}`});
1937+
if(activePage==='chat') applyContextPackToChat();
1938+
}
1939+
function deleteContextPackPreset(btn){
1940+
const preset=selectedContextPackPreset();
1941+
if(!preset) return toast('请选择要删除的工作包预设');
1942+
if(!window.confirm(`删除本地工作包预设“${preset.name}”?`)) return;
1943+
setBusy(btn,true);
1944+
try{
1945+
contextPackPresets=contextPackPresets.filter(p=>p.id!==preset.id);
1946+
if(activeContextPackPresetId===preset.id) activeContextPackPresetId='';
1947+
persistContextPackPresets();
1948+
syncContextPackPanel();
1949+
toast('工作包预设已删除');
1950+
}finally{
1951+
setBusy(btn,false);
1952+
}
1953+
}
18631954
function updateContextPackInstructions(value){
18641955
contextPack.instructions=String(value||'').slice(0,1200);
18651956
contextPack.updatedAt=new Date().toISOString();

apps/web/static/styles.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,13 @@ button.ghost,.prompt-pill{background:#fff;color:#2d2d2a;border-color:#e1ded6}
623623
.context-pack-head span{display:block;color:#7d786d;font-size:10px;font-weight:900;text-transform:uppercase;margin-bottom:3px}
624624
.context-pack-head b{display:block;font-size:14px;line-height:1.3}
625625
.context-pack-card textarea{min-height:86px;border-radius:12px;background:#fff;font-size:12px;line-height:1.5}
626+
.context-pack-presets{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:7px;align-items:end;background:#fff;border:1px solid var(--line);border-radius:12px;padding:7px}
627+
.context-pack-presets label{display:grid;gap:4px;min-width:0}
628+
.context-pack-presets span{color:#7d786d;font-size:10px;font-weight:900;text-transform:uppercase}
629+
.context-pack-presets select{min-height:32px;border-radius:10px;background:#fbfaf7;font-size:12px}
630+
.context-pack-presets div{display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end}
631+
.context-pack-presets button{min-height:32px;padding:5px 8px;border-radius:999px;background:#fbfaf7;color:#4b4842;border:1px solid var(--line);box-shadow:none;font-size:11px}
632+
.context-pack-presets button:hover{background:#171717;color:#fff;border-color:#171717;filter:none;transform:none}
626633
.context-pack-memory{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:6px;align-items:center;background:#fff;border:1px solid var(--line);border-radius:12px;padding:6px}
627634
.context-pack-memory span{color:#7d786d;font-size:10px;font-weight:900;text-transform:uppercase;padding-left:4px;min-width:0}
628635
.context-pack-memory button{min-height:28px;padding:5px 9px;border-radius:999px;background:#fbfaf7;color:#4b4842;border:1px solid var(--line);box-shadow:none;font-size:11px}
@@ -754,6 +761,8 @@ button.ghost,.prompt-pill{background:#fff;color:#2d2d2a;border-color:#e1ded6}
754761
.composer-tools{grid-template-columns:1fr}
755762
.context-pack-actions{grid-template-columns:1fr 1fr}
756763
.context-pack-filter{grid-template-columns:1fr}
764+
.context-pack-presets{grid-template-columns:1fr}
765+
.context-pack-presets div{justify-content:flex-start}
757766
.context-pack-memory{grid-template-columns:1fr 1fr}
758767
.context-pack-memory span{grid-column:1 / -1}
759768
.context-pack-pill{grid-template-columns:1fr}

docs/ui-function-map.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
1. **控制塔**:总体状态、Agent/Trace/SQL/Codex 指标、关键链路、快捷分析入口、任务 composer、最近会话/报告继续工作面板和最近任务队列 Trace 下钻。
66
2. **Agent Studio**:按能力目录选择 Agent,支持名称/ID/Adapter 搜索、能力类型和风险筛选,右侧 inspector 查看风险、版本、知识绑定,并直接进入问数/研究。
7-
3. **智能问数**:左侧场景问题、可搜索/重命名/归档的会话库和 Project-style 工作包,中间对话与图表/表格/报告,右侧 Trace 证据链和可编辑工作 Canvas;composer 显示 Agent/数据集/工具/证据/工作包上下文。工作包支持搜索并加入数据集、知识库和报告资产,可逐项打开或移除上下文,可设置项目内/默认记忆边界,可把 Canvas 草稿随 `context_pack.canvas_markdown` 提交给问数请求,可把知识库随 `context_pack.knowledge_base_ids` 一起提交给问数请求,并提供解释指标、生成图表、打开数据资产、转深度研究、创建 Codex 任务、发送中停止生成、用户问题编辑/重问/分支/写入 Canvas/复制、会话 Brief 生成/复制/下载、回答写入/追加 Canvas、提取下一步、回答后续追问建议、捕获上下文、应用工作包、生成分析大纲/SQL 草稿/报告要点、Canvas Markdown 加粗/斜体/标题/项目列表/编号列表格式化、Canvas 选区润色/补证据/压缩/展开/追问、本地版本回退、版本变化对比、保存报告草稿和下载 Markdown 的快捷动作。
7+
3. **智能问数**:左侧场景问题、可搜索/重命名/归档的会话库和 Project-style 工作包,中间对话与图表/表格/报告,右侧 Trace 证据链和可编辑工作 Canvas;composer 显示 Agent/数据集/工具/证据/工作包上下文。工作包支持搜索并加入数据集、知识库和报告资产,可逐项打开或移除上下文,可保存/加载/删除本地项目预设,可设置项目内/默认记忆边界,可把 Canvas 草稿随 `context_pack.canvas_markdown` 提交给问数请求,可把知识库随 `context_pack.knowledge_base_ids` 一起提交给问数请求,并提供解释指标、生成图表、打开数据资产、转深度研究、创建 Codex 任务、发送中停止生成、用户问题编辑/重问/分支/写入 Canvas/复制、会话 Brief 生成/复制/下载、回答写入/追加 Canvas、提取下一步、回答后续追问建议、捕获上下文、应用工作包、生成分析大纲/SQL 草稿/报告要点、Canvas Markdown 加粗/斜体/标题/项目列表/编号列表格式化、Canvas 选区润色/补证据/压缩/展开/追问、本地版本回退、版本变化对比、保存报告草稿和下载 Markdown 的快捷动作。
88
4. **深度研究**:研究 brief、可审批计划、历史研究任务队列、证据 Trace、报告草稿入口和取消/刷新任务。
99
5. **分析面板**:物化经营总览面板、创建面板 brief、添加 Widget,并通过 SQL Guard 预校验后刷新面板证据。
1010
6. **数据能力**:可搜索/筛选的数据资产库、数据集详情、字段/指标、只读 SQL Workbench、数据画像、业务规则运行、质量历史复核,以及从数据集直接进入问数/研究/Codex 治理任务的上下文动作。

0 commit comments

Comments
 (0)