tabby-ai-assistant 1.0.13 → 1.0.16

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 (42) hide show
  1. package/.editorconfig +18 -0
  2. package/README.md +40 -10
  3. package/dist/index.js +1 -1
  4. package/package.json +5 -3
  5. package/src/components/chat/ai-sidebar.component.scss +220 -9
  6. package/src/components/chat/ai-sidebar.component.ts +379 -29
  7. package/src/components/chat/chat-input.component.ts +36 -4
  8. package/src/components/chat/chat-interface.component.ts +225 -5
  9. package/src/components/chat/chat-message.component.ts +6 -1
  10. package/src/components/settings/context-settings.component.ts +91 -91
  11. package/src/components/terminal/ai-toolbar-button.component.ts +4 -2
  12. package/src/components/terminal/command-suggestion.component.ts +148 -6
  13. package/src/index.ts +81 -19
  14. package/src/providers/tabby/ai-toolbar-button.provider.ts +7 -3
  15. package/src/services/chat/ai-sidebar.service.ts +448 -410
  16. package/src/services/chat/chat-session.service.ts +36 -12
  17. package/src/services/context/compaction.ts +110 -134
  18. package/src/services/context/manager.ts +27 -7
  19. package/src/services/context/memory.ts +17 -33
  20. package/src/services/context/summary.service.ts +136 -0
  21. package/src/services/core/ai-assistant.service.ts +1060 -37
  22. package/src/services/core/ai-provider-manager.service.ts +154 -25
  23. package/src/services/core/checkpoint.service.ts +218 -18
  24. package/src/services/core/toast.service.ts +106 -106
  25. package/src/services/providers/anthropic-provider.service.ts +126 -30
  26. package/src/services/providers/base-provider.service.ts +90 -7
  27. package/src/services/providers/glm-provider.service.ts +151 -38
  28. package/src/services/providers/minimax-provider.service.ts +55 -40
  29. package/src/services/providers/ollama-provider.service.ts +117 -28
  30. package/src/services/providers/openai-compatible.service.ts +164 -34
  31. package/src/services/providers/openai-provider.service.ts +169 -34
  32. package/src/services/providers/vllm-provider.service.ts +116 -28
  33. package/src/services/terminal/terminal-context.service.ts +265 -5
  34. package/src/services/terminal/terminal-manager.service.ts +845 -748
  35. package/src/services/terminal/terminal-tools.service.ts +612 -441
  36. package/src/types/ai.types.ts +156 -3
  37. package/src/utils/cost.utils.ts +249 -0
  38. package/src/utils/validation.utils.ts +306 -2
  39. package/dist/index.js.LICENSE.txt +0 -18
  40. package/src/services/terminal/command-analyzer.service.ts +0 -43
  41. package/src/services/terminal/context-menu.service.ts +0 -45
  42. package/src/services/terminal/hotkey.service.ts +0 -53
@@ -1,8 +1,20 @@
1
1
  import { Injectable } from '@angular/core';
2
2
  import { Subject, Observable } from 'rxjs';
3
- import { TerminalContext, TerminalSession, TerminalError, CommandResult, SystemInfo, ProcessInfo } from '../../types/terminal.types';
3
+ import { TerminalContext, TerminalSession, TerminalError, CommandResult, SystemInfo, ProcessInfo, ProjectInfo } from '../../types/terminal.types';
4
4
  import { LoggerService } from '../core/logger.service';
5
5
 
6
+ /**
7
+ * 项目检测配置
8
+ */
9
+ interface ProjectDetector {
10
+ pattern: RegExp;
11
+ type: ProjectInfo['type'];
12
+ configFiles: string[];
13
+ parseConfig: (content: string) => Partial<ProjectInfo>;
14
+ language: string;
15
+ framework?: string;
16
+ }
17
+
6
18
  @Injectable({ providedIn: 'root' })
