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,369 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { BaseAiProvider as IBaseAiProvider, ProviderConfig, AuthConfig, ProviderCapability, HealthStatus, ValidationResult } from '../../types/provider.types';
|
|
3
|
+
import { ChatRequest, ChatResponse, CommandRequest, CommandResponse, ExplainRequest, ExplainResponse, AnalysisRequest, AnalysisResponse } from '../../types/ai.types';
|
|
4
|
+
import { LoggerService } from '../core/logger.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 基础AI提供商抽象类
|
|
8
|
+
* 所有AI提供商都应该继承此类
|
|
9
|
+
*/
|
|
10
|
+
@Injectable()
|
|
11
|
+
export abstract class BaseAiProvider extends IBaseAiProvider {
|
|
12
|
+
abstract readonly name: string;
|
|
13
|
+
abstract readonly displayName: string;
|
|
14
|
+
abstract readonly capabilities: ProviderCapability[];
|
|
15
|
+
abstract readonly authConfig: AuthConfig;
|
|
16
|
+
|
|
17
|
+
protected config: ProviderConfig | null = null;
|
|
18
|
+
protected isInitialized = false;
|
|
19
|
+
protected lastHealthCheck: { status: HealthStatus; timestamp: Date } | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(protected logger: LoggerService) {
|
|
22
|
+
super();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 配置提供商
|
|
27
|
+
*/
|
|
28
|
+
configure(config: ProviderConfig): void {
|
|
29
|
+
this.config = { ...config };
|
|
30
|
+
this.isInitialized = true;
|
|
31
|
+
this.logger.debug(`Provider ${this.name} configured`, { config: { ...config, apiKey: '***' } });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 聊天功能 - 必须由子类实现
|
|
36
|
+
*/
|
|
37
|
+
abstract chat(request: ChatRequest): Promise<ChatResponse>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 生成命令 - 必须由子类实现
|
|
41
|
+
*/
|
|
42
|
+
abstract generateCommand(request: CommandRequest): Promise<CommandResponse>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 解释命令 - 必须由子类实现
|
|
46
|
+
*/
|
|
47
|
+
abstract explainCommand(request: ExplainRequest): Promise<ExplainResponse>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 分析结果 - 必须由子类实现
|
|
51
|
+
*/
|
|
52
|
+
abstract analyzeResult(request: AnalysisRequest): Promise<AnalysisResponse>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 健康检查 - 默认实现,子类可以重写
|
|
56
|
+
*/
|
|
57
|
+
async healthCheck(): Promise<HealthStatus> {
|
|
58
|
+
try {
|
|
59
|
+
// 简单的健康检查:验证配置和连接
|
|
60
|
+
const validation = this.validateConfig();
|
|
61
|
+
if (!validation.valid) {
|
|
62
|
+
this.lastHealthCheck = { status: HealthStatus.UNHEALTHY, timestamp: new Date() };
|
|
63
|
+
return HealthStatus.UNHEALTHY;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// TODO: 实际的网络健康检查
|
|
67
|
+
this.lastHealthCheck = { status: HealthStatus.HEALTHY, timestamp: new Date() };
|
|
68
|
+
return HealthStatus.HEALTHY;
|
|
69
|
+
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.logger.error(`Health check failed for ${this.name}`, error);
|
|
72
|
+
this.lastHealthCheck = { status: HealthStatus.UNHEALTHY, timestamp: new Date() };
|
|
73
|
+
return HealthStatus.UNHEALTHY;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 验证配置 - 默认实现,子类可以重写
|
|
79
|
+
*/
|
|
80
|
+
validateConfig(): ValidationResult {
|
|
81
|
+
const errors: string[] = [];
|
|
82
|
+
const warnings: string[] = [];
|
|
83
|
+
|
|
84
|
+
// 检查必需的配置
|
|
85
|
+
if (!this.config) {
|
|
86
|
+
errors.push('Provider not configured');
|
|
87
|
+
return { valid: false, errors, warnings };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 检查API密钥
|
|
91
|
+
if (!this.config.apiKey) {
|
|
92
|
+
errors.push('API key is required');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 检查模型
|
|
96
|
+
if (!this.config.model) {
|
|
97
|
+
warnings.push('No model specified, using default');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 检查基础URL
|
|
101
|
+
if (!this.config.baseURL) {
|
|
102
|
+
warnings.push('No base URL specified, using provider default');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 检查令牌限制
|
|
106
|
+
if (this.config.maxTokens && this.config.maxTokens < 1) {
|
|
107
|
+
errors.push('Max tokens must be greater than 0');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 检查温度参数
|
|
111
|
+
if (this.config.temperature !== undefined && (this.config.temperature < 0 || this.config.temperature > 2)) {
|
|
112
|
+
warnings.push('Temperature should be between 0 and 2');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
valid: errors.length === 0,
|
|
117
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
118
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 检查是否支持指定能力
|
|
124
|
+
*/
|
|
125
|
+
supportsCapability(capability: ProviderCapability): boolean {
|
|
126
|
+
return this.capabilities.includes(capability);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 获取提供商信息
|
|
131
|
+
*/
|
|
132
|
+
getInfo(): any {
|
|
133
|
+
return {
|
|
134
|
+
name: this.name,
|
|
135
|
+
displayName: this.displayName,
|
|
136
|
+
version: '1.0.0',
|
|
137
|
+
description: `${this.displayName} AI Provider`,
|
|
138
|
+
capabilities: this.capabilities,
|
|
139
|
+
authConfig: this.authConfig,
|
|
140
|
+
supportedModels: this.config?.model ? [this.config.model] : [],
|
|
141
|
+
configured: this.isInitialized,
|
|
142
|
+
lastHealthCheck: this.lastHealthCheck
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 获取当前配置
|
|
148
|
+
*/
|
|
149
|
+
getConfig(): ProviderConfig | null {
|
|
150
|
+
return this.config;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 检查是否已配置
|
|
155
|
+
*/
|
|
156
|
+
isConfigured(): boolean {
|
|
157
|
+
return this.isInitialized && this.config !== null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 检查是否启用
|
|
162
|
+
*/
|
|
163
|
+
isEnabled(): boolean {
|
|
164
|
+
return this.config?.enabled !== false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 获取认证头
|
|
169
|
+
*/
|
|
170
|
+
protected getAuthHeaders(): Record<string, string> {
|
|
171
|
+
const headers: Record<string, string> = {
|
|
172
|
+
'Content-Type': 'application/json'
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
switch (this.authConfig.type) {
|
|
176
|
+
case 'apiKey':
|
|
177
|
+
case 'bearer':
|
|
178
|
+
if (this.config?.apiKey) {
|
|
179
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
|
|
183
|
+
case 'basic':
|
|
184
|
+
if (this.authConfig.credentials.username && this.authConfig.credentials.password) {
|
|
185
|
+
const credentials = Buffer.from(
|
|
186
|
+
`${this.authConfig.credentials.username}:${this.authConfig.credentials.password}`
|
|
187
|
+
).toString('base64');
|
|
188
|
+
headers['Authorization'] = `Basic ${credentials}`;
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case 'oauth':
|
|
193
|
+
// TODO: 实现OAuth认证
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return headers;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 获取请求超时时间
|
|
202
|
+
*/
|
|
203
|
+
protected getTimeout(): number {
|
|
204
|
+
return this.config?.timeout || 30000; // 默认30秒
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 获取重试次数
|
|
209
|
+
*/
|
|
210
|
+
protected getRetries(): number {
|
|
211
|
+
return this.config?.retries || 3;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 执行重试逻辑
|
|
216
|
+
*/
|
|
217
|
+
protected async withRetry<T>(operation: () => Promise<T>): Promise<T> {
|
|
218
|
+
let lastError: Error | null = null;
|
|
219
|
+
const retries = this.getRetries();
|
|
220
|
+
|
|
221
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
222
|
+
try {
|
|
223
|
+
return await operation();
|
|
224
|
+
} catch (error) {
|
|
225
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
226
|
+
|
|
227
|
+
if (attempt === retries) {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 指数退避
|
|
232
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
233
|
+
this.logger.warn(
|
|
234
|
+
`Operation failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${delay}ms`,
|
|
235
|
+
{ error: lastError.message }
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
throw lastError;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 记录请求
|
|
247
|
+
*/
|
|
248
|
+
protected logRequest(request: any): void {
|
|
249
|
+
this.logger.debug(`[${this.name}] Request`, {
|
|
250
|
+
request: this.sanitizeRequest(request)
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 记录响应
|
|
256
|
+
*/
|
|
257
|
+
protected logResponse(response: any): void {
|
|
258
|
+
this.logger.debug(`[${this.name}] Response`, {
|
|
259
|
+
response: this.sanitizeResponse(response)
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 记录错误
|
|
265
|
+
*/
|
|
266
|
+
protected logError(error: any, context?: any): void {
|
|
267
|
+
this.logger.error(`[${this.name}] Error`, {
|
|
268
|
+
error: error instanceof Error ? error.message : String(error),
|
|
269
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
270
|
+
context
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 清理请求数据(移除敏感信息)
|
|
276
|
+
*/
|
|
277
|
+
protected sanitizeRequest(request: any): any {
|
|
278
|
+
const sanitized = { ...request };
|
|
279
|
+
if (sanitized.apiKey) {
|
|
280
|
+
sanitized.apiKey = '***';
|
|
281
|
+
}
|
|
282
|
+
if (sanitized.headers?.Authorization) {
|
|
283
|
+
sanitized.headers.Authorization = '***';
|
|
284
|
+
}
|
|
285
|
+
return sanitized;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 清理响应数据
|
|
290
|
+
*/
|
|
291
|
+
protected sanitizeResponse(response: any): any {
|
|
292
|
+
const sanitized = { ...response };
|
|
293
|
+
if (sanitized.data?.apiKey) {
|
|
294
|
+
sanitized.data.apiKey = '***';
|
|
295
|
+
}
|
|
296
|
+
return sanitized;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 获取基础URL
|
|
301
|
+
*/
|
|
302
|
+
protected getBaseURL(): string {
|
|
303
|
+
return this.config?.baseURL || this.getDefaultBaseURL();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 获取默认基础URL - 子类必须实现
|
|
308
|
+
*/
|
|
309
|
+
protected abstract getDefaultBaseURL(): string;
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 获取默认模型 - 子类可以重写
|
|
313
|
+
*/
|
|
314
|
+
protected getDefaultModel(): string {
|
|
315
|
+
return this.config?.model || 'default';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 转换聊天消息格式 - 子类可以重写
|
|
320
|
+
*/
|
|
321
|
+
protected transformMessages(messages: any[]): any[] {
|
|
322
|
+
return messages;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 转换响应格式 - 子类可以重写
|
|
327
|
+
*/
|
|
328
|
+
protected transformResponse(response: any): ChatResponse {
|
|
329
|
+
// 默认实现,子类应该重写
|
|
330
|
+
return {
|
|
331
|
+
message: {
|
|
332
|
+
id: this.generateId(),
|
|
333
|
+
role: 'assistant' as any,
|
|
334
|
+
content: typeof response === 'string' ? response : JSON.stringify(response),
|
|
335
|
+
timestamp: new Date()
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 生成唯一ID
|
|
342
|
+
*/
|
|
343
|
+
protected generateId(): string {
|
|
344
|
+
return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* 检查响应是否成功
|
|
349
|
+
*/
|
|
350
|
+
protected isSuccessfulResponse(response: any): boolean {
|
|
351
|
+
return response && !response.error;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 提取错误信息
|
|
356
|
+
*/
|
|
357
|
+
protected extractError(response: any): string {
|
|
358
|
+
if (response.error?.message) {
|
|
359
|
+
return response.error.message;
|
|
360
|
+
}
|
|
361
|
+
if (response.message) {
|
|
362
|
+
return response.message;
|
|
363
|
+
}
|
|
364
|
+
if (typeof response === 'string') {
|
|
365
|
+
return response;
|
|
366
|
+
}
|
|
367
|
+
return 'Unknown error';
|
|
368
|
+
}
|
|
369
|
+
}
|