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.
- package/README.md +232 -0
- package/dist/components/chat/chat-input.component.d.ts +65 -0
- package/dist/components/chat/chat-interface.component.d.ts +71 -0
- package/dist/components/chat/chat-message.component.d.ts +53 -0
- package/dist/components/chat/chat-settings.component.d.ts +62 -0
- package/dist/components/common/error-message.component.d.ts +11 -0
- package/dist/components/common/loading-spinner.component.d.ts +4 -0
- package/dist/components/security/consent-dialog.component.d.ts +11 -0
- package/dist/components/security/password-prompt.component.d.ts +10 -0
- package/dist/components/security/risk-confirm-dialog.component.d.ts +36 -0
- package/dist/components/settings/ai-settings-tab.component.d.ts +72 -0
- package/dist/components/settings/general-settings.component.d.ts +60 -0
- package/dist/components/settings/provider-config.component.d.ts +182 -0
- package/dist/components/settings/security-settings.component.d.ts +23 -0
- package/dist/components/terminal/ai-toolbar-button.component.d.ts +10 -0
- package/dist/components/terminal/command-preview.component.d.ts +15 -0
- package/dist/components/terminal/command-suggestion.component.d.ts +16 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +2 -0
- package/dist/index.js.LICENSE.txt +18 -0
- package/dist/main.d.ts +8 -0
- package/dist/providers/tabby/ai-config.provider.d.ts +18 -0
- package/dist/providers/tabby/ai-hotkey.provider.d.ts +21 -0
- package/dist/providers/tabby/ai-settings-tab.provider.d.ts +11 -0
- package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +17 -0
- package/dist/services/chat/chat-history.service.d.ts +67 -0
- package/dist/services/chat/chat-session.service.d.ts +58 -0
- package/dist/services/chat/command-generator.service.d.ts +49 -0
- package/dist/services/core/ai-assistant.service.d.ts +88 -0
- package/dist/services/core/ai-provider-manager.service.d.ts +119 -0
- package/dist/services/core/config-provider.service.d.ts +137 -0
- package/dist/services/core/logger.service.d.ts +21 -0
- package/dist/services/providers/anthropic-provider.service.d.ts +39 -0
- package/dist/services/providers/base-provider.service.d.ts +137 -0
- package/dist/services/providers/glm-provider.service.d.ts +91 -0
- package/dist/services/providers/minimax-provider.service.d.ts +93 -0
- package/dist/services/providers/openai-compatible.service.d.ts +39 -0
- package/dist/services/providers/openai-provider.service.d.ts +38 -0
- package/dist/services/security/consent-manager.service.d.ts +65 -0
- package/dist/services/security/password-manager.service.d.ts +67 -0
- package/dist/services/security/risk-assessment.service.d.ts +65 -0
- package/dist/services/security/security-validator.service.d.ts +36 -0
- package/dist/services/terminal/command-analyzer.service.d.ts +20 -0
- package/dist/services/terminal/context-menu.service.d.ts +24 -0
- package/dist/services/terminal/hotkey.service.d.ts +28 -0
- package/dist/services/terminal/terminal-context.service.d.ts +100 -0
- package/dist/types/ai.types.d.ts +107 -0
- package/dist/types/provider.types.d.ts +105 -0
- package/dist/types/security.types.d.ts +85 -0
- package/dist/types/terminal.types.d.ts +150 -0
- package/dist/utils/encryption.utils.d.ts +83 -0
- package/dist/utils/formatting.utils.d.ts +106 -0
- package/dist/utils/validation.utils.d.ts +83 -0
- package/integration-test-output.txt +50 -0
- package/integration-tests/api-integration.test.ts +183 -0
- package/jest.config.js +47 -0
- package/package.json +73 -0
- package/setup-jest.ts +37 -0
- package/src/components/chat/chat-input.component.html +61 -0
- package/src/components/chat/chat-input.component.scss +183 -0
- package/src/components/chat/chat-input.component.ts +149 -0
- package/src/components/chat/chat-interface.component.html +119 -0
- package/src/components/chat/chat-interface.component.scss +354 -0
- package/src/components/chat/chat-interface.component.ts +224 -0
- package/src/components/chat/chat-message.component.html +65 -0
- package/src/components/chat/chat-message.component.scss +178 -0
- package/src/components/chat/chat-message.component.ts +93 -0
- package/src/components/chat/chat-settings.component.html +132 -0
- package/src/components/chat/chat-settings.component.scss +172 -0
- package/src/components/chat/chat-settings.component.ts +168 -0
- package/src/components/common/error-message.component.ts +124 -0
- package/src/components/common/loading-spinner.component.ts +72 -0
- package/src/components/security/consent-dialog.component.ts +77 -0
- package/src/components/security/password-prompt.component.ts +79 -0
- package/src/components/security/risk-confirm-dialog.component.html +87 -0
- package/src/components/security/risk-confirm-dialog.component.scss +360 -0
- package/src/components/security/risk-confirm-dialog.component.ts +96 -0
- package/src/components/settings/ai-settings-tab.component.html +140 -0
- package/src/components/settings/ai-settings-tab.component.scss +371 -0
- package/src/components/settings/ai-settings-tab.component.ts +193 -0
- package/src/components/settings/general-settings.component.html +103 -0
- package/src/components/settings/general-settings.component.scss +285 -0
- package/src/components/settings/general-settings.component.ts +123 -0
- package/src/components/settings/provider-config.component.html +95 -0
- package/src/components/settings/provider-config.component.scss +60 -0
- package/src/components/settings/provider-config.component.ts +206 -0
- package/src/components/settings/security-settings.component.html +51 -0
- package/src/components/settings/security-settings.component.scss +66 -0
- package/src/components/settings/security-settings.component.ts +71 -0
- package/src/components/terminal/ai-toolbar-button.component.ts +49 -0
- package/src/components/terminal/command-preview.component.ts +185 -0
- package/src/components/terminal/command-suggestion.component.ts +128 -0
- package/src/index.ts +163 -0
- package/src/main.ts +16 -0
- package/src/providers/tabby/ai-config.provider.ts +70 -0
- package/src/providers/tabby/ai-hotkey.provider.ts +55 -0
- package/src/providers/tabby/ai-settings-tab.provider.ts +18 -0
- package/src/providers/tabby/ai-toolbar-button.provider.ts +49 -0
- package/src/services/chat/chat-history.service.ts +239 -0
- package/src/services/chat/chat-session.service.spec.ts +249 -0
- package/src/services/chat/chat-session.service.ts +180 -0
- package/src/services/chat/command-generator.service.ts +301 -0
- package/src/services/core/ai-assistant.service.ts +334 -0
- package/src/services/core/ai-provider-manager.service.ts +314 -0
- package/src/services/core/config-provider.service.ts +347 -0
- package/src/services/core/logger.service.ts +104 -0
- package/src/services/providers/anthropic-provider.service.ts +373 -0
- package/src/services/providers/base-provider.service.ts +369 -0
- package/src/services/providers/glm-provider.service.ts +467 -0
- package/src/services/providers/minimax-provider.service.ts +427 -0
- package/src/services/providers/openai-compatible.service.ts +394 -0
- package/src/services/providers/openai-provider.service.ts +376 -0
- package/src/services/security/consent-manager.service.ts +332 -0
- package/src/services/security/password-manager.service.ts +188 -0
- package/src/services/security/risk-assessment.service.ts +340 -0
- package/src/services/security/security-validator.service.ts +143 -0
- package/src/services/terminal/command-analyzer.service.ts +43 -0
- package/src/services/terminal/context-menu.service.ts +45 -0
- package/src/services/terminal/hotkey.service.ts +53 -0
- package/src/services/terminal/terminal-context.service.ts +317 -0
- package/src/styles/ai-assistant.scss +449 -0
- package/src/types/ai.types.ts +133 -0
- package/src/types/provider.types.ts +147 -0
- package/src/types/security.types.ts +103 -0
- package/src/types/terminal.types.ts +186 -0
- package/src/utils/encryption.utils.spec.ts +250 -0
- package/src/utils/encryption.utils.ts +271 -0
- package/src/utils/formatting.utils.ts +359 -0
- package/src/utils/validation.utils.spec.ts +225 -0
- package/src/utils/validation.utils.ts +314 -0
- package/tsconfig.json +45 -0
- package/webpack-docker.config.js +42 -0
- package/webpack.config.js +59 -0
- package/webpack.config.js.backup +57 -0
|
@@ -0,0 +1,168 @@
|
|
|
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-chat-settings',
|
|
7
|
+
templateUrl: './chat-settings.component.html',
|
|
8
|
+
styleUrls: ['./chat-settings.component.scss']
|
|
9
|
+
})
|
|
10
|
+
export class ChatSettingsComponent implements OnInit {
|
|
11
|
+
settings = {
|
|
12
|
+
chatHistoryEnabled: true,
|
|
13
|
+
maxChatHistory: 100,
|
|
14
|
+
autoSaveChat: true,
|
|
15
|
+
theme: 'auto',
|
|
16
|
+
fontSize: 14,
|
|
17
|
+
compactMode: false,
|
|
18
|
+
showTimestamps: true,
|
|
19
|
+
showAvatars: true,
|
|
20
|
+
enterToSend: true,
|
|
21
|
+
soundEnabled: true
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
themes = [
|
|
25
|
+
{ value: 'auto', label: '跟随系统' },
|
|
26
|
+
{ value: 'light', label: '浅色主题' },
|
|
27
|
+
{ value: 'dark', label: '深色主题' }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
fontSizes = [12, 14, 16, 18, 20];
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
private config: ConfigProviderService,
|
|
34
|
+
private logger: LoggerService
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
ngOnInit(): void {
|
|
38
|
+
this.loadSettings();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 加载设置
|
|
43
|
+
*/
|
|
44
|
+
private loadSettings(): void {
|
|
45
|
+
this.settings.chatHistoryEnabled = this.config.get('chatHistoryEnabled', true);
|
|
46
|
+
this.settings.maxChatHistory = this.config.get('maxChatHistory', 100);
|
|
47
|
+
this.settings.autoSaveChat = this.config.get('autoSaveChat', true);
|
|
48
|
+
this.settings.theme = this.config.get('theme', 'auto');
|
|
49
|
+
this.settings.fontSize = this.config.get('ui.fontSize', 14);
|
|
50
|
+
this.settings.compactMode = this.config.get('ui.compactMode', false);
|
|
51
|
+
this.settings.showTimestamps = this.config.get('ui.showTimestamps', true);
|
|
52
|
+
this.settings.showAvatars = this.config.get('ui.showAvatars', true);
|
|
53
|
+
this.settings.enterToSend = this.config.get('ui.enterToSend', true);
|
|
54
|
+
this.settings.soundEnabled = this.config.get('ui.soundEnabled', true);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 保存设置
|
|
59
|
+
*/
|
|
60
|
+
saveSetting(key: string, value: any): void {
|
|
61
|
+
try {
|
|
62
|
+
this.config.set(key, value);
|
|
63
|
+
this.logger.debug('Chat setting saved', { key, value });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.logger.error('Failed to save chat setting', error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 更新主题
|
|
71
|
+
*/
|
|
72
|
+
updateTheme(theme: string): void {
|
|
73
|
+
this.settings.theme = theme;
|
|
74
|
+
this.saveSetting('theme', theme);
|
|
75
|
+
this.applyTheme(theme);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 应用主题
|
|
80
|
+
*/
|
|
81
|
+
private applyTheme(theme: string): void {
|
|
82
|
+
const body = document.body;
|
|
83
|
+
body.classList.remove('light-theme', 'dark-theme');
|
|
84
|
+
|
|
85
|
+
if (theme === 'light') {
|
|
86
|
+
body.classList.add('light-theme');
|
|
87
|
+
} else if (theme === 'dark') {
|
|
88
|
+
body.classList.add('dark-theme');
|
|
89
|
+
}
|
|
90
|
+
// 'auto' 主题由系统决定
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 更新字体大小
|
|
95
|
+
*/
|
|
96
|
+
updateFontSize(size: number): void {
|
|
97
|
+
this.settings.fontSize = size;
|
|
98
|
+
this.saveSetting('ui.fontSize', size);
|
|
99
|
+
document.documentElement.style.setProperty('--chat-font-size', `${size}px`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 切换紧凑模式
|
|
104
|
+
*/
|
|
105
|
+
toggleCompactMode(): void {
|
|
106
|
+
this.settings.compactMode = !this.settings.compactMode;
|
|
107
|
+
this.saveSetting('ui.compactMode', this.settings.compactMode);
|
|
108
|
+
document.documentElement.classList.toggle('compact-chat', this.settings.compactMode);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 清空聊天历史
|
|
113
|
+
*/
|
|
114
|
+
clearChatHistory(): void {
|
|
115
|
+
if (confirm('确定要清空所有聊天记录吗?此操作不可恢复。')) {
|
|
116
|
+
localStorage.removeItem('ai-assistant-chat-history');
|
|
117
|
+
this.logger.info('Chat history cleared');
|
|
118
|
+
alert('聊天记录已清空');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 导出聊天设置
|
|
124
|
+
*/
|
|
125
|
+
exportSettings(): void {
|
|
126
|
+
const exportData = {
|
|
127
|
+
chatSettings: this.settings,
|
|
128
|
+
exportTime: new Date().toISOString()
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
|
132
|
+
const url = window.URL.createObjectURL(blob);
|
|
133
|
+
const a = document.createElement('a');
|
|
134
|
+
a.href = url;
|
|
135
|
+
a.download = `chat-settings-${new Date().toISOString().slice(0, 10)}.json`;
|
|
136
|
+
a.click();
|
|
137
|
+
window.URL.revokeObjectURL(url);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 重置为默认设置
|
|
142
|
+
*/
|
|
143
|
+
resetToDefaults(): void {
|
|
144
|
+
if (confirm('确定要重置所有设置为默认值吗?')) {
|
|
145
|
+
this.settings = {
|
|
146
|
+
chatHistoryEnabled: true,
|
|
147
|
+
maxChatHistory: 100,
|
|
148
|
+
autoSaveChat: true,
|
|
149
|
+
theme: 'auto',
|
|
150
|
+
fontSize: 14,
|
|
151
|
+
compactMode: false,
|
|
152
|
+
showTimestamps: true,
|
|
153
|
+
showAvatars: true,
|
|
154
|
+
enterToSend: true,
|
|
155
|
+
soundEnabled: true
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// 保存所有设置
|
|
159
|
+
Object.entries(this.settings).forEach(([key, value]) => {
|
|
160
|
+
const configKey = key.includes('.') ? key : `ui.${key}`;
|
|
161
|
+
this.saveSetting(configKey, value);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
this.logger.info('Chat settings reset to defaults');
|
|
165
|
+
alert('设置已重置为默认值');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'app-error-message',
|
|
5
|
+
template: `
|
|
6
|
+
<div class="error-message" [ngClass]="type">
|
|
7
|
+
<div class="error-icon">
|
|
8
|
+
<i [class]="getIconClass()"></i>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="error-content">
|
|
11
|
+
<h4 *ngIf="title">{{ title }}</h4>
|
|
12
|
+
<p>{{ message }}</p>
|
|
13
|
+
<div *ngIf="details" class="error-details">
|
|
14
|
+
<small>{{ details }}</small>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<button *ngIf="dismissible" class="error-close" (click)="onDismiss()">
|
|
18
|
+
<i class="icon-close"></i>
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
`,
|
|
22
|
+
styles: [`
|
|
23
|
+
.error-message {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: flex-start;
|
|
26
|
+
gap: 0.75rem;
|
|
27
|
+
padding: 1rem;
|
|
28
|
+
border-radius: 0.5rem;
|
|
29
|
+
border: 1px solid;
|
|
30
|
+
margin: 0.5rem 0;
|
|
31
|
+
|
|
32
|
+
&.error {
|
|
33
|
+
background-color: rgba(220, 53, 69, 0.1);
|
|
34
|
+
border-color: var(--ai-danger);
|
|
35
|
+
color: var(--ai-danger);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&.warning {
|
|
39
|
+
background-color: rgba(255, 193, 7, 0.1);
|
|
40
|
+
border-color: var(--ai-warning);
|
|
41
|
+
color: #856404;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&.info {
|
|
45
|
+
background-color: rgba(23, 162, 184, 0.1);
|
|
46
|
+
border-color: var(--ai-info);
|
|
47
|
+
color: var(--ai-info);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&.success {
|
|
51
|
+
background-color: rgba(40, 167, 69, 0.1);
|
|
52
|
+
border-color: var(--ai-success);
|
|
53
|
+
color: var(--ai-success);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.error-icon {
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
font-size: 1.25rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.error-content {
|
|
63
|
+
flex: 1;
|
|
64
|
+
|
|
65
|
+
h4 {
|
|
66
|
+
margin: 0 0 0.5rem 0;
|
|
67
|
+
font-size: 1rem;
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
p {
|
|
72
|
+
margin: 0;
|
|
73
|
+
line-height: 1.5;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.error-details {
|
|
77
|
+
margin-top: 0.5rem;
|
|
78
|
+
opacity: 0.8;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.error-close {
|
|
83
|
+
flex-shrink: 0;
|
|
84
|
+
width: 24px;
|
|
85
|
+
height: 24px;
|
|
86
|
+
border: none;
|
|
87
|
+
background: transparent;
|
|
88
|
+
border-radius: 0.25rem;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
opacity: 0.6;
|
|
94
|
+
transition: opacity 0.2s;
|
|
95
|
+
|
|
96
|
+
&:hover {
|
|
97
|
+
opacity: 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
`]
|
|
101
|
+
})
|
|
102
|
+
export class ErrorMessageComponent {
|
|
103
|
+
@Input() type: 'error' | 'warning' | 'info' | 'success' = 'error';
|
|
104
|
+
@Input() title: string = '';
|
|
105
|
+
@Input() message: string = '';
|
|
106
|
+
@Input() details: string = '';
|
|
107
|
+
@Input() dismissible: boolean = false;
|
|
108
|
+
|
|
109
|
+
@Output() dismissed = new EventEmitter<void>();
|
|
110
|
+
|
|
111
|
+
getIconClass(): string {
|
|
112
|
+
const icons: { [key: string]: string } = {
|
|
113
|
+
'error': 'icon-error',
|
|
114
|
+
'warning': 'icon-warning',
|
|
115
|
+
'info': 'icon-info',
|
|
116
|
+
'success': 'icon-success'
|
|
117
|
+
};
|
|
118
|
+
return icons[this.type] || 'icon-error';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
onDismiss(): void {
|
|
122
|
+
this.dismissed.emit();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'app-loading-spinner',
|
|
5
|
+
template: `
|
|
6
|
+
<div class="spinner-container" [ngClass]="size">
|
|
7
|
+
<div class="spinner"></div>
|
|
8
|
+
<p *ngIf="message" class="spinner-message">{{ message }}</p>
|
|
9
|
+
</div>
|
|
10
|
+
`,
|
|
11
|
+
styles: [`
|
|
12
|
+
.spinner-container {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
padding: 2rem;
|
|
18
|
+
|
|
19
|
+
&.small {
|
|
20
|
+
padding: 1rem;
|
|
21
|
+
|
|
22
|
+
.spinner {
|
|
23
|
+
width: 24px;
|
|
24
|
+
height: 24px;
|
|
25
|
+
border-width: 2px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.spinner-message {
|
|
29
|
+
font-size: 0.75rem;
|
|
30
|
+
margin-top: 0.5rem;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&.medium {
|
|
35
|
+
.spinner {
|
|
36
|
+
width: 36px;
|
|
37
|
+
height: 36px;
|
|
38
|
+
border-width: 3px;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&.large {
|
|
43
|
+
.spinner {
|
|
44
|
+
width: 48px;
|
|
45
|
+
height: 48px;
|
|
46
|
+
border-width: 4px;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.spinner {
|
|
52
|
+
border: 3px solid var(--ai-border);
|
|
53
|
+
border-top-color: var(--ai-primary);
|
|
54
|
+
border-radius: 50%;
|
|
55
|
+
animation: spin 0.8s linear infinite;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.spinner-message {
|
|
59
|
+
margin-top: 1rem;
|
|
60
|
+
color: var(--ai-secondary);
|
|
61
|
+
font-size: 0.875rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@keyframes spin {
|
|
65
|
+
to { transform: rotate(360deg); }
|
|
66
|
+
}
|
|
67
|
+
`]
|
|
68
|
+
})
|
|
69
|
+
export class LoadingSpinnerComponent {
|
|
70
|
+
@Input() size: 'small' | 'medium' | 'large' = 'medium';
|
|
71
|
+
@Input() message: string = '';
|
|
72
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
|
3
|
+
import { RiskLevel } from '../../types/security.types';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'app-consent-dialog',
|
|
7
|
+
template: `
|
|
8
|
+
<div class="consent-dialog">
|
|
9
|
+
<div class="dialog-header">
|
|
10
|
+
<i class="icon-help-circle"></i>
|
|
11
|
+
<h3>确认执行</h3>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="dialog-content">
|
|
14
|
+
<p>确定要执行此命令吗?</p>
|
|
15
|
+
<div class="command-preview">
|
|
16
|
+
<code>{{ command }}</code>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="checkbox-group">
|
|
19
|
+
<label>
|
|
20
|
+
<input type="checkbox" [(ngModel)]="rememberChoice">
|
|
21
|
+
记住我的选择(30天内不再提示)
|
|
22
|
+
</label>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="dialog-footer">
|
|
26
|
+
<button class="btn btn-secondary" (click)="cancel()">取消</button>
|
|
27
|
+
<button class="btn btn-primary" (click)="confirm()">确认执行</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
`,
|
|
31
|
+
styles: [`
|
|
32
|
+
.consent-dialog {
|
|
33
|
+
background: var(--ai-bg-primary);
|
|
34
|
+
border-radius: 0.5rem;
|
|
35
|
+
max-width: 500px;
|
|
36
|
+
}
|
|
37
|
+
.dialog-header {
|
|
38
|
+
padding: 1.5rem;
|
|
39
|
+
text-align: center;
|
|
40
|
+
border-bottom: 1px solid var(--ai-border);
|
|
41
|
+
}
|
|
42
|
+
.dialog-content {
|
|
43
|
+
padding: 1.5rem;
|
|
44
|
+
}
|
|
45
|
+
.command-preview {
|
|
46
|
+
background: var(--ai-bg-secondary);
|
|
47
|
+
padding: 0.75rem;
|
|
48
|
+
border-radius: 0.375rem;
|
|
49
|
+
margin: 1rem 0;
|
|
50
|
+
}
|
|
51
|
+
.checkbox-group {
|
|
52
|
+
margin-top: 1rem;
|
|
53
|
+
}
|
|
54
|
+
.dialog-footer {
|
|
55
|
+
padding: 1rem 1.5rem;
|
|
56
|
+
display: flex;
|
|
57
|
+
justify-content: flex-end;
|
|
58
|
+
gap: 0.5rem;
|
|
59
|
+
border-top: 1px solid var(--ai-border);
|
|
60
|
+
}
|
|
61
|
+
`]
|
|
62
|
+
})
|
|
63
|
+
export class ConsentDialogComponent {
|
|
64
|
+
@Input() command: string = '';
|
|
65
|
+
@Input() riskLevel: RiskLevel = RiskLevel.MEDIUM;
|
|
66
|
+
rememberChoice = false;
|
|
67
|
+
|
|
68
|
+
constructor(public activeModal: NgbActiveModal) {}
|
|
69
|
+
|
|
70
|
+
confirm(): void {
|
|
71
|
+
this.activeModal.close({ confirmed: true, remember: this.rememberChoice });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
cancel(): void {
|
|
75
|
+
this.activeModal.dismiss('cancel');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core';
|
|
2
|
+
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'app-password-prompt',
|
|
6
|
+
template: `
|
|
7
|
+
<div class="password-prompt">
|
|
8
|
+
<div class="prompt-header">
|
|
9
|
+
<i class="icon-lock"></i>
|
|
10
|
+
<h3>密码验证</h3>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="prompt-content">
|
|
13
|
+
<p>请输入密码以执行此高风险操作:</p>
|
|
14
|
+
<input
|
|
15
|
+
type="password"
|
|
16
|
+
class="form-control"
|
|
17
|
+
[(ngModel)]="password"
|
|
18
|
+
(keydown.enter)="submit()"
|
|
19
|
+
placeholder="输入密码"
|
|
20
|
+
#passwordInput>
|
|
21
|
+
<div *ngIf="errorMessage" class="error-message">
|
|
22
|
+
<i class="icon-alert-circle"></i>
|
|
23
|
+
{{ errorMessage }}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="prompt-footer">
|
|
27
|
+
<button class="btn btn-secondary" (click)="cancel()">取消</button>
|
|
28
|
+
<button class="btn btn-primary" (click)="submit()" [disabled]="!password">确认</button>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
styles: [`
|
|
33
|
+
.password-prompt {
|
|
34
|
+
background: var(--ai-bg-primary);
|
|
35
|
+
border-radius: 0.5rem;
|
|
36
|
+
max-width: 400px;
|
|
37
|
+
margin: 0 auto;
|
|
38
|
+
}
|
|
39
|
+
.prompt-header {
|
|
40
|
+
padding: 1.5rem;
|
|
41
|
+
text-align: center;
|
|
42
|
+
border-bottom: 1px solid var(--ai-border);
|
|
43
|
+
}
|
|
44
|
+
.prompt-content {
|
|
45
|
+
padding: 1.5rem;
|
|
46
|
+
}
|
|
47
|
+
.error-message {
|
|
48
|
+
margin-top: 0.75rem;
|
|
49
|
+
color: var(--ai-danger);
|
|
50
|
+
font-size: 0.875rem;
|
|
51
|
+
}
|
|
52
|
+
.prompt-footer {
|
|
53
|
+
padding: 1rem 1.5rem;
|
|
54
|
+
display: flex;
|
|
55
|
+
justify-content: flex-end;
|
|
56
|
+
gap: 0.5rem;
|
|
57
|
+
border-top: 1px solid var(--ai-border);
|
|
58
|
+
}
|
|
59
|
+
`]
|
|
60
|
+
})
|
|
61
|
+
export class PasswordPromptComponent {
|
|
62
|
+
@Input() title: string = '密码验证';
|
|
63
|
+
password = '';
|
|
64
|
+
errorMessage = '';
|
|
65
|
+
|
|
66
|
+
constructor(public activeModal: NgbActiveModal) {}
|
|
67
|
+
|
|
68
|
+
submit(): void {
|
|
69
|
+
if (this.password) {
|
|
70
|
+
this.activeModal.close(this.password);
|
|
71
|
+
} else {
|
|
72
|
+
this.errorMessage = '请输入密码';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
cancel(): void {
|
|
77
|
+
this.activeModal.dismiss('cancel');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<div class="risk-confirm-dialog">
|
|
2
|
+
<!-- 头部 -->
|
|
3
|
+
<div class="dialog-header">
|
|
4
|
+
<div class="risk-indicator" [style.color]="getRiskLevelColor()">
|
|
5
|
+
<i [class]="getRiskLevelIcon()"></i>
|
|
6
|
+
<span>{{ getRiskLevelText() }}</span>
|
|
7
|
+
</div>
|
|
8
|
+
<button type="button" class="close-button" (click)="cancel()">
|
|
9
|
+
<i class="icon-x"></i>
|
|
10
|
+
</button>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<!-- 内容 -->
|
|
14
|
+
<div class="dialog-content">
|
|
15
|
+
<!-- 风险提示 -->
|
|
16
|
+
<div class="risk-warning" [ngClass]="isHighRisk() ? 'high-risk' : 'medium-risk'">
|
|
17
|
+
<div class="warning-icon">
|
|
18
|
+
<i [class]="getRiskLevelIcon()"></i>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="warning-text">
|
|
21
|
+
<h4>安全警告</h4>
|
|
22
|
+
<p>此命令被识别为{{ getRiskLevelText() }},请仔细确认后再执行。</p>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<!-- 命令信息 -->
|
|
27
|
+
<div class="command-info">
|
|
28
|
+
<div class="info-section">
|
|
29
|
+
<label>将要执行的命令:</label>
|
|
30
|
+
<div class="command-display">
|
|
31
|
+
<code>{{ command }}</code>
|
|
32
|
+
<button class="btn-copy" (click)="navigator.clipboard.writeText(command)" title="复制">
|
|
33
|
+
<i class="icon-copy"></i>
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="info-section">
|
|
39
|
+
<label>命令解释:</label>
|
|
40
|
+
<p class="explanation">{{ explanation }}</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- 安全建议 -->
|
|
44
|
+
<div class="info-section" *ngIf="suggestions.length > 0">
|
|
45
|
+
<label>安全建议:</label>
|
|
46
|
+
<ul class="suggestions-list">
|
|
47
|
+
<li *ngFor="let suggestion of suggestions">
|
|
48
|
+
<i class="icon-lightbulb"></i>
|
|
49
|
+
<span>{{ suggestion }}</span>
|
|
50
|
+
</li>
|
|
51
|
+
</ul>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<!-- 额外警告(高风险) -->
|
|
56
|
+
<div *ngIf="isHighRisk()" class="high-risk-warning">
|
|
57
|
+
<div class="warning-box">
|
|
58
|
+
<i class="icon-alert-triangle"></i>
|
|
59
|
+
<div class="warning-content">
|
|
60
|
+
<strong>⚠️ 重要提示</strong>
|
|
61
|
+
<p *ngIf="riskLevel === 'critical'">
|
|
62
|
+
此命令可能导致系统损坏或数据丢失,请在执行前备份重要数据!
|
|
63
|
+
</p>
|
|
64
|
+
<p *ngIf="riskLevel === 'high'">
|
|
65
|
+
此命令将修改系统设置或删除文件,请确保您了解命令的作用。
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- 底部按钮 -->
|
|
73
|
+
<div class="dialog-footer">
|
|
74
|
+
<button type="button" class="btn btn-secondary" (click)="cancel()">
|
|
75
|
+
<i class="icon-x"></i>
|
|
76
|
+
取消
|
|
77
|
+
</button>
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
class="btn btn-danger"
|
|
81
|
+
[ngClass]="{ 'btn-critical': isHighRisk() }"
|
|
82
|
+
(click)="confirm()">
|
|
83
|
+
<i [class]="isHighRisk() ? 'icon-alert-triangle' : 'icon-check'"></i>
|
|
84
|
+
{{ isHighRisk() ? '仍要执行' : '确认执行' }}
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|