7
19
  export class TerminalContextService {
8
20
  private currentContext: TerminalContext | null = null;
@@ -10,6 +22,127 @@ export class TerminalContextService {
10
22
  private errorDetected$ = new Subject<TerminalError>();
11
23
  private commandExecuted$ = new Subject<CommandResult>();
12
24
 
25
+ // 项目检测器配置
26
+ private readonly projectDetectors: ProjectDetector[] = [
27
+ {
28
+ pattern: /package\.json$/,
29
+ type: 'npm',
30
+ configFiles: ['package.json'],
31
+ parseConfig: (content: string) => {
32
+ try {
33
+ const pkg = JSON.parse(content);
34
+ return {
35
+ name: pkg.name,
36
+ version: pkg.version,
37
+ dependencies: Object.keys(pkg.dependencies || {}),
38
+ scripts: pkg.scripts,
39
+ description: pkg.description,
40
+ framework: pkg.dependencies ? this.detectFramework(Object.keys(pkg.dependencies)) : undefined
41
+ };
42
+ } catch {
43
+ return {};
44
+ }
45
+ },
46
+ language: 'JavaScript/TypeScript'
47
+ },
48
+ {
49
+ pattern: /pom\.xml$/,
50
+ type: 'maven',
51
+ configFiles: ['pom.xml'],
52
+ parseConfig: (content: string) => {
53
+ const nameMatch = content.match(/<artifactId>([^<]+)<\/artifactId>/);
54
+ const versionMatch = content.match(/<version>([^<]+)<\/version>/);
55
+ return {
56
+ name: nameMatch?.[1],
57
+ version: versionMatch?.[1],
58
+ language: 'Java'
59
+ };
60
+ },
61
+ language: 'Java'
62
+ },
63
+ {
64
+ pattern: /build\.gradle$/,
65
+ type: 'gradle',
66
+ configFiles: ['build.gradle', 'build.gradle.kts'],
67
+ parseConfig: (content: string) => {
68
+ const nameMatch = content.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
69
+ const versionMatch = content.match(/version\s*=\s*['"]([^'"]+)['"]/);
70
+ return {
71
+ name: nameMatch?.[1],
72
+ version: versionMatch?.[1],
73
+ language: 'Java/Kotlin'
74
+ };
75
+ },
76
+ language: 'Java/Kotlin'
77
+ },
78
+ {
79
+ pattern: /requirements\.txt$/,
80
+ type: 'pip',
81
+ configFiles: ['requirements.txt'],
82
+ parseConfig: (content: string) => {
83
+ const deps = content.split('\n')
84
+ .map(line => line.split(/[>=<!]/)[0].trim())
85
+ .filter(d => d.length > 0);
86
+ return {
87
+ dependencies: deps,
88
+ language: 'Python',
89
+ framework: this.detectPythonFramework(deps)
90
+ };
91
+ },
92
+ language: 'Python'
93
+ },
94
+ {
95
+ pattern: /Cargo\.toml$/,
96
+ type: 'cargo',
97
+ configFiles: ['Cargo.toml'],
98
+ parseConfig: (content: string) => {
99
+ const nameMatch = content.match(/name\s*=\s*["']([^"']+)["']/);
100
+ const versionMatch = content.match(/version\s*=\s*["']([^"']+)["']/);
101
+ return {
102
+ name: nameMatch?.[1],
103
+ version: versionMatch?.[1],
104
+ language: 'Rust'
105
+ };
106
+ },
107
+ language: 'Rust'
108
+ },
109
+ {
110
+ pattern: /go\.mod$/,
111
+ type: 'go',
112
+ configFiles: ['go.mod'],
113
+ parseConfig: (content: string) => {
114
+ const moduleMatch = content.match(/module\s+([^\s]+)/);
115
+ const versionMatch = content.match(/go\s+([\d.]+)/);
116
+ return {
117
+ name: moduleMatch?.[1],
118
+ version: versionMatch?.[1],
119
+ language: 'Go'
120
+ };
121
+ },
122
+ language: 'Go'
123
+ },
124
+ {
125
+ pattern: /yarn\.lock$/,
126
+ type: 'yarn',
127
+ configFiles: ['package.json', 'yarn.lock'],
128
+ parseConfig: (content: string) => {
129
+ try {
130
+ const pkg = JSON.parse(content);
131
+ return {
132
+ name: pkg.name,
133
+ version: pkg.version,
134
+ dependencies: Object.keys(pkg.dependencies || {}),
135
+ scripts: pkg.scripts,
136
+ description: pkg.description
137
+ };
138
+ } catch {
139
+ return {};
140
+ }
141
+ },
142
+ language: 'JavaScript/TypeScript'
143
+ }
144
+ ];
145
+
13
146
  constructor(private logger: LoggerService) {
14
147
  this.initializeContext();
15
148
  }
@@ -25,7 +158,7 @@ export class TerminalContextService {
25
158
  isRunning: false,
26
159
  recentCommands: [],
27
160
  systemInfo,
28
- projectInfo
161
+ projectInfo: projectInfo || undefined
29
162
  };
30
163
 
31
164
  this.logger.info('Terminal context initialized', { context: this.currentContext });
@@ -244,13 +377,140 @@ export class TerminalContextService {
244
377
 
245
378
  /**
246
379
  * 检测项目信息
380
+ * 根据当前工作目录中的配置文件检测项目类型和元数据
247
381
  */
248
- private async detectProjectInfo(): Promise<any> {
249
- // TODO: 实现项目检测逻辑
250
- // 检测 .git, package.json, pom.xml, build.gradle 等
382
+ async detectProjectInfo(): Promise<ProjectInfo | null> {
383
+ const cwd = this.currentContext?.session.cwd || process.cwd();
384
+
385
+ // 检测 .git 目录(Git 项目)
386
+ const hasGit = await this.checkFileExists('.git');
387
+ if (hasGit) {
388
+ return {
389
+ type: 'git',
390
+ root: cwd,
391
+ name: this.extractProjectName(cwd),
392
+ language: 'N/A'
393
+ };
394
+ }
395
+
396
+ // 遍历项目检测器
397
+ for (const detector of this.projectDetectors) {
398
+ for (const configFile of detector.configFiles) {
399
+ const content = await this.readFileContent(configFile);
400
+ if (content) {
401
+ const config = detector.parseConfig(content);
402
+ return {
403
+ type: detector.type,
404
+ root: cwd,
405
+ name: config.name || this.extractProjectName(cwd),
406
+ version: config.version,
407
+ dependencies: config.dependencies,
408
+ scripts: config.scripts,
409
+ description: config.description,
410
+ language: detector.language,
411
+ framework: config.framework || detector.framework
412
+ };
413
+ }
414
+ }
415
+ }
416
+
417
+ // 未检测到项目
251
418
  return null;
252
419
  }
253
420
 
421
+ /**
422
+ * 手动触发项目重新检测
423
+ */
424
+ async refreshProjectInfo(): Promise<void> {
425
+ if (!this.currentContext) return;
426
+
427
+ const projectInfo = await this.detectProjectInfo();
428
+ this.currentContext.projectInfo = projectInfo || undefined;
429
+ this.contextChange$.next(this.currentContext);
430
+
431
+ this.logger.info('Project info refreshed', { projectInfo });
432
+ }
433
+
434
+ /**
435
+ * 获取所有检测到的项目类型
436
+ */
437
+ getSupportedProjectTypes(): ProjectInfo['type'][] {
438
+ return this.projectDetectors.map(d => d.type);
439
+ }
440
+
441
+ /**
442
+ * 检查文件是否存在(模拟实现)
443
+ */
444
+ private async checkFileExists(_filename: string): Promise<boolean> {
445
+ // 在浏览器环境中,此方法需要与实际的 Tabby API 集成
446
+ // 这里返回模拟值用于演示
447
+ return false;
448
+ }
449
+
450
+ /**
451
+ * 读取文件内容(模拟实现)
452
+ */
453
+ private async readFileContent(_filename: string): Promise<string | null> {
454
+ // 在浏览器环境中,此方法需要与实际的 Tabby API 集成
455
+ // 这里返回模拟值用于演示
456
+ return null;
457
+ }
458
+
459
+ /**
460
+ * 从路径提取项目名称
461
+ */
462
+ private extractProjectName(path: string): string {
463
+ const parts = path.split('/');
464
+ return parts[parts.length - 1] || 'unknown-project';
465
+ }
466
+
467
+ /**
468
+ * 检测 JavaScript/TypeScript 项目框架
469
+ */
470
+ private detectFramework(dependencies: string[]): string | undefined {
471
+ const frameworkIndicators: { [key: string]: string[] } = {
472
+ 'React': ['react', 'react-dom'],
473
+ 'Vue': ['vue'],
474
+ 'Angular': ['@angular/core'],
475
+ 'Next.js': ['next'],
476
+ 'Nuxt': ['nuxt'],
477
+ 'Svelte': ['svelte'],
478
+ 'Express': ['express'],
479
+ 'NestJS': ['@nestjs/core'],
480
+ 'Electron': ['electron'],
481
+ 'Expo': ['expo']
482
+ };
483
+
484
+ for (const [framework, indicators] of Object.entries(frameworkIndicators)) {
485
+ if (indicators.some(ind => dependencies.includes(ind))) {
486
+ return framework;
487
+ }
488
+ }
489
+ return undefined;
490
+ }
491
+
492
+ /**
493
+ * 检测 Python 项目框架
494
+ */
495
+ private detectPythonFramework(dependencies: string[]): string | undefined {
496
+ const frameworkIndicators: { [key: string]: string[] } = {
497
+ 'Django': ['django'],
498
+ 'Flask': ['flask'],
499
+ 'FastAPI': ['fastapi'],
500
+ 'Pyramid': ['pyramid'],
501
+ 'Tornado': ['tornado'],
502
+ 'Web2py': ['web2py'],
503
+ 'CherryPy': ['cherrypy']
504
+ };
505
+
506
+ for (const [framework, indicators] of Object.entries(frameworkIndicators)) {
507
+ if (indicators.some(ind => dependencies.includes(ind))) {
508
+ return framework;
509
+ }
510
+ }
511
+ return undefined;
512
+ }
513
+
254
514
  /**
255
515
  * 检测当前Shell
256
516
  */