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,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateApiKey,
|
|
3
|
+
validateUrl,
|
|
4
|
+
validateModel,
|
|
5
|
+
validateTemperature,
|
|
6
|
+
validateMaxTokens,
|
|
7
|
+
validateCommand,
|
|
8
|
+
validateEmail,
|
|
9
|
+
validatePassword,
|
|
10
|
+
validatePort,
|
|
11
|
+
validateJson,
|
|
12
|
+
validateFilePath
|
|
13
|
+
} from './validation.utils';
|
|
14
|
+
|
|
15
|
+
describe('ValidationUtils', () => {
|
|
16
|
+
describe('validateApiKey', () => {
|
|
17
|
+
it('should validate OpenAI API keys', () => {
|
|
18
|
+
const result = validateApiKey('sk-test123456789', 'openai');
|
|
19
|
+
expect(result.valid).toBe(true);
|
|
20
|
+
|
|
21
|
+
const invalidResult = validateApiKey('invalid-key', 'openai');
|
|
22
|
+
expect(invalidResult.valid).toBe(false);
|
|
23
|
+
expect(invalidResult.error).toContain('sk-');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should validate Anthropic API keys', () => {
|
|
27
|
+
const result = validateApiKey('sk-ant-test123456789', 'anthropic');
|
|
28
|
+
expect(result.valid).toBe(true);
|
|
29
|
+
|
|
30
|
+
const invalidResult = validateApiKey('invalid-key', 'anthropic');
|
|
31
|
+
expect(invalidResult.valid).toBe(false);
|
|
32
|
+
expect(invalidResult.error).toContain('sk-ant-');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should validate Minimax API keys', () => {
|
|
36
|
+
const result = validateApiKey('minimax-key-123456789', 'minimax');
|
|
37
|
+
expect(result.valid).toBe(true);
|
|
38
|
+
|
|
39
|
+
const invalidResult = validateApiKey('short', 'minimax');
|
|
40
|
+
expect(invalidResult.valid).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should validate GLM API keys', () => {
|
|
44
|
+
const result = validateApiKey('glm-key-123456789', 'glm');
|
|
45
|
+
expect(result.valid).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should accept empty keys for compatible provider', () => {
|
|
49
|
+
const result = validateApiKey('', 'openai-compatible');
|
|
50
|
+
expect(result.valid).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('validateUrl', () => {
|
|
55
|
+
it('should validate correct URLs', () => {
|
|
56
|
+
const result = validateUrl('https://api.example.com');
|
|
57
|
+
expect(result.valid).toBe(true);
|
|
58
|
+
|
|
59
|
+
const result2 = validateUrl('http://localhost:11434');
|
|
60
|
+
expect(result2.valid).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should reject invalid URLs', () => {
|
|
64
|
+
const result = validateUrl('not-a-url');
|
|
65
|
+
expect(result.valid).toBe(false);
|
|
66
|
+
expect(result.error).toContain('URL');
|
|
67
|
+
|
|
68
|
+
const result2 = validateUrl('ftp://example.com');
|
|
69
|
+
expect(result2.valid).toBe(false);
|
|
70
|
+
expect(result2.error).toContain('HTTP');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should reject empty URLs', () => {
|
|
74
|
+
const result = validateUrl('');
|
|
75
|
+
expect(result.valid).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('validateModel', () => {
|
|
80
|
+
it('should validate correct model names', () => {
|
|
81
|
+
const result = validateModel('gpt-4', 'openai');
|
|
82
|
+
expect(result.valid).toBe(true);
|
|
83
|
+
|
|
84
|
+
const result2 = validateModel('claude-3-sonnet', 'anthropic');
|
|
85
|
+
expect(result2.valid).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should reject invalid model names', () => {
|
|
89
|
+
const result = validateModel('', 'openai');
|
|
90
|
+
expect(result.valid).toBe(false);
|
|
91
|
+
|
|
92
|
+
const result2 = validateModel('model@#$%', 'openai');
|
|
93
|
+
expect(result2.valid).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('validateTemperature', () => {
|
|
98
|
+
it('should validate correct temperature values', () => {
|
|
99
|
+
expect(validateTemperature(0).valid).toBe(true);
|
|
100
|
+
expect(validateTemperature(0.7).valid).toBe(true);
|
|
101
|
+
expect(validateTemperature(1.5).valid).toBe(true);
|
|
102
|
+
expect(validateTemperature(2).valid).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should reject invalid temperature values', () => {
|
|
106
|
+
expect(validateTemperature(-0.1).valid).toBe(false);
|
|
107
|
+
expect(validateTemperature(2.1).valid).toBe(false);
|
|
108
|
+
expect(validateTemperature(NaN).valid).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('validateMaxTokens', () => {
|
|
113
|
+
it('should validate correct token values', () => {
|
|
114
|
+
expect(validateMaxTokens(100).valid).toBe(true);
|
|
115
|
+
expect(validateMaxTokens(1000).valid).toBe(true);
|
|
116
|
+
expect(validateMaxTokens(32000).valid).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should reject invalid token values', () => {
|
|
120
|
+
expect(validateMaxTokens(0).valid).toBe(false);
|
|
121
|
+
expect(validateMaxTokens(-100).valid).toBe(false);
|
|
122
|
+
expect(validateMaxTokens(32001).valid).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('validateCommand', () => {
|
|
127
|
+
it('should accept safe commands', () => {
|
|
128
|
+
expect(validateCommand('ls -la').valid).toBe(true);
|
|
129
|
+
expect(validateCommand('git status').valid).toBe(true);
|
|
130
|
+
expect(validateCommand('echo "hello world"').valid).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should reject dangerous commands', () => {
|
|
134
|
+
expect(validateCommand('rm -rf /').valid).toBe(false);
|
|
135
|
+
expect(validateCommand('sudo rm -rf /home').valid).toBe(false);
|
|
136
|
+
expect(validateCommand('chmod 777 /etc/passwd').valid).toBe(false);
|
|
137
|
+
expect(validateCommand('dd if=/dev/zero of=/dev/sda').valid).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should reject commands with injection attempts', () => {
|
|
141
|
+
expect(validateCommand('ls; rm -rf /').valid).toBe(false);
|
|
142
|
+
expect(validateCommand('cat file | sh').valid).toBe(false);
|
|
143
|
+
expect(validateCommand('$(rm -rf /)').valid).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('validateEmail', () => {
|
|
148
|
+
it('should validate correct email addresses', () => {
|
|
149
|
+
expect(validateEmail('test@example.com').valid).toBe(true);
|
|
150
|
+
expect(validateEmail('user.name@domain.co.uk').valid).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should reject invalid email addresses', () => {
|
|
154
|
+
expect(validateEmail('invalid').valid).toBe(false);
|
|
155
|
+
expect(validateEmail('@example.com').valid).toBe(false);
|
|
156
|
+
expect(validateEmail('test@').valid).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('validatePassword', () => {
|
|
161
|
+
it('should validate strong passwords', () => {
|
|
162
|
+
const result = validatePassword('SecurePass123!');
|
|
163
|
+
expect(result.valid).toBe(true);
|
|
164
|
+
expect(result.score).toBeGreaterThan(40);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should reject weak passwords', () => {
|
|
168
|
+
expect(validatePassword('weak').valid).toBe(false);
|
|
169
|
+
expect(validatePassword('password').valid).toBe(false);
|
|
170
|
+
expect(validatePassword('123456').valid).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should score passwords correctly', () => {
|
|
174
|
+
const weak = validatePassword('123');
|
|
175
|
+
expect(weak.score).toBe(0);
|
|
176
|
+
|
|
177
|
+
const medium = validatePassword('password123');
|
|
178
|
+
expect(medium.score).toBeGreaterThan(0);
|
|
179
|
+
expect(medium.score).toBeLessThan(50);
|
|
180
|
+
|
|
181
|
+
const strong = validatePassword('StrongPass123!');
|
|
182
|
+
expect(strong.score).toBeGreaterThan(60);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('validatePort', () => {
|
|
187
|
+
it('should validate correct ports', () => {
|
|
188
|
+
expect(validatePort(80).valid).toBe(true);
|
|
189
|
+
expect(validatePort(443).valid).toBe(true);
|
|
190
|
+
expect(validatePort(3000).valid).toBe(true);
|
|
191
|
+
expect(validatePort(65535).valid).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should reject invalid ports', () => {
|
|
195
|
+
expect(validatePort(0).valid).toBe(false);
|
|
196
|
+
expect(validatePort(65536).valid).toBe(false);
|
|
197
|
+
expect(validatePort(-1).valid).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('validateJson', () => {
|
|
202
|
+
it('should validate correct JSON', () => {
|
|
203
|
+
const result = validateJson('{"key": "value"}');
|
|
204
|
+
expect(result.valid).toBe(true);
|
|
205
|
+
expect(result.data).toEqual({ key: 'value' });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should reject invalid JSON', () => {
|
|
209
|
+
const result = validateJson('{invalid json}');
|
|
210
|
+
expect(result.valid).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('validateFilePath', () => {
|
|
215
|
+
it('should validate correct file paths', () => {
|
|
216
|
+
expect(validateFilePath('/home/user/file.txt').valid).toBe(true);
|
|
217
|
+
expect(validateFilePath('C:\\Windows\\System32').valid).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should reject invalid file paths', () => {
|
|
221
|
+
expect(validateFilePath('').valid).toBe(false);
|
|
222
|
+
expect(validateFilePath('path<with>invalid').valid).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 验证工具类
|
|
3
|
+
* 提供各种数据验证和格式检查功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 验证API密钥格式
|
|
8
|
+
*/
|
|
9
|
+
export function validateApiKey(apiKey: string, provider: string): { valid: boolean; error?: string } {
|
|
10
|
+
if (!apiKey || apiKey.trim().length === 0) {
|
|
11
|
+
return { valid: false, error: 'API密钥不能为空' };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 移除前后空格
|
|
15
|
+
const trimmedKey = apiKey.trim();
|
|
16
|
+
|
|
17
|
+
// 根据提供商验证格式
|
|
18
|
+
switch (provider.toLowerCase()) {
|
|
19
|
+
case 'openai':
|
|
20
|
+
return validateOpenAiKey(trimmedKey);
|
|
21
|
+
case 'anthropic':
|
|
22
|
+
return validateAnthropicKey(trimmedKey);
|
|
23
|
+
case 'minimax':
|
|
24
|
+
return validateMinimaxKey(trimmedKey);
|
|
25
|
+
case 'glm':
|
|
26
|
+
return validateGlmKey(trimmedKey);
|
|
27
|
+
case 'openai-compatible':
|
|
28
|
+
return { valid: true };
|
|
29
|
+
default:
|
|
30
|
+
return { valid: true };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 验证OpenAI API密钥格式
|
|
36
|
+
*/
|
|
37
|
+
function validateOpenAiKey(key: string): { valid: boolean; error?: string } {
|
|
38
|
+
// OpenAI密钥通常以 sk- 开头
|
|
39
|
+
if (!key.startsWith('sk-')) {
|
|
40
|
+
return { valid: false, error: 'OpenAI API密钥应以 sk- 开头' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (key.length < 50) {
|
|
44
|
+
return { valid: false, error: 'OpenAI API密钥长度不足' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { valid: true };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 验证Anthropic API密钥格式
|
|
52
|
+
*/
|
|
53
|
+
function validateAnthropicKey(key: string): { valid: boolean; error?: string } {
|
|
54
|
+
// Anthropic密钥通常以 sk-ant- 开头
|
|
55
|
+
if (!key.startsWith('sk-ant-')) {
|
|
56
|
+
return { valid: false, error: 'Anthropic API密钥应以 sk-ant- 开头' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (key.length < 50) {
|
|
60
|
+
return { valid: false, error: 'Anthropic API密钥长度不足' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { valid: true };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 验证Minimax API密钥格式
|
|
68
|
+
*/
|
|
69
|
+
function validateMinimaxKey(key: string): { valid: boolean; error?: string } {
|
|
70
|
+
// Minimax密钥长度检查
|
|
71
|
+
if (key.length < 20) {
|
|
72
|
+
return { valid: false, error: 'Minimax API密钥长度不足' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { valid: true };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 验证GLM API密钥格式
|
|
80
|
+
*/
|
|
81
|
+
function validateGlmKey(key: string): { valid: boolean; error?: string } {
|
|
82
|
+
// GLM密钥长度检查
|
|
83
|
+
if (key.length < 20) {
|
|
84
|
+
return { valid: false, error: 'GLM API密钥长度不足' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { valid: true };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 验证URL格式
|
|
92
|
+
*/
|
|
93
|
+
export function validateUrl(url: string): { valid: boolean; error?: string } {
|
|
94
|
+
if (!url || url.trim().length === 0) {
|
|
95
|
+
return { valid: false, error: 'URL不能为空' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const parsedUrl = new URL(url);
|
|
100
|
+
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
|
|
101
|
+
return { valid: false, error: 'URL必须使用HTTP或HTTPS协议' };
|
|
102
|
+
}
|
|
103
|
+
return { valid: true };
|
|
104
|
+
} catch {
|
|
105
|
+
return { valid: false, error: '无效的URL格式' };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 验证模型名称
|
|
111
|
+
*/
|
|
112
|
+
export function validateModel(model: string, _provider: string): { valid: boolean; error?: string } {
|
|
113
|
+
if (!model || model.trim().length === 0) {
|
|
114
|
+
return { valid: false, error: '模型名称不能为空' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const trimmedModel = model.trim();
|
|
118
|
+
|
|
119
|
+
// 验证模型名称格式
|
|
120
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(trimmedModel)) {
|
|
121
|
+
return { valid: false, error: '模型名称包含非法字符' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// TODO: 根据提供商类型进行特定验证
|
|
125
|
+
// _provider 参数保留用于未来扩展
|
|
126
|
+
|
|
127
|
+
return { valid: true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 验证温度参数
|
|
132
|
+
*/
|
|
133
|
+
export function validateTemperature(temperature: number): { valid: boolean; error?: string } {
|
|
134
|
+
if (typeof temperature !== 'number' || isNaN(temperature)) {
|
|
135
|
+
return { valid: false, error: '温度必须是有效数字' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (temperature < 0 || temperature > 2) {
|
|
139
|
+
return { valid: false, error: '温度值必须在0-2之间' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { valid: true };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 验证最大令牌数
|
|
147
|
+
*/
|
|
148
|
+
export function validateMaxTokens(maxTokens: number): { valid: boolean; error?: string } {
|
|
149
|
+
if (typeof maxTokens !== 'number' || isNaN(maxTokens) || maxTokens <= 0) {
|
|
150
|
+
return { valid: false, error: '最大令牌数必须是正整数' };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (maxTokens > 32000) {
|
|
154
|
+
return { valid: false, error: '最大令牌数不能超过32000' };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { valid: true };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 验证命令字符串
|
|
162
|
+
*/
|
|
163
|
+
export function validateCommand(command: string): { valid: boolean; error?: string } {
|
|
164
|
+
if (!command || command.trim().length === 0) {
|
|
165
|
+
return { valid: false, error: '命令不能为空' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const trimmed = command.trim();
|
|
169
|
+
|
|
170
|
+
// 检查危险模式
|
|
171
|
+
const dangerousPatterns = [
|
|
172
|
+
/rm\s+-rf\s+\//,
|
|
173
|
+
/sudo\s+rm/,
|
|
174
|
+
/>\s*\/dev\/null/,
|
|
175
|
+
/chmod\s+777/,
|
|
176
|
+
/dd\s+if=/,
|
|
177
|
+
/fork\s*\(/,
|
|
178
|
+
/\|\s*sh\b/,
|
|
179
|
+
/\|\s*bash\b/,
|
|
180
|
+
/\$\(/,
|
|
181
|
+
/`[^`]*`/,
|
|
182
|
+
/;\s*rm/,
|
|
183
|
+
/&&\s*rm/
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
for (const pattern of dangerousPatterns) {
|
|
187
|
+
if (pattern.test(trimmed)) {
|
|
188
|
+
return { valid: false, error: '检测到潜在危险命令' };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { valid: true };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 验证邮箱格式
|
|
197
|
+
*/
|
|
198
|
+
export function validateEmail(email: string): { valid: boolean; error?: string } {
|
|
199
|
+
if (!email || email.trim().length === 0) {
|
|
200
|
+
return { valid: false, error: '邮箱不能为空' };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
204
|
+
if (!emailRegex.test(email)) {
|
|
205
|
+
return { valid: false, error: '邮箱格式无效' };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { valid: true };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 验证密码强度
|
|
213
|
+
*/
|
|
214
|
+
export function validatePassword(password: string): { valid: boolean; error?: string; score: number } {
|
|
215
|
+
if (!password || password.length === 0) {
|
|
216
|
+
return { valid: false, error: '密码不能为空', score: 0 };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let score = 0;
|
|
220
|
+
const errors: string[] = [];
|
|
221
|
+
|
|
222
|
+
// 长度检查
|
|
223
|
+
if (password.length < 8) {
|
|
224
|
+
errors.push('密码至少需要8个字符');
|
|
225
|
+
} else {
|
|
226
|
+
score += 20;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (password.length >= 12) {
|
|
230
|
+
score += 10;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 字符类型检查
|
|
234
|
+
const hasLower = /[a-z]/.test(password);
|
|
235
|
+
const hasUpper = /[A-Z]/.test(password);
|
|
236
|
+
const hasNumber = /\d/.test(password);
|
|
237
|
+
const hasSpecial = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password);
|
|
238
|
+
|
|
239
|
+
if (hasLower) score += 10;
|
|
240
|
+
if (hasUpper) score += 10;
|
|
241
|
+
if (hasNumber) score += 10;
|
|
242
|
+
if (hasSpecial) score += 10;
|
|
243
|
+
|
|
244
|
+
if (!hasLower) errors.push('需要包含小写字母');
|
|
245
|
+
if (!hasUpper) errors.push('需要包含大写字母');
|
|
246
|
+
if (!hasNumber) errors.push('需要包含数字');
|
|
247
|
+
if (!hasSpecial) errors.push('需要包含特殊字符');
|
|
248
|
+
|
|
249
|
+
// 常见密码检查
|
|
250
|
+
const commonPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein'];
|
|
251
|
+
if (commonPasswords.includes(password.toLowerCase())) {
|
|
252
|
+
return { valid: false, error: '密码过于常见', score: 0 };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const valid = errors.length === 0 && score >= 40;
|
|
256
|
+
return {
|
|
257
|
+
valid,
|
|
258
|
+
error: valid ? undefined : errors.join(', '),
|
|
259
|
+
score: Math.min(score, 100)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 验证端口号
|
|
265
|
+
*/
|
|
266
|
+
export function validatePort(port: number): { valid: boolean; error?: string } {
|
|
267
|
+
if (typeof port !== 'number' || isNaN(port) || !Number.isInteger(port)) {
|
|
268
|
+
return { valid: false, error: '端口必须是整数' };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (port < 1 || port > 65535) {
|
|
272
|
+
return { valid: false, error: '端口号必须在1-65535之间' };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { valid: true };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 验证JSON格式
|
|
280
|
+
*/
|
|
281
|
+
export function validateJson(jsonString: string): { valid: boolean; error?: string; data?: any } {
|
|
282
|
+
if (!jsonString || jsonString.trim().length === 0) {
|
|
283
|
+
return { valid: false, error: 'JSON字符串不能为空' };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const data = JSON.parse(jsonString);
|
|
288
|
+
return { valid: true, data };
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return { valid: false, error: '无效的JSON格式' };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 验证文件路径
|
|
296
|
+
*/
|
|
297
|
+
export function validateFilePath(path: string): { valid: boolean; error?: string } {
|
|
298
|
+
if (!path || path.trim().length === 0) {
|
|
299
|
+
return { valid: false, error: '文件路径不能为空' };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 检查路径长度
|
|
303
|
+
if (path.length > 260) {
|
|
304
|
+
return { valid: false, error: '文件路径过长' };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 检查非法字符
|
|
308
|
+
const invalidChars = /[<>:"/\\|?*]/;
|
|
309
|
+
if (invalidChars.test(path)) {
|
|
310
|
+
return { valid: false, error: '路径包含非法字符' };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return { valid: true };
|
|
314
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": false,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": false,
|
|
13
|
+
"noImplicitAny": false,
|
|
14
|
+
"noImplicitReturns": false,
|
|
15
|
+
"noImplicitThis": false,
|
|
16
|
+
"noUnusedLocals": false,
|
|
17
|
+
"noUnusedParameters": false,
|
|
18
|
+
"strictNullChecks": false,
|
|
19
|
+
"strictFunctionTypes": false,
|
|
20
|
+
"strictBindCallApply": false,
|
|
21
|
+
"strictPropertyInitialization": false,
|
|
22
|
+
"noFallthroughCasesInSwitch": false,
|
|
23
|
+
"moduleResolution": "node",
|
|
24
|
+
"experimentalDecorators": true,
|
|
25
|
+
"emitDecoratorMetadata": true,
|
|
26
|
+
"resolveJsonModule": true,
|
|
27
|
+
"sourceMap": true,
|
|
28
|
+
"baseUrl": "./",
|
|
29
|
+
"paths": {
|
|
30
|
+
"@/*": ["src/*"]
|
|
31
|
+
},
|
|
32
|
+
"typeRoots": [
|
|
33
|
+
"node_modules/@types",
|
|
34
|
+
"src/types"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"include": [
|
|
38
|
+
"src/**/*"
|
|
39
|
+
],
|
|
40
|
+
"exclude": [
|
|
41
|
+
"node_modules",
|
|
42
|
+
"dist",
|
|
43
|
+
"**/*.spec.ts"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
target: 'node',
|
|
5
|
+
entry: 'src/index.ts',
|
|
6
|
+
devtool: 'source-map',
|
|
7
|
+
context: __dirname,
|
|
8
|
+
mode: 'production',
|
|
9
|
+
output: {
|
|
10
|
+
path: path.resolve(__dirname, 'dist'),
|
|
11
|
+
filename: 'index.js',
|
|
12
|
+
pathinfo: true,
|
|
13
|
+
libraryTarget: 'umd',
|
|
14
|
+
devtoolModuleFilenameTemplate: 'webpack-tabby-ai-assistant:///[resource-path]',
|
|
15
|
+
},
|
|
16
|
+
resolve: {
|
|
17
|
+
modules: ['.', 'src', 'node_modules'].map(x => path.join(__dirname, x)),
|
|
18
|
+
extensions: ['.ts', '.js'],
|
|
19
|
+
},
|
|
20
|
+
module: {
|
|
21
|
+
rules: [
|
|
22
|
+
{
|
|
23
|
+
test: /\.ts$/,
|
|
24
|
+
loader: 'ts-loader',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
test: /\.scss/,
|
|
28
|
+
use: ['raw-loader', 'css-loader', 'sass-loader'],
|
|
29
|
+
},
|
|
30
|
+
{ test: /\.pug$/, use: ['pug-plain-loader'] },
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
externals: [
|
|
34
|
+
'fs',
|
|
35
|
+
'ngx-toastr',
|
|
36
|
+
'util',
|
|
37
|
+
/^rxjs/,
|
|
38
|
+
/^@angular/,
|
|
39
|
+
/^@ng-bootstrap/,
|
|
40
|
+
/^tabby-/,
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const webpack = require('webpack');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
mode: 'production',
|
|
6
|
+
entry: './src/index.ts',
|
|
7
|
+
output: {
|
|
8
|
+
path: path.resolve(__dirname, 'dist'),
|
|
9
|
+
filename: 'index.js',
|
|
10
|
+
library: 'AiAssistantModule',
|
|
11
|
+
libraryTarget: 'commonjs2',
|
|
12
|
+
libraryExport: 'default',
|
|
13
|
+
devtoolModuleFilenameTemplate: 'webpack-tabby-ai-assistant:///[resource-path]',
|
|
14
|
+
},
|
|
15
|
+
resolve: {
|
|
16
|
+
extensions: ['.ts', '.js'],
|
|
17
|
+
alias: {
|
|
18
|
+
'@': path.resolve(__dirname, 'src')
|
|
19
|
+
},
|
|
20
|
+
fallback: {
|
|
21
|
+
'os': false,
|
|
22
|
+
'path': false,
|
|
23
|
+
'crypto': false,
|
|
24
|
+
'stream': false,
|
|
25
|
+
'util': false
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
module: {
|
|
29
|
+
rules: [
|
|
30
|
+
{
|
|
31
|
+
test: /\.ts$/,
|
|
32
|
+
use: [
|
|
33
|
+
{ loader: 'ts-loader' },
|
|
34
|
+
{ loader: 'angular2-template-loader' }
|
|
35
|
+
],
|
|
36
|
+
exclude: /node_modules/
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
test: /\.(html|css|scss)$/,
|
|
40
|
+
use: ['raw-loader']
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
externals: [
|
|
45
|
+
'tabby-core',
|
|
46
|
+
'tabby-terminal',
|
|
47
|
+
'tabby-settings',
|
|
48
|
+
'@angular/core',
|
|
49
|
+
'@angular/common',
|
|
50
|
+
'@angular/forms',
|
|
51
|
+
'@ng-bootstrap/ng-bootstrap',
|
|
52
|
+
'rxjs'
|
|
53
|
+
],
|
|
54
|
+
plugins: [
|
|
55
|
+
new webpack.DefinePlugin({
|
|
56
|
+
'process.env.NODE_ENV': JSON.stringify('production')
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
};
|