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.
Files changed (116) hide show
  1. package/dist/components/chat/ai-sidebar.component.d.ts +147 -0
  2. package/dist/components/chat/chat-interface.component.d.ts +38 -6
  3. package/dist/components/settings/general-settings.component.d.ts +6 -3
  4. package/dist/components/settings/provider-config.component.d.ts +25 -12
  5. package/dist/components/terminal/command-preview.component.d.ts +38 -0
  6. package/dist/index-full.d.ts +8 -0
  7. package/dist/index-minimal.d.ts +3 -0
  8. package/dist/index.d.ts +7 -3
  9. package/dist/index.js +1 -2
  10. package/dist/providers/tabby/ai-config.provider.d.ts +57 -5
  11. package/dist/providers/tabby/ai-hotkey.provider.d.ts +8 -14
  12. package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +8 -9
  13. package/dist/services/chat/ai-sidebar.service.d.ts +89 -0
  14. package/dist/services/chat/chat-history.service.d.ts +78 -0
  15. package/dist/services/chat/chat-session.service.d.ts +57 -2
  16. package/dist/services/context/compaction.d.ts +90 -0
  17. package/dist/services/context/manager.d.ts +69 -0
  18. package/dist/services/context/memory.d.ts +116 -0
  19. package/dist/services/context/token-budget.d.ts +105 -0
  20. package/dist/services/core/ai-assistant.service.d.ts +40 -1
  21. package/dist/services/core/checkpoint.service.d.ts +130 -0
  22. package/dist/services/platform/escape-sequence.service.d.ts +132 -0
  23. package/dist/services/platform/platform-detection.service.d.ts +146 -0
  24. package/dist/services/providers/anthropic-provider.service.d.ts +5 -0
  25. package/dist/services/providers/base-provider.service.d.ts +6 -1
  26. package/dist/services/providers/glm-provider.service.d.ts +5 -0
  27. package/dist/services/providers/minimax-provider.service.d.ts +10 -1
  28. package/dist/services/providers/ollama-provider.service.d.ts +76 -0
  29. package/dist/services/providers/openai-compatible.service.d.ts +5 -0
  30. package/dist/services/providers/openai-provider.service.d.ts +5 -0
  31. package/dist/services/providers/vllm-provider.service.d.ts +82 -0
  32. package/dist/services/terminal/buffer-analyzer.service.d.ts +128 -0
  33. package/dist/services/terminal/terminal-manager.service.d.ts +185 -0
  34. package/dist/services/terminal/terminal-tools.service.d.ts +79 -0
  35. package/dist/types/ai.types.d.ts +92 -0
  36. package/dist/types/provider.types.d.ts +1 -1
  37. package/package.json +7 -10
  38. package/src/components/chat/ai-sidebar.component.ts +945 -0
  39. package/src/components/chat/chat-input.component.html +9 -24
  40. package/src/components/chat/chat-input.component.scss +3 -2
  41. package/src/components/chat/chat-interface.component.html +77 -69
  42. package/src/components/chat/chat-interface.component.scss +54 -4
  43. package/src/components/chat/chat-interface.component.ts +250 -34
  44. package/src/components/chat/chat-settings.component.scss +4 -4
  45. package/src/components/chat/chat-settings.component.ts +22 -11
  46. package/src/components/common/error-message.component.html +15 -0
  47. package/src/components/common/error-message.component.scss +77 -0
  48. package/src/components/common/error-message.component.ts +2 -96
  49. package/src/components/common/loading-spinner.component.html +4 -0
  50. package/src/components/common/loading-spinner.component.scss +57 -0
  51. package/src/components/common/loading-spinner.component.ts +2 -63
  52. package/src/components/security/consent-dialog.component.html +22 -0
  53. package/src/components/security/consent-dialog.component.scss +34 -0
  54. package/src/components/security/consent-dialog.component.ts +2 -55
  55. package/src/components/security/password-prompt.component.html +19 -0
  56. package/src/components/security/password-prompt.component.scss +30 -0
  57. package/src/components/security/password-prompt.component.ts +2 -54
  58. package/src/components/security/risk-confirm-dialog.component.html +8 -12
  59. package/src/components/security/risk-confirm-dialog.component.scss +8 -5
  60. package/src/components/security/risk-confirm-dialog.component.ts +6 -6
  61. package/src/components/settings/ai-settings-tab.component.html +16 -20
  62. package/src/components/settings/ai-settings-tab.component.scss +8 -5
  63. package/src/components/settings/ai-settings-tab.component.ts +12 -12
  64. package/src/components/settings/general-settings.component.html +8 -17
  65. package/src/components/settings/general-settings.component.scss +6 -3
  66. package/src/components/settings/general-settings.component.ts +62 -22
  67. package/src/components/settings/provider-config.component.html +19 -39
  68. package/src/components/settings/provider-config.component.scss +182 -39
  69. package/src/components/settings/provider-config.component.ts +119 -7
  70. package/src/components/settings/security-settings.component.scss +1 -1
  71. package/src/components/terminal/ai-toolbar-button.component.html +8 -0
  72. package/src/components/terminal/ai-toolbar-button.component.scss +20 -0
  73. package/src/components/terminal/ai-toolbar-button.component.ts +2 -30
  74. package/src/components/terminal/command-preview.component.html +61 -0
  75. package/src/components/terminal/command-preview.component.scss +72 -0
  76. package/src/components/terminal/command-preview.component.ts +127 -140
  77. package/src/components/terminal/command-suggestion.component.html +23 -0
  78. package/src/components/terminal/command-suggestion.component.scss +55 -0
  79. package/src/components/terminal/command-suggestion.component.ts +2 -77
  80. package/src/index-minimal.ts +32 -0
  81. package/src/index.ts +94 -11
  82. package/src/index.ts.backup +165 -0
  83. package/src/providers/tabby/ai-config.provider.ts +60 -51
  84. package/src/providers/tabby/ai-hotkey.provider.ts +23 -39
  85. package/src/providers/tabby/ai-settings-tab.provider.ts +2 -2
  86. package/src/providers/tabby/ai-toolbar-button.provider.ts +29 -24
  87. package/src/services/chat/ai-sidebar.service.ts +258 -0
  88. package/src/services/chat/chat-history.service.ts +308 -0
  89. package/src/services/chat/chat-history.service.ts.backup +239 -0
  90. package/src/services/chat/chat-session.service.ts +276 -3
  91. package/src/services/context/compaction.ts +483 -0
  92. package/src/services/context/manager.ts +442 -0
  93. package/src/services/context/memory.ts +519 -0
  94. package/src/services/context/token-budget.ts +422 -0
  95. package/src/services/core/ai-assistant.service.ts +280 -5
  96. package/src/services/core/ai-provider-manager.service.ts +2 -2
  97. package/src/services/core/checkpoint.service.ts +619 -0
  98. package/src/services/platform/escape-sequence.service.ts +499 -0
  99. package/src/services/platform/platform-detection.service.ts +494 -0
  100. package/src/services/providers/anthropic-provider.service.ts +28 -1
  101. package/src/services/providers/base-provider.service.ts +7 -1
  102. package/src/services/providers/glm-provider.service.ts +28 -1
  103. package/src/services/providers/minimax-provider.service.ts +209 -11
  104. package/src/services/providers/ollama-provider.service.ts +445 -0
  105. package/src/services/providers/openai-compatible.service.ts +9 -0
  106. package/src/services/providers/openai-provider.service.ts +9 -0
  107. package/src/services/providers/vllm-provider.service.ts +463 -0
  108. package/src/services/security/risk-assessment.service.ts +6 -2
  109. package/src/services/terminal/buffer-analyzer.service.ts +594 -0
  110. package/src/services/terminal/terminal-manager.service.ts +748 -0
  111. package/src/services/terminal/terminal-tools.service.ts +441 -0
  112. package/src/styles/ai-assistant.scss +78 -6
  113. package/src/types/ai.types.ts +144 -0
  114. package/src/types/provider.types.ts +1 -1
  115. package/tsconfig.json +9 -9
  116. package/webpack.config.js +28 -6
