tabby-ai-assistant 1.0.0

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 (134) hide show
  1. package/README.md +232 -0
  2. package/dist/components/chat/chat-input.component.d.ts +65 -0
  3. package/dist/components/chat/chat-interface.component.d.ts +71 -0
  4. package/dist/components/chat/chat-message.component.d.ts +53 -0
  5. package/dist/components/chat/chat-settings.component.d.ts +62 -0
  6. package/dist/components/common/error-message.component.d.ts +11 -0
  7. package/dist/components/common/loading-spinner.component.d.ts +4 -0
  8. package/dist/components/security/consent-dialog.component.d.ts +11 -0
  9. package/dist/components/security/password-prompt.component.d.ts +10 -0
  10. package/dist/components/security/risk-confirm-dialog.component.d.ts +36 -0
  11. package/dist/components/settings/ai-settings-tab.component.d.ts +72 -0
  12. package/dist/components/settings/general-settings.component.d.ts +60 -0
  13. package/dist/components/settings/provider-config.component.d.ts +182 -0
  14. package/dist/components/settings/security-settings.component.d.ts +23 -0
  15. package/dist/components/terminal/ai-toolbar-button.component.d.ts +10 -0
  16. package/dist/components/terminal/command-preview.component.d.ts +15 -0
  17. package/dist/components/terminal/command-suggestion.component.d.ts +16 -0
  18. package/dist/index.d.ts +8 -0
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.LICENSE.txt +18 -0
  21. package/dist/main.d.ts +8 -0
  22. package/dist/providers/tabby/ai-config.provider.d.ts +18 -0
  23. package/dist/providers/tabby/ai-hotkey.provider.d.ts +21 -0
  24. package/dist/providers/tabby/ai-settings-tab.provider.d.ts +11 -0
  25. package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +17 -0
  26. package/dist/services/chat/chat-history.service.d.ts +67 -0
  27. package/dist/services/chat/chat-session.service.d.ts +58 -0
  28. package/dist/services/chat/command-generator.service.d.ts +49 -0
  29. package/dist/services/core/ai-assistant.service.d.ts +88 -0
  30. package/dist/services/core/ai-provider-manager.service.d.ts +119 -0
  31. package/dist/services/core/config-provider.service.d.ts +137 -0
  32. package/dist/services/core/logger.service.d.ts +21 -0
  33. package/dist/services/providers/anthropic-provider.service.d.ts +39 -0
  34. package/dist/services/providers/base-provider.service.d.ts +137 -0
  35. package/dist/services/providers/glm-provider.service.d.ts +91 -0
  36. package/dist/services/providers/minimax-provider.service.d.ts +93 -0
  37. package/dist/services/providers/openai-compatible.service.d.ts +39 -0
  38. package/dist/services/providers/openai-provider.service.d.ts +38 -0
  39. package/dist/services/security/consent-manager.service.d.ts +65 -0
  40. package/dist/services/security/password-manager.service.d.ts +67 -0
  41. package/dist/services/security/risk-assessment.service.d.ts +65 -0
  42. package/dist/services/security/security-validator.service.d.ts +36 -0
  43. package/dist/services/terminal/command-analyzer.service.d.ts +20 -0
  44. package/dist/services/terminal/context-menu.service.d.ts +24 -0
  45. package/dist/services/terminal/hotkey.service.d.ts +28 -0
  46. package/dist/services/terminal/terminal-context.service.d.ts +100 -0
  47. package/dist/types/ai.types.d.ts +107 -0
  48. package/dist/types/provider.types.d.ts +105 -0
  49. package/dist/types/security.types.d.ts +85 -0
  50. package/dist/types/terminal.types.d.ts +150 -0
  51. package/dist/utils/encryption.utils.d.ts +83 -0
  52. package/dist/utils/formatting.utils.d.ts +106 -0
  53. package/dist/utils/validation.utils.d.ts +83 -0
  54. package/integration-test-output.txt +50 -0
  55. package/integration-tests/api-integration.test.ts +183 -0
  56. package/jest.config.js +47 -0
  57. package/package.json +73 -0
  58. package/setup-jest.ts +37 -0
  59. package/src/components/chat/chat-input.component.html +61 -0
  60. package/src/components/chat/chat-input.component.scss +183 -0
  61. package/src/components/chat/chat-input.component.ts +149 -0
  62. package/src/components/chat/chat-interface.component.html +119 -0
  63. package/src/components/chat/chat-interface.component.scss +354 -0
  64. package/src/components/chat/chat-interface.component.ts +224 -0
  65. package/src/components/chat/chat-message.component.html +65 -0
  66. package/src/components/chat/chat-message.component.scss +178 -0
  67. package/src/components/chat/chat-message.component.ts +93 -0
  68. package/src/components/chat/chat-settings.component.html +132 -0
  69. package/src/components/chat/chat-settings.component.scss +172 -0
  70. package/src/components/chat/chat-settings.component.ts +168 -0
  71. package/src/components/common/error-message.component.ts +124 -0
  72. package/src/components/common/loading-spinner.component.ts +72 -0
  73. package/src/components/security/consent-dialog.component.ts +77 -0
  74. package/src/components/security/password-prompt.component.ts +79 -0
  75. package/src/components/security/risk-confirm-dialog.component.html +87 -0
  76. package/src/components/security/risk-confirm-dialog.component.scss +360 -0
  77. package/src/components/security/risk-confirm-dialog.component.ts +96 -0
  78. package/src/components/settings/ai-settings-tab.component.html +140 -0
  79. package/src/components/settings/ai-settings-tab.component.scss +371 -0
  80. package/src/components/settings/ai-settings-tab.component.ts +193 -0
  81. package/src/components/settings/general-settings.component.html +103 -0
  82. package/src/components/settings/general-settings.component.scss +285 -0
  83. package/src/components/settings/general-settings.component.ts +123 -0
  84. package/src/components/settings/provider-config.component.html +95 -0
  85. package/src/components/settings/provider-config.component.scss +60 -0
  86. package/src/components/settings/provider-config.component.ts +206 -0
  87. package/src/components/settings/security-settings.component.html +51 -0
  88. package/src/components/settings/security-settings.component.scss +66 -0
  89. package/src/components/settings/security-settings.component.ts +71 -0
  90. package/src/components/terminal/ai-toolbar-button.component.ts +49 -0
  91. package/src/components/terminal/command-preview.component.ts +185 -0
  92. package/src/components/terminal/command-suggestion.component.ts +128 -0
  93. package/src/index.ts +163 -0
  94. package/src/main.ts +16 -0
  95. package/src/providers/tabby/ai-config.provider.ts +70 -0
  96. package/src/providers/tabby/ai-hotkey.provider.ts +55 -0
  97. package/src/providers/tabby/ai-settings-tab.provider.ts +18 -0
  98. package/src/providers/tabby/ai-toolbar-button.provider.ts +49 -0
  99. package/src/services/chat/chat-history.service.ts +239 -0
  100. package/src/services/chat/chat-session.service.spec.ts +249 -0
  101. package/src/services/chat/chat-session.service.ts +180 -0
  102. package/src/services/chat/command-generator.service.ts +301 -0
  103. package/src/services/core/ai-assistant.service.ts +334 -0
  104. package/src/services/core/ai-provider-manager.service.ts +314 -0
  105. package/src/services/core/config-provider.service.ts +347 -0
  106. package/src/services/core/logger.service.ts +104 -0
  107. package/src/services/providers/anthropic-provider.service.ts +373 -0
  108. package/src/services/providers/base-provider.service.ts +369 -0
  109. package/src/services/providers/glm-provider.service.ts +467 -0
  110. package/src/services/providers/minimax-provider.service.ts +427 -0
  111. package/src/services/providers/openai-compatible.service.ts +394 -0
  112. package/src/services/providers/openai-provider.service.ts +376 -0
  113. package/src/services/security/consent-manager.service.ts +332 -0
  114. package/src/services/security/password-manager.service.ts +188 -0
  115. package/src/services/security/risk-assessment.service.ts +340 -0
  116. package/src/services/security/security-validator.service.ts +143 -0
  117. package/src/services/terminal/command-analyzer.service.ts +43 -0
  118. package/src/services/terminal/context-menu.service.ts +45 -0
  119. package/src/services/terminal/hotkey.service.ts +53 -0
  120. package/src/services/terminal/terminal-context.service.ts +317 -0
  121. package/src/styles/ai-assistant.scss +449 -0
  122. package/src/types/ai.types.ts +133 -0
  123. package/src/types/provider.types.ts +147 -0
  124. package/src/types/security.types.ts +103 -0
  125. package/src/types/terminal.types.ts +186 -0
  126. package/src/utils/encryption.utils.spec.ts +250 -0
  127. package/src/utils/encryption.utils.ts +271 -0
  128. package/src/utils/formatting.utils.ts +359 -0
  129. package/src/utils/validation.utils.spec.ts +225 -0
  130. package/src/utils/validation.utils.ts +314 -0
  131. package/tsconfig.json +45 -0
  132. package/webpack-docker.config.js +42 -0
  133. package/webpack.config.js +59 -0
  134. package/webpack.config.js.backup +57 -0
