tabby-ai-assistant 1.0.5 → 1.0.6
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/dist/components/chat/ai-sidebar.component.d.ts +147 -0
- package/dist/components/chat/chat-interface.component.d.ts +38 -6
- package/dist/components/settings/general-settings.component.d.ts +6 -3
- package/dist/components/settings/provider-config.component.d.ts +25 -12
- package/dist/components/terminal/command-preview.component.d.ts +38 -0
- package/dist/index-full.d.ts +8 -0
- package/dist/index-minimal.d.ts +3 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +1 -2
- package/dist/providers/tabby/ai-config.provider.d.ts +57 -5
- package/dist/providers/tabby/ai-hotkey.provider.d.ts +8 -14
- package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +8 -9
- package/dist/services/chat/ai-sidebar.service.d.ts +89 -0
- package/dist/services/chat/chat-history.service.d.ts +78 -0
- package/dist/services/chat/chat-session.service.d.ts +57 -2
- package/dist/services/context/compaction.d.ts +90 -0
- package/dist/services/context/manager.d.ts +69 -0
- package/dist/services/context/memory.d.ts +116 -0
- package/dist/services/context/token-budget.d.ts +105 -0
- package/dist/services/core/ai-assistant.service.d.ts +40 -1
- package/dist/services/core/checkpoint.service.d.ts +130 -0
- package/dist/services/platform/escape-sequence.service.d.ts +132 -0
- package/dist/services/platform/platform-detection.service.d.ts +146 -0
- package/dist/services/providers/anthropic-provider.service.d.ts +5 -0
- package/dist/services/providers/base-provider.service.d.ts +6 -1
- package/dist/services/providers/glm-provider.service.d.ts +5 -0
- package/dist/services/providers/minimax-provider.service.d.ts +10 -1
- package/dist/services/providers/ollama-provider.service.d.ts +76 -0
- package/dist/services/providers/openai-compatible.service.d.ts +5 -0
- package/dist/services/providers/openai-provider.service.d.ts +5 -0
- package/dist/services/providers/vllm-provider.service.d.ts +82 -0
- package/dist/services/terminal/buffer-analyzer.service.d.ts +128 -0
- package/dist/services/terminal/terminal-manager.service.d.ts +185 -0
- package/dist/services/terminal/terminal-tools.service.d.ts +79 -0
- package/dist/types/ai.types.d.ts +92 -0
- package/dist/types/provider.types.d.ts +1 -1
- package/package.json +7 -10
- package/src/components/chat/ai-sidebar.component.ts +945 -0
- package/src/components/chat/chat-input.component.html +9 -24
- package/src/components/chat/chat-input.component.scss +3 -2
- package/src/components/chat/chat-interface.component.html +77 -69
- package/src/components/chat/chat-interface.component.scss +54 -4
- package/src/components/chat/chat-interface.component.ts +250 -34
- package/src/components/chat/chat-settings.component.scss +4 -4
- package/src/components/chat/chat-settings.component.ts +22 -11
- package/src/components/common/error-message.component.html +15 -0
- package/src/components/common/error-message.component.scss +77 -0
- package/src/components/common/error-message.component.ts +2 -96
- package/src/components/common/loading-spinner.component.html +4 -0
- package/src/components/common/loading-spinner.component.scss +57 -0
- package/src/components/common/loading-spinner.component.ts +2 -63
- package/src/components/security/consent-dialog.component.html +22 -0
- package/src/components/security/consent-dialog.component.scss +34 -0
- package/src/components/security/consent-dialog.component.ts +2 -55
- package/src/components/security/password-prompt.component.html +19 -0
- package/src/components/security/password-prompt.component.scss +30 -0
- package/src/components/security/password-prompt.component.ts +2 -54
- package/src/components/security/risk-confirm-dialog.component.html +8 -12
- package/src/components/security/risk-confirm-dialog.component.scss +8 -5
- package/src/components/security/risk-confirm-dialog.component.ts +6 -6
- package/src/components/settings/ai-settings-tab.component.html +16 -20
- package/src/components/settings/ai-settings-tab.component.scss +8 -5
- package/src/components/settings/ai-settings-tab.component.ts +12 -12
- package/src/components/settings/general-settings.component.html +8 -17
- package/src/components/settings/general-settings.component.scss +6 -3
- package/src/components/settings/general-settings.component.ts +62 -22
- package/src/components/settings/provider-config.component.html +19 -39
- package/src/components/settings/provider-config.component.scss +182 -39
- package/src/components/settings/provider-config.component.ts +119 -7
- package/src/components/settings/security-settings.component.scss +1 -1
- package/src/components/terminal/ai-toolbar-button.component.html +8 -0
- package/src/components/terminal/ai-toolbar-button.component.scss +20 -0
- package/src/components/terminal/ai-toolbar-button.component.ts +2 -30
- package/src/components/terminal/command-preview.component.html +61 -0
- package/src/components/terminal/command-preview.component.scss +72 -0
- package/src/components/terminal/command-preview.component.ts +127 -140
- package/src/components/terminal/command-suggestion.component.html +23 -0
- package/src/components/terminal/command-suggestion.component.scss +55 -0
- package/src/components/terminal/command-suggestion.component.ts +2 -77
- package/src/index-minimal.ts +32 -0
- package/src/index.ts +94 -11
- package/src/index.ts.backup +165 -0
- package/src/providers/tabby/ai-config.provider.ts +60 -51
- package/src/providers/tabby/ai-hotkey.provider.ts +23 -39
- package/src/providers/tabby/ai-settings-tab.provider.ts +2 -2
- package/src/providers/tabby/ai-toolbar-button.provider.ts +29 -24
- package/src/services/chat/ai-sidebar.service.ts +258 -0
- package/src/services/chat/chat-history.service.ts +308 -0
- package/src/services/chat/chat-history.service.ts.backup +239 -0
- package/src/services/chat/chat-session.service.ts +276 -3
- package/src/services/context/compaction.ts +483 -0
- package/src/services/context/manager.ts +442 -0
- package/src/services/context/memory.ts +519 -0
- package/src/services/context/token-budget.ts +422 -0
- package/src/services/core/ai-assistant.service.ts +280 -5
- package/src/services/core/ai-provider-manager.service.ts +2 -2
- package/src/services/core/checkpoint.service.ts +619 -0
- package/src/services/platform/escape-sequence.service.ts +499 -0
- package/src/services/platform/platform-detection.service.ts +494 -0
- package/src/services/providers/anthropic-provider.service.ts +28 -1
- package/src/services/providers/base-provider.service.ts +7 -1
- package/src/services/providers/glm-provider.service.ts +28 -1
- package/src/services/providers/minimax-provider.service.ts +209 -11
- package/src/services/providers/ollama-provider.service.ts +445 -0
- package/src/services/providers/openai-compatible.service.ts +9 -0
- package/src/services/providers/openai-provider.service.ts +9 -0
- package/src/services/providers/vllm-provider.service.ts +463 -0
- package/src/services/security/risk-assessment.service.ts +6 -2
- package/src/services/terminal/buffer-analyzer.service.ts +594 -0
- package/src/services/terminal/terminal-manager.service.ts +748 -0
- package/src/services/terminal/terminal-tools.service.ts +441 -0
- package/src/styles/ai-assistant.scss +78 -6
- package/src/types/ai.types.ts +144 -0
- package/src/types/provider.types.ts +1 -1
- package/tsconfig.json +9 -9
- package/webpack.config.js +28 -6
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { LoggerService } from '../core/logger.service';
|
|
3
|
+
import {
|
|
4
|
+
ApiMessage,
|
|
5
|
+
ContextConfig,
|
|
6
|
+
DEFAULT_CONTEXT_CONFIG,
|
|
7
|
+
CompactionResult,
|
|
8
|
+
PruneResult,
|
|
9
|
+
TruncationResult
|
|
10
|
+
} from '../../types/ai.types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 压缩算法实现类
|
|
14
|
+
* 提供Prune、Compact、Truncate三种压缩算法的具体实现
|
|
15
|
+
*/
|
|
16
|
+
@Injectable({
|
|
17
|
+
providedIn: 'root'
|
|
18
|
+
})
|
|
19
|
+
export class Compaction {
|
|
20
|
+
private config: ContextConfig;
|
|
21
|
+
|
|
22
|
+
constructor(private logger: LoggerService) {
|
|
23
|
+
this.config = { ...DEFAULT_CONTEXT_CONFIG };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Prune算法 - 移除工具输出中的冗余信息
|
|
28
|
+
* 保留关键信息,移除重复、冗长或无关的内容
|
|
29
|
+
*/
|
|
30
|
+
async prune(messages: ApiMessage[]): Promise<PruneResult> {
|
|
31
|
+
this.logger.info('Executing Prune algorithm', { messageCount: messages.length });
|
|
32
|
+
|
|
33
|
+
let tokensSaved = 0;
|
|
34
|
+
let partsCompacted = 0;
|
|
35
|
+
const processedMessages: ApiMessage[] = [];
|
|
36
|
+
|
|
37
|
+
for (const message of messages) {
|
|
38
|
+
const processedMessage = await this.pruneMessage(message);
|
|
39
|
+
if (processedMessage) {
|
|
40
|
+
processedMessages.push(processedMessage);
|
|
41
|
+
tokensSaved += processedMessage.tokensSaved || 0;
|
|
42
|
+
partsCompacted += processedMessage.partsCompacted || 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.logger.info('Prune completed', {
|
|
47
|
+
originalCount: messages.length,
|
|
48
|
+
processedCount: processedMessages.length,
|
|
49
|
+
tokensSaved,
|
|
50
|
+
partsCompacted
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
pruned: tokensSaved > 0,
|
|
55
|
+
tokensSaved,
|
|
56
|
+
partsCompacted
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Compact算法 - 使用AI生成摘要压缩上下文
|
|
62
|
+
* 将多个消息合并为一个摘要,保留核心信息
|
|
63
|
+
*/
|
|
64
|
+
async compact(messages: ApiMessage[]): Promise<CompactionResult> {
|
|
65
|
+
this.logger.info('Executing Compact algorithm', { messageCount: messages.length });
|
|
66
|
+
|
|
67
|
+
const condenseId = `compact_${Date.now()}`;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// 1. 准备摘要输入
|
|
71
|
+
const summaryInput = this.formatMessagesForSummary(messages);
|
|
72
|
+
|
|
73
|
+
// 2. 生成摘要(TODO: 调用AI API)
|
|
74
|
+
const summary = await this.generateSummary(summaryInput, messages);
|
|
75
|
+
|
|
76
|
+
// 3. 创建摘要消息
|
|
77
|
+
const summaryMessage: ApiMessage = {
|
|
78
|
+
role: 'system',
|
|
79
|
+
content: summary,
|
|
80
|
+
ts: Date.now(),
|
|
81
|
+
isSummary: true,
|
|
82
|
+
condenseId,
|
|
83
|
+
condenseParent: undefined
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// 4. 标记被压缩的消息
|
|
87
|
+
messages.forEach(msg => {
|
|
88
|
+
msg.condenseParent = condenseId;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// 5. 计算节省的Token数
|
|
92
|
+
const originalTokens = this.calculateTotalTokens(messages);
|
|
93
|
+
const summaryTokens = this.estimateTokens(summary);
|
|
94
|
+
const tokensSaved = originalTokens - summaryTokens;
|
|
95
|
+
|
|
96
|
+
this.logger.info('Compact completed', {
|
|
97
|
+
condenseId,
|
|
98
|
+
originalTokens,
|
|
99
|
+
summaryTokens,
|
|
100
|
+
tokensSaved
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
messages: [summaryMessage],
|
|
106
|
+
summary,
|
|
107
|
+
condenseId,
|
|
108
|
+
tokensSaved,
|
|
109
|
+
cost: 0 // TODO: 计算实际API成本
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this.logger.error('Compact failed', { error });
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
messages,
|
|
117
|
+
tokensSaved: 0,
|
|
118
|
+
cost: 0,
|
|
119
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Truncate算法 - 滑动窗口截断
|
|
126
|
+
* 保留最近的消息,移除较早的消息
|
|
127
|
+
*/
|
|
128
|
+
truncate(messages: ApiMessage[]): TruncationResult {
|
|
129
|
+
this.logger.info('Executing Truncate algorithm', { messageCount: messages.length });
|
|
130
|
+
|
|
131
|
+
const truncationId = `truncate_${Date.now()}`;
|
|
132
|
+
const messagesToKeep = Math.min(this.config.messagesToKeep, messages.length);
|
|
133
|
+
|
|
134
|
+
// 保留最近的N条消息
|
|
135
|
+
const keptMessages = messages.slice(-messagesToKeep);
|
|
136
|
+
const removedMessages = messages.slice(0, -messagesToKeep);
|
|
137
|
+
|
|
138
|
+
// 添加截断标记
|
|
139
|
+
const truncationMarker: ApiMessage = {
|
|
140
|
+
role: 'system',
|
|
141
|
+
content: `[${removedMessages.length}条消息已被截断以节省Token成本]`,
|
|
142
|
+
ts: Date.now(),
|
|
143
|
+
isTruncationMarker: true,
|
|
144
|
+
truncationId,
|
|
145
|
+
truncationParent: undefined
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const resultMessages = [...keptMessages, truncationMarker];
|
|
149
|
+
|
|
150
|
+
// 估算节省的Token数(移除的消息)
|
|
151
|
+
const removedTokens = this.calculateTotalTokens(removedMessages);
|
|
152
|
+
|
|
153
|
+
this.logger.info('Truncate completed', {
|
|
154
|
+
originalCount: messages.length,
|
|
155
|
+
keptCount: keptMessages.length,
|
|
156
|
+
removedCount: removedMessages.length,
|
|
157
|
+
removedTokens
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
messages: resultMessages,
|
|
162
|
+
truncationId,
|
|
163
|
+
messagesRemoved: removedMessages.length
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 智能压缩 - 根据消息类型和重要性选择最佳压缩策略
|
|
169
|
+
*/
|
|
170
|
+
async smartCompact(messages: ApiMessage[], tokenBudget: number): Promise<{
|
|
171
|
+
strategy: 'prune' | 'compact' | 'truncate';
|
|
172
|
+
result: PruneResult | CompactionResult | TruncationResult;
|
|
173
|
+
messages: ApiMessage[];
|
|
174
|
+
}> {
|
|
175
|
+
this.logger.info('Executing Smart Compact', {
|
|
176
|
+
messageCount: messages.length,
|
|
177
|
+
tokenBudget
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const currentTokens = this.calculateTotalTokens(messages);
|
|
181
|
+
const usageRate = currentTokens / tokenBudget;
|
|
182
|
+
|
|
183
|
+
// 策略1:轻度压缩 - Prune(使用率 < 80%)
|
|
184
|
+
if (usageRate < 0.8) {
|
|
185
|
+
this.logger.info('Using Prune strategy (light compression)');
|
|
186
|
+
const pruneResult = await this.prune(messages);
|
|
187
|
+
return {
|
|
188
|
+
strategy: 'prune',
|
|
189
|
+
result: pruneResult,
|
|
190
|
+
messages: await this.applyPruneResult(messages, pruneResult)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 策略2:中度压缩 - Compact(使用率 < 95%)
|
|
195
|
+
if (usageRate < 0.95) {
|
|
196
|
+
this.logger.info('Using Compact strategy (medium compression)');
|
|
197
|
+
const compactResult = await this.compact(messages);
|
|
198
|
+
return {
|
|
199
|
+
strategy: 'compact',
|
|
200
|
+
result: compactResult,
|
|
201
|
+
messages: compactResult.messages
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 策略3:重度压缩 - Truncate(使用率 >= 95%)
|
|
206
|
+
this.logger.info('Using Truncate strategy (heavy compression)');
|
|
207
|
+
const truncateResult = this.truncate(messages);
|
|
208
|
+
return {
|
|
209
|
+
strategy: 'truncate',
|
|
210
|
+
result: truncateResult,
|
|
211
|
+
messages: truncateResult.messages
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 更新配置
|
|
217
|
+
*/
|
|
218
|
+
updateConfig(newConfig: Partial<ContextConfig>): void {
|
|
219
|
+
this.config = { ...this.config, ...newConfig };
|
|
220
|
+
this.logger.info('Compaction config updated', { config: this.config });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 获取当前配置
|
|
225
|
+
*/
|
|
226
|
+
getConfig(): ContextConfig {
|
|
227
|
+
return { ...this.config };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ==================== 私有方法 ====================
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 裁剪单条消息
|
|
234
|
+
*/
|
|
235
|
+
private async pruneMessage(message: ApiMessage): Promise<(ApiMessage & { tokensSaved?: number; partsCompacted?: number }) | null> {
|
|
236
|
+
const content = message.content;
|
|
237
|
+
|
|
238
|
+
// 处理字符串内容
|
|
239
|
+
if (typeof content === 'string') {
|
|
240
|
+
return this.pruneStringContent(message, content);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 处理复杂内容(工具调用结果等)
|
|
244
|
+
if (Array.isArray(content)) {
|
|
245
|
+
return this.pruneComplexContent(message, content);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return message;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 裁剪字符串内容
|
|
253
|
+
*/
|
|
254
|
+
private pruneStringContent(message: ApiMessage, content: string): ApiMessage & { tokensSaved?: number; partsCompacted?: number } {
|
|
255
|
+
let tokensSaved = 0;
|
|
256
|
+
let partsCompacted = 0;
|
|
257
|
+
let prunedContent = content;
|
|
258
|
+
|
|
259
|
+
// 1. 移除过长的重复行
|
|
260
|
+
const lines = content.split('\n');
|
|
261
|
+
const uniqueLines: string[] = [];
|
|
262
|
+
const seenLines = new Set<string>();
|
|
263
|
+
|
|
264
|
+
for (const line of lines) {
|
|
265
|
+
// 跳过完全相同的重复行
|
|
266
|
+
if (!seenLines.has(line)) {
|
|
267
|
+
uniqueLines.push(line);
|
|
268
|
+
seenLines.add(line);
|
|
269
|
+
} else {
|
|
270
|
+
partsCompacted++;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (uniqueLines.length < lines.length) {
|
|
275
|
+
prunedContent = uniqueLines.join('\n');
|
|
276
|
+
tokensSaved += (lines.length - uniqueLines.length) * 10; // 估算每行节省的token数
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 2. 截断过长的输出(保留前500字符和后100字符)
|
|
280
|
+
if (prunedContent.length > 1000) {
|
|
281
|
+
const preservedLength = 500 + 100;
|
|
282
|
+
const removedLength = prunedContent.length - preservedLength;
|
|
283
|
+
prunedContent = prunedContent.substring(0, 500) + `\n[...${removedLength}字符已裁剪...]` + prunedContent.substring(prunedContent.length - 100);
|
|
284
|
+
tokensSaved += Math.floor(removedLength / 4);
|
|
285
|
+
partsCompacted++;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 3. 移除多余的空行(保留最多2个连续空行)
|
|
289
|
+
prunedContent = prunedContent.replace(/\n{3,}/g, '\n\n');
|
|
290
|
+
const removedBlankLines = (content.match(/\n{3,}/g) || []).length;
|
|
291
|
+
tokensSaved += removedBlankLines * 5;
|
|
292
|
+
partsCompacted += removedBlankLines;
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
...message,
|
|
296
|
+
content: prunedContent,
|
|
297
|
+
tokensSaved,
|
|
298
|
+
partsCompacted
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 裁剪复杂内容(工具调用结果等)
|
|
304
|
+
*/
|
|
305
|
+
private pruneComplexContent(message: ApiMessage, content: any[]): ApiMessage & { tokensSaved?: number; partsCompacted?: number } {
|
|
306
|
+
// TODO: 实现复杂内容的裁剪逻辑
|
|
307
|
+
// 例如:工具调用结果的格式化、JSON数据的精简等
|
|
308
|
+
return {
|
|
309
|
+
...message,
|
|
310
|
+
content,
|
|
311
|
+
tokensSaved: 0,
|
|
312
|
+
partsCompacted: 0
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 应用Prune结果到消息
|
|
318
|
+
*/
|
|
319
|
+
private async applyPruneResult(messages: ApiMessage[], pruneResult: PruneResult): Promise<ApiMessage[]> {
|
|
320
|
+
// 简化实现:假设prune已经处理了消息
|
|
321
|
+
// 实际实现中需要更复杂的逻辑来处理prune结果
|
|
322
|
+
return messages;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 格式化消息用于摘要
|
|
327
|
+
*/
|
|
328
|
+
private formatMessagesForSummary(messages: ApiMessage[]): string {
|
|
329
|
+
const formattedMessages = messages.map(msg => {
|
|
330
|
+
const content = this.extractTextContent(msg.content);
|
|
331
|
+
return `[${msg.role}] ${content}`;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return formattedMessages.join('\n---\n');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 提取文本内容
|
|
339
|
+
*/
|
|
340
|
+
private extractTextContent(content: string | any[]): string {
|
|
341
|
+
if (typeof content === 'string') {
|
|
342
|
+
return content;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (Array.isArray(content)) {
|
|
346
|
+
return content
|
|
347
|
+
.filter(block => block.type === 'text' && block.text)
|
|
348
|
+
.map(block => block.text)
|
|
349
|
+
.join('\n');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return '[复杂内容]';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* 生成摘要(集成AI API)
|
|
357
|
+
*/
|
|
358
|
+
private async generateSummary(input: string, messages: ApiMessage[]): Promise<string> {
|
|
359
|
+
try {
|
|
360
|
+
// TODO: 实现AI摘要生成
|
|
361
|
+
// 实际实现中应该调用AI API
|
|
362
|
+
|
|
363
|
+
// 临时实现:基于规则的智能摘要
|
|
364
|
+
const messageCount = messages.length;
|
|
365
|
+
const estimatedTokens = this.calculateTotalTokens(messages);
|
|
366
|
+
|
|
367
|
+
// 如果消息数量较少,使用简单摘要
|
|
368
|
+
if (messageCount < 5) {
|
|
369
|
+
return this.createSimpleSummary(messages);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 如果消息数量较多,使用智能摘要
|
|
373
|
+
const intelligentSummary = this.createIntelligentSummary(messages);
|
|
374
|
+
|
|
375
|
+
return `【AI摘要】压缩了${messageCount}条消息(约${estimatedTokens}个Token)
|
|
376
|
+
|
|
377
|
+
${intelligentSummary}
|
|
378
|
+
|
|
379
|
+
--- 上下文压缩完成 ---`;
|
|
380
|
+
|
|
381
|
+
} catch (error) {
|
|
382
|
+
this.logger.error('Failed to generate AI summary', { error });
|
|
383
|
+
// 降级到本地摘要
|
|
384
|
+
return this.createFallbackSummary(messages);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 创建简单摘要(适用于短对话)
|
|
390
|
+
*/
|
|
391
|
+
private createSimpleSummary(messages: ApiMessage[]): string {
|
|
392
|
+
const userMessages = messages.filter(m => m.role === 'user');
|
|
393
|
+
const assistantMessages = messages.filter(m => m.role === 'assistant');
|
|
394
|
+
|
|
395
|
+
if (userMessages.length === 0) {
|
|
396
|
+
return '会话摘要:空对话';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const firstMessage = this.extractTextContent(userMessages[0].content);
|
|
400
|
+
const lastMessage = this.extractTextContent(assistantMessages[assistantMessages.length - 1]?.content || '');
|
|
401
|
+
|
|
402
|
+
return `【摘要】简短对话:
|
|
403
|
+
用户问题:${firstMessage.substring(0, 80)}${firstMessage.length > 80 ? '...' : ''}
|
|
404
|
+
助手回复:${lastMessage.substring(0, 80)}${lastMessage.length > 80 ? '...' : ''}
|
|
405
|
+
消息总数:${messages.length}条`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 创建降级摘要(当AI API不可用时)
|
|
410
|
+
*/
|
|
411
|
+
private createFallbackSummary(messages: ApiMessage[]): string {
|
|
412
|
+
const messageCount = messages.length;
|
|
413
|
+
const userCount = messages.filter(m => m.role === 'user').length;
|
|
414
|
+
const assistantCount = messages.filter(m => m.role === 'assistant').length;
|
|
415
|
+
const totalTokens = this.calculateTotalTokens(messages);
|
|
416
|
+
|
|
417
|
+
const summary = [
|
|
418
|
+
`【压缩摘要】`,
|
|
419
|
+
`消息统计:${messageCount}条消息(用户${userCount}条,助手${assistantCount}条)`,
|
|
420
|
+
`Token使用:约${totalTokens}个Token`,
|
|
421
|
+
``,
|
|
422
|
+
`关键内容提取:`
|
|
423
|
+
];
|
|
424
|
+
|
|
425
|
+
// 提取关键消息
|
|
426
|
+
const keyMessages = messages
|
|
427
|
+
.filter(m => m.role === 'user' && this.extractTextContent(m.content).length > 10)
|
|
428
|
+
.slice(0, 3)
|
|
429
|
+
.map((m, i) => ` ${i + 1}. ${this.extractTextContent(m.content).substring(0, 60)}...`);
|
|
430
|
+
|
|
431
|
+
summary.push(...keyMessages);
|
|
432
|
+
|
|
433
|
+
summary.push('', '--- 压缩完成 ---');
|
|
434
|
+
|
|
435
|
+
return summary.join('\n');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* 创建智能摘要(简化版)
|
|
440
|
+
*/
|
|
441
|
+
private createIntelligentSummary(messages: ApiMessage[]): string {
|
|
442
|
+
const userMessages = messages.filter(m => m.role === 'user');
|
|
443
|
+
const assistantMessages = messages.filter(m => m.role === 'assistant');
|
|
444
|
+
|
|
445
|
+
let summary = '';
|
|
446
|
+
|
|
447
|
+
if (userMessages.length > 0) {
|
|
448
|
+
summary += `用户发送了${userMessages.length}条消息\n`;
|
|
449
|
+
summary += `主要问题:${this.extractTextContent(userMessages[0].content).substring(0, 100)}...\n`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (assistantMessages.length > 0) {
|
|
453
|
+
summary += `助手回复了${assistantMessages.length}条消息\n`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
summary += `会话主题:基于历史消息推断的主题\n`;
|
|
457
|
+
summary += `关键决策:基于历史消息提取的关键操作\n`;
|
|
458
|
+
|
|
459
|
+
return summary;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* 计算总Token数
|
|
464
|
+
*/
|
|
465
|
+
private calculateTotalTokens(messages: ApiMessage[]): number {
|
|
466
|
+
return messages.reduce((total, message) => {
|
|
467
|
+
const content = this.extractTextContent(message.content);
|
|
468
|
+
return total + this.estimateTokens(content);
|
|
469
|
+
}, 0);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* 估算Token数量
|
|
474
|
+
*/
|
|
475
|
+
private estimateTokens(text: string): number {
|
|
476
|
+
// 中文:1个Token约等于1.5个字符
|
|
477
|
+
// 英文:1个Token约等于4个字符
|
|
478
|
+
// 混合:粗略估算为1个Token约等于2.5个字符
|
|
479
|
+
const chineseCharCount = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
|
|
480
|
+
const englishCharCount = text.length - chineseCharCount;
|
|
481
|
+
return Math.ceil(chineseCharCount / 1.5 + englishCharCount / 4);
|
|
482
|
+
}
|
|
483
|
+
}
|