@@ -0,0 +1,441 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { TerminalManagerService, TerminalInfo } from './terminal-manager.service';
3
+ import { LoggerService } from '../core/logger.service';
4
+
5
+ /**
6
+ * 终端工具定义
7
+ */
8
+ export interface ToolDefinition {
9
+ name: string;
10
+ description: string;
11
+ input_schema: {
12
+ type: string;
13
+ properties: Record<string, any>;
14
+ required?: string[];
15
+ };
16
+ }
17
+
18
+ /**
19
+ * 工具调用请求
20
+ */
21
+ export interface ToolCall {
22
+ id: string;
23
+ name: string;
24
+ input: Record<string, any>;
25
+ }
26
+
27
+ /**
28
+ * 工具调用结果
29
+ */
30
+ export interface ToolResult {
31
+ tool_use_id: string;
32
+ content: string;
33
+ is_error?: boolean;
34
+ }
35
+
36
+ /**
37
+ * 终端工具服务
38
+ * 定义 AI 可调用的终端相关工具
39
+ */
40
+ @Injectable({ providedIn: 'root' })
41
+ export class TerminalToolsService {
42
+ // 工具定义
43
+ private tools: ToolDefinition[] = [
44
+ {
45
+ name: 'read_terminal_output',
46
+ description: '读取指定终端的最近输出内容。用于获取命令执行结果或终端状态。',
47
+ input_schema: {
48
+ type: 'object',
49
+ properties: {
50
+ lines: {
51
+ type: 'number',
52
+ description: '要读取的行数,默认为 50'
53
+ },
54
+ terminal_index: {
55
+ type: 'number',
56
+ description: '目标终端索引。如不指定则读取活动终端。'
57
+ }
58
+ },
59
+ required: []
60
+ }
61
+ },
62
+ {
63
+ name: 'write_to_terminal',
64
+ description: '向终端写入命令。可以指定终端索引或使用当前活动终端。',
65
+ input_schema: {
66
+ type: 'object',
67
+ properties: {
68
+ command: {
69
+ type: 'string',
70
+ description: '要写入的命令'
71
+ },
72
+ execute: {
73
+ type: 'boolean',
74
+ description: '是否立即执行命令(添加回车),默认为 true'
75
+ },
76
+ terminal_index: {
77
+ type: 'number',
78
+ description: '目标终端索引(从 0 开始)。如不指定则使用当前活动终端。'
79
+ }
80
+ },
81
+ required: ['command']
82
+ }
83
+ },
84
+ {
85
+ name: 'get_terminal_list',
86
+ description: '获取所有打开的终端列表,包括终端 ID、标题、活动状态等。',
87
+ input_schema: {
88
+ type: 'object',
89
+ properties: {},
90
+ required: []
91
+ }
92
+ },
93
+ {
94
+ name: 'get_terminal_cwd',
95
+ description: '获取当前终端的工作目录。',
96
+ input_schema: {
97
+ type: 'object',
98
+ properties: {},
99
+ required: []
100
+ }
101
+ },
102
+ {
103
+ name: 'get_terminal_selection',
104
+ description: '获取当前终端中选中的文本。',
105
+ input_schema: {
106
+ type: 'object',
107
+ properties: {},
108
+ required: []
109
+ }
110
+ },
111
+ {
112
+ name: 'focus_terminal',
113
+ description: '切换到指定索引的终端,使其成为活动终端。',
114
+ input_schema: {
115
+ type: 'object',
116
+ properties: {
117
+ terminal_index: {
118
+ type: 'number',
119
+ description: '目标终端索引(从 0 开始)'
120
+ }
121
+ },
122
+ required: ['terminal_index']
123
+ }
124
+ }
125
+ ];
126
+
127
+ // 终端输出缓存
128
+ private outputBuffer: string[] = [];
129
+ private maxBufferLines = 500;
130
+
131
+ constructor(
132
+ private terminalManager: TerminalManagerService,
133
+ private logger: LoggerService
134
+ ) {
135
+ // 不再需要静态订阅输出,直接从 xterm buffer 动态读取
136
+ }
137
+
138
+ /**
139
+ * 获取所有工具定义
140
+ */
141
+ getToolDefinitions(): ToolDefinition[] {
142
+ return this.tools;
143
+ }
144
+
145
+ /**
146
+ * 执行工具调用
147
+ */
148
+ async executeToolCall(toolCall: ToolCall): Promise<ToolResult> {
149
+ this.logger.info('Executing tool call', { name: toolCall.name, input: toolCall.input });
150
+
151
+ try {
152
+ let result: string;
153
+
154
+ switch (toolCall.name) {
155
+ case 'read_terminal_output':
156
+ result = this.readTerminalOutput(
157
+ toolCall.input.lines || 50,
158
+ toolCall.input.terminal_index
159
+ );
160
+ break;
161
+ case 'write_to_terminal':
162
+ result = await this.writeToTerminal(
163
+ toolCall.input.command,
164
+ toolCall.input.execute ?? true,
165
+ toolCall.input.terminal_index
166
+ );
167
+ break;
168
+ case 'get_terminal_list':
169
+ result = this.getTerminalList();
170
+ break;
171
+ case 'get_terminal_cwd':
172
+ result = this.getTerminalCwd();
173
+ break;
174
+ case 'get_terminal_selection':
175
+ result = this.getTerminalSelection();
176
+ break;
177
+ case 'focus_terminal':
178
+ result = this.focusTerminal(toolCall.input.terminal_index);
179
+ break;
180
+ default:
181
+ throw new Error(`Unknown tool: ${toolCall.name}`);
182
+ }
183
+
184
+ this.logger.info('Tool call completed', { name: toolCall.name, resultLength: result.length });
185
+
186
+ return {
187
+ tool_use_id: toolCall.id,
188
+ content: result
189
+ };
190
+ } catch (error) {
191
+ const errorMessage = error instanceof Error ? error.message : String(error);
192
+ this.logger.error('Tool call failed', { name: toolCall.name, error: errorMessage });
193
+
194
+ return {
195
+ tool_use_id: toolCall.id,
196
+ content: `错误: ${errorMessage}`,
197
+ is_error: true
198
+ };
199
+ }
200
+ }
201
+
202
+ /**
203
+ * 从 xterm buffer 读取内容
204
+ * 包含详细调试日志以定位白屏问题
205
+ */
206
+ private readFromXtermBuffer(terminal: any, lines: number): string {
207
+ try {
208
+ // === 调试代码:记录终端结构 ===
209
+ this.logger.info('【DEBUG】Terminal structure debug', {
210
+ hasTerminal: !!terminal,
211
+ terminalType: terminal?.constructor?.name,
212
+ hasFrontend: !!terminal?.frontend,
213
+ frontendType: terminal?.frontend?.constructor?.name,
214
+ frontendKeys: terminal?.frontend ? Object.keys(terminal.frontend).slice(0, 15) : [],
215
+ hasXterm: !!terminal?.frontend?.xterm,
216
+ xtermType: terminal?.frontend?.xterm?.constructor?.name,
217
+ hasBuffer: !!terminal?.frontend?.xterm?.buffer,
218
+ bufferActive: !!terminal?.frontend?.xterm?.buffer?.active
219
+ });
220
+
221
+ // 尝试多种可能的 xterm buffer 访问路径
222
+ let buffer: any = null;
223
+ let bufferSource = '';
224
+
225
+ // 路径1: frontend.xterm.buffer.active (xterm.js 标准)
226
+ if (terminal.frontend?.xterm?.buffer?.active) {
227
+ buffer = terminal.frontend.xterm.buffer.active;
228
+ bufferSource = 'frontend.xterm.buffer.active';
229
+ this.logger.info('【DEBUG】Using buffer path: ' + bufferSource);
230
+ }
231
+ // 路径2: frontend.buffer (可能是直接暴露)
232
+ else if (terminal.frontend?.buffer?.active) {
233
+ buffer = terminal.frontend.buffer.active;
234
+ bufferSource = 'frontend.buffer.active';
235
+ this.logger.info('【DEBUG】Using buffer path: ' + bufferSource);
236
+ }
237
+ // 路径3: frontend._core.buffer (私有属性)
238
+ else if (terminal.frontend?._core?.buffer?.active) {
239
+ buffer = terminal.frontend._core.buffer.active;
240
+ bufferSource = 'frontend._core.buffer.active';
241
+ this.logger.info('【DEBUG】Using buffer path: ' + bufferSource);
242
+ }
243
+ // 路径4: 尝试通过 terminal 上的其他属性
244
+ else {
245
+ this.logger.warn('【DEBUG】No standard buffer path found, trying alternatives', {
246
+ hasContent: !!terminal.content,
247
+ hasContent$: !!terminal.content$,
248
+ hasSession: !!terminal.session,
249
+ allFrontendKeys: terminal?.frontend ? Object.keys(terminal.frontend) : []
250
+ });
251
+
252
+ // 如果有 content 属性,尝试使用它
253
+ if (terminal.content) {
254
+ return `[DEBUG] 终端内容:\n${terminal.content}`;
255
+ }
256
+
257
+ return '(无法访问终端 buffer,请检查终端是否就绪)';
258
+ }
259
+
260
+ if (!buffer) {
261
+ this.logger.warn('【DEBUG】Buffer is null after all path attempts');
262
+ return '(无法访问终端 buffer,buffer 为空)';
263
+ }
264
+
265
+ const totalLines = buffer.length || 0;
266
+ this.logger.info('【DEBUG】Buffer info', {
267
+ totalLines,
268
+ requestedLines: lines,
269
+ bufferSource
270
+ });
271
+
272
+ if (totalLines === 0) {
273
+ return '(终端 buffer 为空)';
274
+ }
275
+
276
+ const startLine = Math.max(0, totalLines - lines);
277
+ const result: string[] = [];
278
+
279
+ for (let i = startLine; i < totalLines; i++) {
280
+ try {
281
+ const line = buffer.getLine(i);
282
+ if (line && typeof line.translateToString === 'function') {
283
+ result.push(line.translateToString(true));
284
+ }
285
+ } catch (e) {
286
+ this.logger.warn('【DEBUG】Failed to read line ' + i, e);
287
+ // 跳过无法读取的行
288
+ }
289
+ }
290
+
291
+ const finalOutput = result.join('\n') || '(终端输出为空)';
292
+ this.logger.info('【DEBUG】Read completed', {
293
+ linesRead: result.length,
294
+ outputLength: finalOutput.length
295
+ });
296
+
297
+ return finalOutput;
298
+ } catch (error) {
299
+ this.logger.error('【DEBUG】Failed to read xterm buffer', {
300
+ error: error instanceof Error ? error.message : String(error),
301
+ stack: error instanceof Error ? error.stack : ''
302
+ });
303
+ return '(读取终端失败,请重试)';
304
+ }
305
+ }
306
+
307
+ /**
308
+ * 读取终端输出
309
+ */
310
+ private readTerminalOutput(lines: number, terminalIndex?: number): string {
311
+ // 尝试从缓冲区获取
312
+ if (this.outputBuffer.length > 0) {
313
+ const recentLines = this.outputBuffer.slice(-lines);
314
+ return recentLines.join('\n');
315
+ }
316
+
317
+ // 直接从指定终端的 xterm buffer 读取
318
+ const terminals = this.terminalManager.getAllTerminals();
319
+ const terminal = terminalIndex !== undefined
320
+ ? terminals[terminalIndex]
321
+ : this.terminalManager.getActiveTerminal();
322
+
323
+ if (!terminal) {
324
+ return '(无可用终端)';
325
+ }
326
+
327
+ return this.readFromXtermBuffer(terminal, lines);
328
+ }
329
+
330
+ /**
331
+ * 写入终端 - 带执行反馈
332
+ */
333
+ private async writeToTerminal(command: string, execute: boolean, terminalIndex?: number): Promise<string> {
334
+ this.logger.info('writeToTerminal called', { command, execute, terminalIndex });
335
+
336
+ let success: boolean;
337
+ let targetTerminalIndex: number;
338
+
339
+ if (terminalIndex !== undefined) {
340
+ // 向指定索引的终端写入
341
+ this.logger.info('Sending command to terminal index', { terminalIndex });
342
+ success = this.terminalManager.sendCommandToIndex(terminalIndex, command, execute);
343
+ targetTerminalIndex = terminalIndex;
344
+ this.logger.info('sendCommandToIndex result', { success });
345
+ } else {
346
+ // 向当前活动终端写入
347
+ this.logger.info('Sending command to active terminal');
348
+ success = this.terminalManager.sendCommand(command, execute);
349
+ targetTerminalIndex = 0; // 默认活动终端
350
+ this.logger.info('sendCommand result', { success });
351
+ }
352
+
353
+ if (!success) {
354
+ throw new Error(terminalIndex !== undefined
355
+ ? `无法写入终端 ${terminalIndex},索引无效或终端不可用`
356
+ : '无法写入终端,请确保有活动的终端窗口');
357
+ }
358
+
359
+ // 等待命令执行(给终端一些时间处理)
360
+ await new Promise(resolve => setTimeout(resolve, 800));
361
+
362
+ // 直接从 xterm buffer 读取(而非依赖订阅)
363
+ const terminals = this.terminalManager.getAllTerminals();
364
+ const terminal = terminalIndex !== undefined
365
+ ? terminals[terminalIndex]
366
+ : this.terminalManager.getActiveTerminal();
367
+
368
+ let output = '(终端输出为空)';
369
+ if (terminal) {
370
+ output = this.readFromXtermBuffer(terminal, 30);
371
+ }
372
+
373
+ // 返回执行结果而非指导
374
+ return [
375
+ `✅ 命令已执行: ${command}`,
376
+ '',
377
+ '=== 终端输出 ===',
378
+ output,
379
+ '=== 输出结束 ==='
380
+ ].join('\n');
381
+ }
382
+
383
+ /**
384
+ * 获取终端列表
385
+ */
386
+ private getTerminalList(): string {
387
+ const terminals: TerminalInfo[] = this.terminalManager.getAllTerminalInfo();
388
+ if (terminals.length === 0) {
389
+ return '(没有打开的终端)';
390
+ }
391
+
392
+ // 检测操作系统
393
+ const platform = process.platform;
394
+ const isWindows = platform === 'win32';
395
+ const osInfo = isWindows ? 'Windows' : (platform === 'darwin' ? 'macOS' : 'Linux');
396
+
397
+ const list = terminals.map((t, i) =>
398
+ `[${i}] ${t.title}${t.isActive ? ' (活动)' : ''}${t.cwd ? ` - ${t.cwd}` : ''}`
399
+ ).join('\n');
400
+
401
+ return `操作系统: ${osInfo}\n共 ${terminals.length} 个终端:\n${list}\n\n注意: ${isWindows ? '请使用 Windows 命令 (如 dir, cd, type 等)' : '请使用 Unix 命令 (如 ls, cd, cat 等)'}`;
402
+ }
403
+
404
+
405
+
406
+ /**
407
+ * 获取终端工作目录
408
+ */
409
+ private getTerminalCwd(): string {
410
+ const cwd = this.terminalManager.getTerminalCwd();
411
+ if (cwd) {
412
+ return `当前工作目录: ${cwd}`;
413
+ } else {
414
+ return '(无法获取工作目录)';
415
+ }
416
+ }
417
+
418
+ /**
419
+ * 获取终端选中文本
420
+ */
421
+ private getTerminalSelection(): string {
422
+ const selection = this.terminalManager.getSelection();
423
+ if (selection) {
424
+ return selection;
425
+ } else {
426
+ return '(没有选中的文本)';
427
+ }
428
+ }
429
+
430
+ /**
431
+ * 切换终端焦点
432
+ */
433
+ private focusTerminal(index: number): string {
434
+ const success = this.terminalManager.focusTerminal(index);
435
+ if (success) {
436
+ return `✅ 已切换到终端 ${index}`;
437
+ } else {
438
+ return `❌ 无法切换到终端 ${index},索引无效`;
439
+ }
440
+ }
441
+ }
@@ -32,8 +32,10 @@
32
32
  --ai-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
