@@ -52,6 +52,8 @@ const CONTEXT_PACK_PRESETS_STORAGE_KEY = 'dap_context_pack_presets_v1';
5252const CONTEXT_PACK_FILE_LIMIT = 4 ;
5353const CONTEXT_PACK_FILE_CHAR_LIMIT = 12000 ;
5454const CONTEXT_PACK_FILE_BYTES_LIMIT = 262144 ;
55+ const CONTEXT_PACK_NOTE_LIMIT = 8 ;
56+ const CONTEXT_PACK_NOTE_CHAR_LIMIT = 4000 ;
5557let contextPack = loadContextPack ( ) ;
5658let contextPackPresets = loadContextPackPresets ( ) ;
5759let activeContextPackPresetId = '' ;
@@ -152,7 +154,7 @@ function compactTags(values,limit=4){
152154 return list . length ?list . map ( v => tag ( v ) ) . join ( '' ) :'<span class="muted">暂无标签</span>' ;
153155}
154156function defaultContextPack ( ) {
155- return { name :'默认工作包' , instructions :'' , agentId :'' , datasetIds :[ ] , knowledgeBaseIds :[ ] , reportIds :[ ] , traceIds :[ ] , sessionId :'' , sessionIds :[ ] , localFiles :[ ] , toolMode :'auto' , evidenceDepth :'standard' , memoryMode :'project' , includeCanvas :false , updatedAt :'' } ;
157+ return { name :'默认工作包' , instructions :'' , agentId :'' , datasetIds :[ ] , knowledgeBaseIds :[ ] , reportIds :[ ] , traceIds :[ ] , sessionId :'' , sessionIds :[ ] , localFiles :[ ] , savedNotes : [ ] , toolMode :'auto' , evidenceDepth :'standard' , memoryMode :'project' , includeCanvas :false , updatedAt :'' } ;
156158}
157159function normalizeIdList ( list , limit = 6 ) {
158160 return [ ...new Set ( asList ( list ) . map ( v => String ( v || '' ) . trim ( ) ) . filter ( Boolean ) ) ] . slice ( 0 , limit ) ;
@@ -174,6 +176,26 @@ function normalizeContextFile(file={}){
174176function normalizeContextFiles ( files ) {
175177 return ( Array . isArray ( files ) ?files :[ ] ) . map ( normalizeContextFile ) . filter ( f => f . name && f . content ) . slice ( 0 , CONTEXT_PACK_FILE_LIMIT ) ;
176178}
179+ function contextPackNoteId ( title = 'note' ) {
180+ const stem = String ( title || 'note' ) . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] + / g, '-' ) . replace ( / ^ - + | - + $ / g, '' ) . slice ( 0 , 30 ) || 'note' ;
181+ return `note_${ stem } _${ Date . now ( ) . toString ( 36 ) } _${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 6 ) } ` ;
182+ }
183+ function normalizeContextNote ( note = { } ) {
184+ const title = short ( String ( note . title || note . question || '回答摘录' ) . trim ( ) || '回答摘录' , 80 ) ;
185+ return {
186+ id :String ( note . id || contextPackNoteId ( title ) ) . slice ( 0 , 90 ) ,
187+ title,
188+ source :short ( String ( note . source || 'chat_answer' ) . trim ( ) || 'chat_answer' , 40 ) ,
189+ question :short ( String ( note . question || '' ) . trim ( ) , 220 ) ,
190+ content :String ( note . content || '' ) . slice ( 0 , CONTEXT_PACK_NOTE_CHAR_LIMIT ) ,
191+ traceId :String ( note . traceId || note . trace_id || '' ) . slice ( 0 , 90 ) ,
192+ sessionId :String ( note . sessionId || note . session_id || '' ) . slice ( 0 , 90 ) ,
193+ createdAt :String ( note . createdAt || note . created_at || new Date ( ) . toISOString ( ) ) . slice ( 0 , 40 )
194+ } ;
195+ }
196+ function normalizeContextNotes ( notes ) {
197+ return ( Array . isArray ( notes ) ?notes :[ ] ) . map ( normalizeContextNote ) . filter ( note => note . title && note . content ) . slice ( 0 , CONTEXT_PACK_NOTE_LIMIT ) ;
198+ }
177199function normalizeContextPack ( pack = { } ) {
178200 const base = defaultContextPack ( ) ;
179201 const next = Object . assign ( { } , base , pack || { } ) ;
@@ -188,6 +210,7 @@ function normalizeContextPack(pack={}){
188210 next . sessionIds = normalizeIdList ( [ next . sessionId , ...asList ( next . sessionIds ) ] , 8 ) ;
189211 next . sessionId = String ( next . sessionId || next . sessionIds [ 0 ] || '' ) . slice ( 0 , 90 ) ;
190212 next . localFiles = normalizeContextFiles ( next . localFiles ) ;
213+ next . savedNotes = normalizeContextNotes ( next . savedNotes ) ;
191214 next . toolMode = [ 'auto' , 'analysis' , 'sql' , 'codex' ] . includes ( next . toolMode ) ?next . toolMode :'auto' ;
192215 next . evidenceDepth = [ 'standard' , 'full' ] . includes ( next . evidenceDepth ) ?next . evidenceDepth :'standard' ;
193216 next . memoryMode = [ 'default' , 'project' ] . includes ( next . memoryMode ) ?next . memoryMode :'project' ;
@@ -231,7 +254,7 @@ function persistContextPackPresets(){
231254}
232255function contextPackHasContent ( pack = contextPack ) {
233256 const p = normalizeContextPack ( pack ) ;
234- return Boolean ( p . instructions . trim ( ) || p . agentId || p . datasetIds . length || p . knowledgeBaseIds . length || p . reportIds . length || p . traceIds . length || p . sessionIds . length || p . localFiles . length || ( p . includeCanvas && chatCanvasValue ( ) . trim ( ) ) ) ;
257+ return Boolean ( p . instructions . trim ( ) || p . agentId || p . datasetIds . length || p . knowledgeBaseIds . length || p . reportIds . length || p . traceIds . length || p . sessionIds . length || p . localFiles . length || p . savedNotes . length || ( p . includeCanvas && chatCanvasValue ( ) . trim ( ) ) ) ;
235258}
236259function asList ( value ) {
237260 if ( Array . isArray ( value ) ) return value ;
@@ -868,6 +891,45 @@ function writeAnswerToCanvas(key,mode='replace'){
868891 setChatCanvasDraft ( next , status , { reason :status } ) ;
869892 focusChatCanvas ( ) ;
870893}
894+ function savedAnswerNoteMarkdown ( r , meta = { } ) {
895+ const lines = [
896+ `# 回答摘录:${ answerQuestion ( meta ) } ` ,
897+ '' ,
898+ `- Trace:${ r ?. trace_id || meta . trace_id || '未返回 Trace' } ` ,
899+ `- 会话:${ meta . session_id || activeSessionId || '当前会话' } ` ,
900+ '' ,
901+ '## 回答' ,
902+ r ?. answer || '暂无回答正文'
903+ ] ;
904+ if ( r ?. report_markdown ) lines . push ( '' , '## 报告草稿' , r . report_markdown ) ;
905+ if ( ( r ?. warnings || [ ] ) . length ) lines . push ( '' , '## 注意事项' , ...( r . warnings || [ ] ) . map ( w => `- ${ w } ` ) ) ;
906+ if ( ( r ?. next_actions || [ ] ) . length ) lines . push ( '' , '## 下一步' , ...( r . next_actions || [ ] ) . map ( a => `- ${ a } ` ) ) ;
907+ return lines . join ( '\n' ) . slice ( 0 , CONTEXT_PACK_NOTE_CHAR_LIMIT ) ;
908+ }
909+ function saveAnswerToContextPack ( key , btn ) {
910+ const cached = answerDraftCache [ key ] ;
911+ if ( ! cached ) return toast ( '回答上下文已失效,请重新打开会话' ) ;
912+ const { r, meta} = cached ;
913+ setBusy ( btn , true ) ;
914+ try {
915+ const note = normalizeContextNote ( {
916+ title :answerQuestion ( meta ) ,
917+ source :'chat_answer' ,
918+ question :answerQuestion ( meta ) ,
919+ content :savedAnswerNoteMarkdown ( r , meta ) ,
920+ traceId :r ?. trace_id || meta . trace_id || '' ,
921+ sessionId :meta . session_id || activeSessionId || '' ,
922+ createdAt :new Date ( ) . toISOString ( )
923+ } ) ;
924+ contextPack . savedNotes = normalizeContextNotes ( [ note , ...normalizeContextPack ( contextPack ) . savedNotes ] ) ;
925+ if ( note . traceId ) contextPack . traceIds = normalizeIdList ( [ note . traceId , ...contextPack . traceIds ] , 6 ) ;
926+ if ( note . sessionId ) addSessionToContextPack ( note . sessionId ) ;
927+ contextPack . updatedAt = new Date ( ) . toISOString ( ) ;
928+ persistContextPack ( { toast :'回答已存入工作包' } ) ;
929+ } finally {
930+ setBusy ( btn , false ) ;
931+ }
932+ }
871933function reportEvidenceFromAnswer ( r , meta = { } ) {
872934 return [ {
873935 type :'chat_answer' ,
@@ -921,6 +983,7 @@ function answerContextActions(r, meta={}, key=''){
921983 `<button class="answer-tool" data-prompt="${ esc ( research ) } " onclick="setAnalysisDraft(this.dataset.prompt,'agent_business_analysis')">转深度研究</button>` ,
922984 `<button class="answer-tool" data-title="优化问数工作流" data-prompt="${ esc ( codexPrompt ) } " onclick="setCodexDraft(this.dataset.title,this.dataset.prompt)">创建 Codex 任务</button>` ,
923985 key ?`<button class="answer-tool" onclick="saveAnswerAsReport('${ jsArg ( key ) } ',this)">保存报告</button>` :'' ,
986+ key ?`<button class="answer-tool" onclick="saveAnswerToContextPack('${ jsArg ( key ) } ',this)">存入工作包</button>` :'' ,
924987 traceId ?`<button class="answer-tool" onclick="openEvidence('${ jsArg ( traceId ) } ','steps')">定位证据</button>` :'' ,
925988 traceId ?`<button class="answer-tool" onclick="addTraceToContextPack('${ jsArg ( traceId ) } ',this)">加入工作包</button>` :'' ,
926989 `<button class="answer-tool ghost-tool" data-copy="${ esc ( copyText ) } " onclick="copyAnswerText(this.dataset.copy,this)">复制</button>`
@@ -1948,13 +2011,14 @@ function contextPackCounts(){
19482011 traces :p . traceIds . length ,
19492012 sessions :p . sessionIds . length ,
19502013 files :p . localFiles . length ,
2014+ notes :p . savedNotes . length ,
19512015 instructions :p . instructions . trim ( ) ?1 :0 ,
19522016 canvas :p . includeCanvas && chatCanvasValue ( ) . trim ( ) ?1 :0
19532017 } ;
19542018}
19552019function contextPackSummaryLabel ( ) {
19562020 const counts = contextPackCounts ( ) ;
1957- const total = counts . datasets + counts . knowledge + counts . reports + counts . traces + counts . sessions + counts . files + counts . instructions + counts . canvas + ( contextPack . agentId ?1 :0 ) ;
2021+ const total = counts . datasets + counts . knowledge + counts . reports + counts . traces + counts . sessions + counts . files + counts . notes + counts . instructions + counts . canvas + ( contextPack . agentId ?1 :0 ) ;
19582022 return total ?`${ total } 项上下文` :'未捕获' ;
19592023}
19602024function contextPackSummaryDetail ( ) {
@@ -1968,6 +2032,7 @@ function contextPackSummaryDetail(){
19682032 if ( counts . reports ) parts . push ( `${ counts . reports } 报告` ) ;
19692033 if ( counts . traces ) parts . push ( `${ counts . traces } Trace` ) ;
19702034 if ( counts . files ) parts . push ( `${ counts . files } 本地资料` ) ;
2035+ if ( counts . notes ) parts . push ( `${ counts . notes } 回答摘录` ) ;
19712036 if ( counts . canvas ) parts . push ( 'Canvas' ) ;
19722037 if ( counts . sessions ) parts . push ( `${ counts . sessions } 会话` ) ;
19732038 return parts . length ?parts . join ( ' · ' ) :'Project-style local context' ;
@@ -1989,10 +2054,11 @@ function contextPackPills(){
19892054 p . traceIds . forEach ( id => pills . push ( { kind :'trace' , id, label :'Trace' , value :id } ) ) ;
19902055 p . sessionIds . forEach ( id => pills . push ( { kind :'session' , id, label :'会话' , value :id } ) ) ;
19912056 p . localFiles . forEach ( file => pills . push ( { kind :'file' , id :file . id , label :'本地资料' , value :`${ file . name } · ${ fileSizeLabel ( file . size ) } ` } ) ) ;
2057+ p . savedNotes . forEach ( note => pills . push ( { kind :'note' , id :note . id , label :'回答摘录' , value :note . traceId ?`${ note . title } · ${ note . traceId } ` :note . title } ) ) ;
19922058 return pills . length ?`<div class="context-pack-pills">${ pills . map ( pill => `<article class="context-pack-pill">
19932059 <div><small>${ esc ( pill . label ) } </small><b>${ esc ( short ( pill . value , 34 ) ) } </b></div>
19942060 <nav aria-label="${ esc ( pill . label ) } 操作"><button type="button" onclick="openContextPackItem('${ jsArg ( pill . kind ) } ','${ jsArg ( pill . id ) } ')">打开</button><button type="button" onclick="removeContextPackItem('${ jsArg ( pill . kind ) } ','${ jsArg ( pill . id ) } ')">移除</button></nav>
1995- </article>` ) . join ( '' ) } </div>` :`<p class="context-pack-empty">捕获当前会话、添加资产或加入本地资料后 ,这里会显示 Agent、数据集、知识库、Trace、报告、会话和资料线索 。</p>` ;
2061+ </article>` ) . join ( '' ) } </div>` :`<p class="context-pack-empty">捕获当前会话、添加资产、加入本地资料或存入关键回答后 ,这里会显示 Agent、数据集、知识库、Trace、报告、会话、资料和摘录线索 。</p>` ;
19962062}
19972063function contextPackStatusText ( ) {
19982064 return contextPack . updatedAt ?`已更新 ${ timeText ( contextPack . updatedAt ) } · 本地浏览器工作包` :'本地浏览器工作包 · 未写入服务端' ;
@@ -2241,6 +2307,7 @@ function removeContextPackItem(kind,id){
22412307 if ( kind === 'report' ) contextPack . reportIds = removeId ( contextPack . reportIds ) ;
22422308 if ( kind === 'trace' ) contextPack . traceIds = removeId ( contextPack . traceIds ) ;
22432309 if ( kind === 'file' ) contextPack . localFiles = normalizeContextFiles ( contextPack . localFiles . filter ( file => file . id !== id ) ) ;
2310+ if ( kind === 'note' ) contextPack . savedNotes = normalizeContextNotes ( contextPack . savedNotes . filter ( note => note . id !== id ) ) ;
22442311 if ( kind === 'session' ) {
22452312 contextPack . sessionIds = removeId ( asList ( contextPack . sessionIds ) ) ;
22462313 contextPack . sessionId = contextPack . sessionIds [ 0 ] || '' ;
@@ -2261,6 +2328,12 @@ function openContextPackItem(kind,id){
22612328 showPage ( 'chat' ) ;
22622329 setTimeout ( ( ) => setChatCanvasDraft ( `# 本地资料:${ file . name } \n\n${ file . content } ` , '本地资料已写入 Canvas' ) , 120 ) ;
22632330 }
2331+ if ( kind === 'note' ) {
2332+ const note = normalizeContextPack ( contextPack ) . savedNotes . find ( item => item . id === id ) ;
2333+ if ( ! note ) return toast ( '回答摘录不存在' ) ;
2334+ showPage ( 'chat' ) ;
2335+ setTimeout ( ( ) => setChatCanvasDraft ( note . content , '回答摘录已写入 Canvas' ) , 120 ) ;
2336+ }
22642337}
22652338function addTraceToContextPack ( traceId , btn ) {
22662339 if ( ! traceId ) return ;
@@ -2292,6 +2365,10 @@ function contextPackPrompt(){
22922365 lines . push ( '' , '本地资料:' ) ;
22932366 p . localFiles . forEach ( file => lines . push ( `- ${ file . name } (${ fileSizeLabel ( file . size ) } ):${ short ( file . content . replace ( / \s + / g, ' ' ) , 900 ) } ` ) ) ;
22942367 }
2368+ if ( p . savedNotes . length ) {
2369+ lines . push ( '' , '回答摘录:' ) ;
2370+ p . savedNotes . forEach ( note => lines . push ( `- ${ note . title } ${ note . traceId ?` (${ note . traceId } )` :'' } :${ short ( note . content . replace ( / \s + / g, ' ' ) , 900 ) } ` ) ) ;
2371+ }
22952372 if ( p . traceIds . length ) lines . push ( `Trace:${ p . traceIds . join ( '、' ) } ` ) ;
22962373 if ( p . sessionIds . length ) lines . push ( `来源会话:${ p . sessionIds . join ( '、' ) } ` ) ;
22972374 if ( p . includeCanvas && chatCanvasValue ( ) . trim ( ) ) lines . push ( `Canvas 草稿:${ short ( chatCanvasValue ( ) . trim ( ) . replace ( / \s + / g, ' ' ) , 520 ) } ` ) ;
@@ -2308,6 +2385,7 @@ function contextPackCanvasMarkdown(){
23082385 if ( p . knowledgeBaseIds . length ) lines . push ( '## 知识库' , ...p . knowledgeBaseIds . map ( id => `- ${ knowledgeBaseName ( id ) } (${ id } )` ) , '' ) ;
23092386 if ( p . reportIds . length ) lines . push ( '## 报告' , ...p . reportIds . map ( id => `- ${ contextPackReportTitle ( id ) } (${ id } )` ) , '' ) ;
23102387 if ( p . localFiles . length ) lines . push ( '## 本地资料' , ...p . localFiles . flatMap ( file => [ `### ${ file . name } ` , `大小:${ fileSizeLabel ( file . size ) } ;类型:${ file . type } ` , '' , file . content . slice ( 0 , 3000 ) , '' ] ) , '' ) ;
2388+ if ( p . savedNotes . length ) lines . push ( '## 回答摘录' , ...p . savedNotes . flatMap ( note => [ `### ${ note . title } ` , `来源:${ note . source } ${ note . traceId ?`;Trace:${ note . traceId } ` :'' } ${ note . sessionId ?`;会话:${ note . sessionId } ` :'' } ` , '' , note . content . slice ( 0 , 3000 ) , '' ] ) , '' ) ;
23112389 if ( p . traceIds . length ) lines . push ( '## Trace' , ...p . traceIds . map ( id => `- ${ id } ` ) , '' ) ;
23122390 if ( p . sessionIds . length ) lines . push ( '## 来源会话' , ...p . sessionIds . map ( id => `- ${ id } ` ) , '' ) ;
23132391 if ( p . includeCanvas && chatCanvasValue ( ) . trim ( ) ) lines . push ( '## Canvas 草稿' , chatCanvasValue ( ) . trim ( ) . slice ( 0 , 3000 ) , '' ) ;
@@ -2323,6 +2401,7 @@ function contextPackPayload(){
23232401 knowledge_base_ids :p . knowledgeBaseIds ,
23242402 report_ids :p . reportIds ,
23252403 local_files :p . localFiles . map ( file => ( { id :file . id , name :file . name , type :file . type , size :file . size , content :file . content . slice ( 0 , 6000 ) , created_at :file . createdAt } ) ) ,
2404+ saved_notes :p . savedNotes . map ( note => ( { id :note . id , title :note . title , source :note . source , question :note . question , content :note . content . slice ( 0 , 3000 ) , trace_id :note . traceId , session_id :note . sessionId , created_at :note . createdAt } ) ) ,
23262405 trace_ids :p . traceIds ,
23272406 session_id :p . sessionId || null ,
23282407 session_ids :p . sessionIds ,
0 commit comments