@@ -0,0 +1,206 @@
1
+ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
2
+ import { ConfigProviderService } from '../../services/core/config-provider.service';
3
+ import { LoggerService } from '../../services/core/logger.service';
4
+
5
+ @Component({
6
+ selector: 'app-provider-config',
7
+ templateUrl: './provider-config.component.html',
8
+ styleUrls: ['./provider-config.component.scss']
9
+ })
10
+ export class ProviderConfigComponent implements OnInit {
11
+ @Input() providerStatus: any = {};
12
+ @Output() refreshStatus = new EventEmitter<void>();
13
+ @Output() switchProvider = new EventEmitter<string>();
14
+
15
+ selectedProvider = '';
16
+ configs: { [key: string]: any } = {};
17
+
18
+ // 预定义的提供商模板
19
+ providerTemplates = {
20
+ 'openai': {
21
+ name: 'OpenAI',
22
+ description: 'OpenAI GPT模型',
23
+ fields: [
24
+ { key: 'apiKey', label: 'API Key', type: 'password', required: true },
25
+ { key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.openai.com/v1', required: false },
26
+ { key: 'model', label: 'Model', type: 'select', options: ['gpt-4', 'gpt-3.5-turbo'], default: 'gpt-4', required: false }
27
+ ]
28
+ },
29
+ 'anthropic': {
30
+ name: 'Anthropic Claude',
31
+ description: 'Anthropic Claude模型',
32
+ fields: [
33
+ { key: 'apiKey', label: 'API Key', type: 'password', required: true },
34
+ { key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.anthropic.com', required: false },
35
+ { key: 'model', label: 'Model', type: 'select', options: ['claude-3-opus', 'claude-3-sonnet'], default: 'claude-3-sonnet', required: false }
36
+ ]
37
+ },
38
+ 'minimax': {
39
+ name: 'Minimax',
40
+ description: 'Minimax AI模型',
41
+ fields: [
42
+ { key: 'apiKey', label: 'API Key', type: 'password', required: true },
43
+ { key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.minimaxi.com/anthropic', required: false },
44
+ { key: 'model', label: 'Model', type: 'select', options: ['MiniMax-M2', 'MiniMax-M2-Stable'], default: 'MiniMax-M2', required: false }
45
+ ]
46
+ },
47
+ 'glm': {
48
+ name: 'GLM (ChatGLM)',
49
+ description: '智谱AI ChatGLM模型',
50
+ fields: [
51
+ { key: 'apiKey', label: 'API Key', type: 'password', required: true },
52
+ { key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://open.bigmodel.cn/api/paas/v4', required: false },
53
+ { key: 'model', label: 'Model', type: 'select', options: ['glm-4', 'glm-4-air', 'chatglm4'], default: 'glm-4', required: false }
54
+ ]
55
+ }
56
+ };
57
+
58
+ constructor(
59
+ private config: ConfigProviderService,
60
+ private logger: LoggerService
61
+ ) {}
62
+
63
+ ngOnInit(): void {
64
+ this.loadConfigs();
65
+ }
66
+
67
+ /**
68
+ * 加载配置
69
+ */
70
+ private loadConfigs(): void {
71
+ const allConfigs = this.config.getAllProviderConfigs();
72
+ this.configs = allConfigs;
73
+ this.selectedProvider = this.config.getDefaultProvider();
74
+ }
75
+
76
+ /**
77
+ * 保存配置
78
+ */
79
+ saveConfig(providerName: string): void {
80
+ const providerConfig = this.configs[providerName];
81
+ if (providerConfig) {
82
+ this.config.setProviderConfig(providerName, providerConfig);
83
+ this.logger.info('Provider config saved', { provider: providerName });
84
+ }
85
+ }
86
+
87
+ /**
88
+ * 添加提供商
89
+ */
90
+ addProvider(providerName: string): void {
91
+ if (!this.configs[providerName]) {
92
+ const template = this.providerTemplates[providerName];
93
+ if (template) {
94
+ const newConfig = {
95
+ name: providerName,
96
+ displayName: template.name,
97
+ enabled: true,
98
+ ...this.createDefaultConfig(template.fields)
99
+ };
100
+ this.configs[providerName] = newConfig;
101
+ this.saveConfig(providerName);
102
+ }
103
+ }
104
+ }
105
+
106
+ /**
107
+ * 删除提供商
108
+ */
109
+ removeProvider(providerName: string): void {
110
+ if (confirm(`确定要删除 ${this.providerTemplates[providerName]?.name} 的配置吗?`)) {
111
+ delete this.configs[providerName];
112
+ this.config.deleteProviderConfig(providerName);
113
+ this.logger.info('Provider config removed', { provider: providerName });
114
+ }
115
+ }
116
+
117
+ /**
118
+ * 切换提供商启用状态
119
+ */
120
+ toggleProviderEnabled(providerName: string): void {
121
+ if (this.configs[providerName]) {
122
+ this.configs[providerName].enabled = !this.configs[providerName].enabled;
123
+ this.saveConfig(providerName);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * 测试连接
129
+ */
130
+ async testConnection(providerName: string): Promise<void> {
131
+ // TODO: 实现连接测试
132
+ alert(`正在测试 ${this.providerTemplates[providerName]?.name} 的连接...`);
133
+ }
134
+
135
+ /**
136
+ * 创建默认配置
137
+ */
138
+ private createDefaultConfig(fields: any[]): any {
139
+ const config: any = {};
140
+ fields.forEach(field => {
141
+ if (field.default !== undefined) {
142
+ config[field.key] = field.default;
143
+ }
144
+ });
145
+ return config;
146
+ }
147
+
148
+ /**
149
+ * 获取字段类型
150
+ */
151
+ getFieldType(field: any): string {
152
+ return field.type || 'text';
153
+ }
154
+
155
+ /**
156
+ * 获取选项
157
+ */
158
+ getFieldOptions(field: any): string[] {
159
+ return field.options || [];
160
+ }
161
+
162
+ /**
163
+ * 检查是否是密码字段
164
+ */
165
+ isPasswordField(field: any): boolean {
166
+ return field.type === 'password';
167
+ }
168
+
169
+ /**
170
+ * 检查是否必填
171
+ */
172
+ isRequired(field: any): boolean {
173
+ return field.required;
174
+ }
175
+
176
+ /**
177
+ * 获取提供商模板
178
+ */
179
+ getProviderTemplate(providerName: string): any {
180
+ return this.providerTemplates[providerName];
181
+ }
182
+
183
+ /**
184
+ * 检查是否有配置
185
+ */
186
+ hasConfig(providerName: string): boolean {
187
+ return !!this.configs[providerName];
188
+ }
189
+
190
+ /**
191
+ * 获取配置值
192
+ */
193
+ getConfigValue(providerName: string, key: string, defaultValue: any = ''): any {
194
+ return this.configs[providerName]?.[key] ?? defaultValue;
195
+ }
196
+
197
+ /**
198
+ * 更新配置值
199
+ */
200
+ updateConfigValue(providerName: string, key: string, value: any): void {
201
+ if (!this.configs[providerName]) {
202
+ this.configs[providerName] = {};
203
+ }
204
+ this.configs[providerName][key] = value;
205
+ }
206
+ }
@@ -0,0 +1,51 @@
1
+ <div class="security-settings">
2
+ <h3>Security Settings</h3>
3
+
4
+ <div class="form-group">
5
+ <label class="form-label">
6
+ <input type="checkbox" [(ngModel)]="enablePasswordProtection">
7
+ Enable Password Protection
8
+ </label>
9
+ <small class="form-text text-muted">
10
+ Require password for high-risk commands
11
+ </small>
12
+ </div>
13
+
14
+ <div class="form-group" *ngIf="enablePasswordProtection">
15
+ <label class="form-label">Set Password</label>
16
+ <input type="password" class="form-control" [(ngModel)]="password" placeholder="Enter password">
17
+ </div>
18
+
19
+ <div class="form-group">
20
+ <label class="form-label">
21
+ <input type="checkbox" [(ngModel)]="enableRiskAssessment">
22
+ Enable Risk Assessment
23
+ </label>
24
+ <small class="form-text text-muted">
25
+ Automatically assess command risk levels
26
+ </small>
27
+ </div>
28
+
29
+ <div class="form-group">
30
+ <label class="form-label">Default Risk Level</label>
31
+ <select class="form-control" [(ngModel)]="defaultRiskLevel">
32
+ <option value="low">Low</option>
33
+ <option value="medium">Medium</option>
34
+ <option value="high">High</option>
35
+ </select>
36
+ </div>
37
+
38
+ <div class="form-group">
39
+ <label class="form-label">
40
+ <input type="checkbox" [(ngModel)]="enableConsentPersistence">
41
+ Persist User Consent
42
+ </label>
43
+ <small class="form-text text-muted">
44
+ Remember user consent for 30 days
45
+ </small>
46
+ </div>
47
+
48
+ <button class="btn btn-primary" (click)="saveSettings()">
49
+ Save Settings
50
+ </button>
51
+ </div>
@@ -0,0 +1,66 @@
1
+ /* Security Settings Styles */
2
+ .security-settings {
3
+ h3 {
4
+ margin-bottom: 1.5rem;
5
+ color: #333;
6
+ }
7
+
8
+ .form-group {
9
+ margin-bottom: 1.5rem;
10
+ }
11
+
12
+ .form-label {
13
+ font-weight: 600;
14
+ margin-bottom: 0.5rem;
15
+ display: block;
16
+
17
+ input[type="checkbox"] {
18
+ margin-right: 0.5rem;
19
+ }
20
+ }
21
+
22
+ .form-control {
23
+ width: 100%;
24
+ padding: 0.5rem;
25
+ border: 1px solid #ccc;
26
+ border-radius: 4px;
27
+ font-size: 14px;
28
+
29
+ &:focus {
30
+ outline: none;
31
+ border-color: #007bff;
32
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
33
+ }
34
+ }
35
+
36
+ .form-text {
37
+ display: block;
38
+ margin-top: 0.25rem;
39
+ font-size: 12px;
40
+ color: #6c757d;
41
+ }
42
+
43
+ .btn {
44
+ padding: 0.5rem 1.5rem;
45
+ border: none;
46
+ border-radius: 4px;
47
+ cursor: pointer;
48
+ font-size: 14px;
49
+ font-weight: 600;
50
+ transition: all 0.2s;
51
+
52
+ &.btn-primary {
53
+ background-color: #007bff;
54
+ color: white;
55
+
56
+ &:hover {
57
+ background-color: #0056b3;
58
+ Y(-1px transform: translate);
59
+ }
60
+
61
+ &:active {
62
+ transform: translateY(0);
63
+ }
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,71 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { ConfigProviderService } from '../../services/core/config-provider.service';
3
+ import { LoggerService } from '../../services/core/logger.service';
4
+
5
+ @Component({
6
+ selector: 'app-security-settings',
7
+ templateUrl: './security-settings.component.html',
8
+ styleUrls: ['./security-settings.component.scss']
9
+ })
10
+ export class SecuritySettingsComponent implements OnInit {
11
+ settings = {
12
+ enablePasswordProtection: false,
13
+ consentExpiryDays: 30,
14
+ autoApproveLowRisk: true,
15
+ promptForMediumRisk: true,
16
+ requirePasswordForHighRisk: true,
17
+ enableRiskAssessment: true
18
+ };
19
+
20
+ dangerousPatterns = [
21
+ 'rm -rf /',
22
+ 'sudo rm',
23
+ 'format',
24
+ 'dd if=',
25
+ 'fork('
26
+ ];
27
+
28
+ constructor(
29
+ private config: ConfigProviderService,
30
+ private logger: LoggerService
31
+ ) {}
32
+
33
+ ngOnInit(): void {
34
+ this.loadSettings();
35
+ }
36
+
37
+ private loadSettings(): void {
38
+ const securityConfig = this.config.getSecurityConfig();
39
+ this.settings = { ...this.settings, ...securityConfig };
40
+ }
41
+
42
+ updateSetting(key: string, value: any): void {
43
+ (this.settings as any)[key] = value;
44
+ this.config.updateSecurityConfig({ [key]: value });
45
+ this.logger.debug('Security setting updated', { key, value });
46
+ }
47
+
48
+ addDangerousPattern(pattern: string): void {
49
+ if (pattern && !this.dangerousPatterns.includes(pattern)) {
50
+ this.dangerousPatterns.push(pattern);
51
+ }
52
+ }
53
+
54
+ removeDangerousPattern(index: number): void {
55
+ this.dangerousPatterns.splice(index, 1);
56
+ }
57
+
58
+ resetToDefaults(): void {
59
+ if (confirm('确定要重置安全设置为默认值吗?')) {
60
+ this.settings = {
61
+ enablePasswordProtection: false,
62
+ consentExpiryDays: 30,
63
+ autoApproveLowRisk: true,
64
+ promptForMediumRisk: true,
65
+ requirePasswordForHighRisk: true,
66
+ enableRiskAssessment: true
67
+ };
68
+ this.config.updateSecurityConfig(this.settings);
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,49 @@
1
+ import { Component, Input } from '@angular/core';
2
+
3
+ /**
4
+ * AI工具栏按钮组件
5
+ * 在Tabby工具栏显示AI助手按钮
6
+ */
7
+ @Component({
8
+ selector: 'ai-toolbar-button',
9
+ template: `
10
+ <button
11
+ class="btn btn-sm btn-primary"
12
+ [title]="tooltip"
13
+ (click)="onClick()"
14
+ >
15
+ <i class="fas fa-robot"></i>
16
+ <span class="ms-1" *ngIf="showLabel">{{ label }}</span>
17
+ </button>
18
+ `,
19
+ styles: [`
20
+ :host {
21
+ display: inline-block;
22
+ }
23
+ button {
24
+ padding: 4px 8px;
25
+ border-radius: 4px;
26
+ border: none;
27
+ background: #007bff;
28
+ color: white;
29
+ cursor: pointer;
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 4px;
33
+ transition: background-color 0.2s;
34
+ }
35
+ button:hover {
36
+ background: #0056b3;
37
+ }
38
+ `]
39
+ })
40
+ export class AiToolbarButtonComponent {
41
+ @Input() label: string = 'AI Assistant';
42
+ @Input() tooltip: string = 'Open AI Assistant';
43
+ @Input() showLabel: boolean = true;
44
+
45
+ onClick(): void {
46
+ // TODO: 触发打开AI助手
47
+ console.log('AI Assistant button clicked');
48
+ }
49
+ }
@@ -0,0 +1,185 @@
1
+ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
+ import { CommandResponse } from '../../types/ai.types';
3
+ import { RiskLevel } from '../../types/security.types';
4
+
5
+ @Component({
6
+ selector: 'app-command-preview',
7
+ template: `
8
+ <div class="command-preview" *ngIf="command">
9
+ <div class="preview-header">
10
+ <h4>
11
+ <i class="icon-terminal"></i>
12
+ 命令预览
13
+ </h4>
14
+ <button class="btn-close" (click)="close()">
15
+ <i class="icon-x"></i>
16
+ </button>
17
+ </div>
18
+
19
+ <div class="preview-content">
20
+ <!-- 风险级别 -->
21
+ <div class="risk-indicator" [ngClass]="'risk-' + riskLevel.toLowerCase()">
22
+ <i [class]="getRiskIcon()"></i>
23
+ <span>{{ getRiskText() }}</span>
24
+ </div>
25
+
26
+ <!-- 命令 -->
27
+ <div class="command-section">
28
+ <label>将要执行的命令:</label>
29
+ <div class="command-display">
30
+ <code>{{ command.command }}</code>
31
+ <button class="btn-copy" (click)="copyCommand()" title="复制">
32
+ <i class="icon-copy"></i>
33
+ </button>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- 解释 -->
38
+ <div class="explanation-section">
39
+ <label>命令解释:</label>
40
+ <p>{{ command.explanation }}</p>
41
+ </div>
42
+
43
+ <!-- 置信度 -->
44
+ <div class="confidence-section" *ngIf="command.confidence">
45
+ <label>AI置信度:</label>
46
+ <div class="confidence-meter">
47
+ <div class="confidence-bar" [style.width.%]="command.confidence * 100"></div>
48
+ <span class="confidence-text">{{ (command.confidence * 100).toFixed(0) }}%</span>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- 替代方案 -->
53
+ <div class="alternatives-section" *ngIf="command.alternatives && command.alternatives.length > 0">
54
+ <label>替代方案:</label>
55
+ <div class="alternatives-list">
56
+ <div
57
+ *ngFor="let alt of command.alternatives"
58
+ class="alternative-item"
59
+ (click)="selectAlternative(alt)">
60
+ <code>{{ alt.command }}</code>
61
+ <span class="alt-confidence">{{ (alt.confidence * 100).toFixed(0) }}%</span>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="preview-footer">
68
+ <button class="btn btn-secondary" (click)="close()">取消</button>
69
+ <button class="btn btn-primary" (click)="execute()">执行命令</button>
70
+ </div>
71
+ </div>
72
+ `,
73
+ styles: [`
74
+ .command-preview {
75
+ background: var(--ai-bg-primary);
76
+ border: 1px solid var(--ai-border);
77
+ border-radius: 0.5rem;
78
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
79
+ max-width: 600px;
80
+ }
81
+ .preview-header {
82
+ display: flex;
83
+ justify-content: space-between;
84
+ align-items: center;
85
+ padding: 1rem;
86
+ background: var(--ai-bg-secondary);
87
+ border-bottom: 1px solid var(--ai-border);
88
+ }
89
+ .preview-content {
90
+ padding: 1rem;
91
+ max-height: 50vh;
92
+ overflow-y: auto;
93
+ }
94
+ .risk-indicator {
95
+ display: flex;
96
+ align-items: center;
97
+ gap: 0.5rem;
98
+ padding: 0.5rem;
99
+ margin-bottom: 1rem;
100
+ border-radius: 0.375rem;
101
+ }
102
+ .command-section, .explanation-section, .confidence-section, .alternatives-section {
103
+ margin-bottom: 1rem;
104
+ }
105
+ .command-display {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 0.5rem;
109
+ background: var(--ai-bg-secondary);
110
+ padding: 0.75rem;
111
+ border-radius: 0.375rem;
112
+ }
113
+ .alternatives-list {
114
+ background: var(--ai-bg-secondary);
115
+ border-radius: 0.375rem;
116
+ overflow: hidden;
117
+ }
118
+ .alternative-item {
119
+ padding: 0.75rem;
120
+ border-bottom: 1px solid var(--ai-border);
121
+ cursor: pointer;
122
+ transition: background-color 0.2s;
123
+ display: flex;
124
+ justify-content: space-between;
125
+ align-items: center;
126
+ }
127
+ .alternative-item:hover {
128
+ background-color: var(--ai-border);
129
+ }
130
+ .preview-footer {
131
+ padding: 1rem;
132
+ display: flex;
133
+ justify-content: flex-end;
134
+ gap: 0.5rem;
135
+ border-top: 1px solid var(--ai-border);
136
+ }
137
+ `]
138
+ })
139
+ export class CommandPreviewComponent {
140
+ @Input() command: CommandResponse | null = null;
141
+ @Input() riskLevel: RiskLevel = RiskLevel.LOW;
142
+
143
+ @Output() executed = new EventEmitter<CommandResponse>();
144
+ @Output() closed = new EventEmitter<void>();
145
+
146
+ getRiskText(): string {
147
+ switch (this.riskLevel) {
148
+ case RiskLevel.LOW: return '低风险';
149
+ case RiskLevel.MEDIUM: return '中风险';
150
+ case RiskLevel.HIGH: return '高风险';
151
+ case RiskLevel.CRITICAL: return '极风险';
152
+ default: return '未知风险';
153
+ }
154
+ }
155
+
156
+ getRiskIcon(): string {
157
+ switch (this.riskLevel) {
158
+ case RiskLevel.LOW: return 'icon-check-circle';
159
+ case RiskLevel.MEDIUM: return 'icon-warning';
160
+ case RiskLevel.HIGH: return 'icon-alert-triangle';
161
+ case RiskLevel.CRITICAL: return 'icon-alert-octagon';
162
+ default: return 'icon-help-circle';
163
+ }
164
+ }
165
+
166
+ copyCommand(): void {
167
+ if (this.command) {
168
+ navigator.clipboard.writeText(this.command.command);
169
+ }
170
+ }
171
+
172
+ execute(): void {
173
+ if (this.command) {
174
+ this.executed.emit(this.command);
175
+ }
176
+ }
177
+
178
+ close(): void {
179
+ this.closed.emit();
180
+ }
181
+
182
+ selectAlternative(alt: any): void {
183
+ // TODO: 实现选择替代方案
184
+ }
185
+ }