33
33
  }
34
34
 
35
- // 深色主题
36
- .dark-theme {
35
+ // 深色主题 - 支持多种类名
36
+ .dark-theme,
37
+ .ai-theme-dark,
38
+ body.ai-theme-dark {
37
39
  --ai-primary: #4dabf7;
38
40
  --ai-secondary: #adb5bd;
39
41
  --ai-success: #51cf66;
@@ -58,6 +60,34 @@
58
60
  --ai-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.3);
59
61
  }
60
62
 
63
+ // 浅色主题
64
+ .light-theme,
65
+ .ai-theme-light,
66
+ body.ai-theme-light {
67
+ --ai-primary: #007bff;
68
+ --ai-secondary: #6c757d;
69
+ --ai-success: #28a745;
70
+ --ai-warning: #ffc107;
71
+ --ai-danger: #dc3545;
72
+ --ai-info: #17a2b8;
73
+ --ai-light: #f8f9fa;
74
+ --ai-dark: #343a40;
75
+
76
+ --ai-risk-low: #28a745;
77
+ --ai-risk-medium: #ffc107;
78
+ --ai-risk-high: #fd7e14;
79
+ --ai-risk-critical: #dc3545;
80
+
81
+ --ai-user-message: #e3f2fd;
82
+ --ai-assistant-message: #f5f5f5;
83
+ --ai-system-message: #fff3cd;
84
+
85
+ --ai-bg-primary: #ffffff;
86
+ --ai-bg-secondary: #f8f9fa;
87
+ --ai-border: #dee2e6;
88
+ --ai-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
89
+ }
90
+
61
91
  // 通用样式
