@@ -44,7 +44,10 @@ let activeReportId = '';
4444let commandAssetsLoaded = false ;
4545let contextPackAssetFilterState = { q :'' , type :'all' } ;
4646const CONTEXT_PACK_STORAGE_KEY = 'dap_context_pack_v1' ;
47+ const CONTEXT_PACK_PRESETS_STORAGE_KEY = 'dap_context_pack_presets_v1' ;
4748let contextPack = loadContextPack ( ) ;
49+ let contextPackPresets = loadContextPackPresets ( ) ;
50+ let activeContextPackPresetId = '' ;
4851
4952async 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+ }
175204function 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(){
17521781function 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+ }
17551797function 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+ }
18631954function updateContextPackInstructions ( value ) {
18641955 contextPack . instructions = String ( value || '' ) . slice ( 0 , 1200 ) ;
18651956 contextPack . updatedAt = new Date ( ) . toISOString ( ) ;
0 commit comments