62
92
  .ai-assistant {
63
93
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
@@ -198,7 +228,7 @@
198
228
  transition: background-color 0.15s ease-in-out;
199
229
 
200
230
  &:hover {
201
- background-color: darken(#007bff, 7.5%);
231
+ background-color: #0069d9;
202
232
  }
203
233
 
204
234
  &:disabled {
@@ -210,7 +240,7 @@
210
240
  background-color: var(--ai-secondary);
211
241
 
212
242
  &:hover {
213
- background-color: darken(#6c757d, 7.5%);
243
+ background-color: #5a6268;
214
244
  }
215
245
  }
216
246
 
@@ -218,7 +248,7 @@
218
248
  background-color: var(--ai-danger);
219
249
 
220
250
  &:hover {
221
- background-color: darken(#dc3545, 7.5%);
251
+ background-color: #c82333;
222
252
  }
223
253
  }
224
254
  }
@@ -294,9 +324,12 @@
294
324
  }
295
325
 
296
326
  @keyframes pulse {
297
- 0%, 100% {
327
+
328
+ 0%,
329
+ 100% {
298
330
  opacity: 1;
299
331
  }
332
+
300
333
  50% {
301
334
  opacity: 0.5;
302
335
  }
@@ -447,3 +480,42 @@
447
480
  gap: 0.5rem;
448
481
  }
449
482
  }
483
+
484
+ // 左侧滑入模态框样式
485
+ .ai-chat-modal-left {
486
+ .modal-dialog {
487
+ position: fixed !important;
488
+ top: 0 !important;
489
+ left: 0 !important;
490
+ bottom: 0 !important;
491
+ margin: 0 !important;
492
+ max-width: 450px !important;
493
+ width: 100% !important;
494
+ height: 100vh !important;
495
+ transform: none !important;
496
+
497
+ .modal-content {
498
+ height: 100% !important;
499
+ border: none !important;
500
+ border-radius: 0 !important;
501
+ background: var(--ai-bg-primary, #1a1a2e) !important;
502
+ display: flex;
503
+ flex-direction: column;
504
+ }
505
+ }
506
+
507
+ // 动画效果
508
+ &.fade .modal-dialog {
509
+ transform: translateX(-100%) !important;
510
+ transition: transform 0.3s ease-out !important;
511
+ }
512
+
513
+ &.show .modal-dialog {
514
+ transform: translateX(0) !important;
515
+ }
516
+
517
+ // 背景遮罩
518
+ .modal-backdrop {
519
+ background-color: rgba(0, 0, 0, 0.3) !important;
520
+ }
521
+ }