@spacex110/core 0.1.11

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.
@@ -0,0 +1,249 @@
1
+ /**
2
+ * AI Provider Selector - Provider Definitions
3
+ * All supported AI providers metadata
4
+ */
5
+
6
+ import type { Provider } from './types';
7
+
8
+ const ICON_CDN_BASE = 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.77.0/files/icons';
9
+
10
+ // Provider IDs
11
+ export const PROVIDER_ID = {
12
+ OPENAI: 'openai',
13
+ ANTHROPIC: 'anthropic',
14
+ GEMINI: 'gemini',
15
+ DEEPSEEK: 'deepseek',
16
+ OPENROUTER: 'openrouter',
17
+ GROQ: 'groq',
18
+ MISTRAL: 'mistral',
19
+ MOONSHOT: 'moonshot',
20
+ QWEN: 'qwen',
21
+ ZHIPU: 'zhipu',
22
+ SILICONFLOW: 'siliconflow',
23
+ XAI: 'xai',
24
+ TOGETHER: 'together',
25
+ FIREWORKS: 'fireworks',
26
+ DEEPINFRA: 'deepinfra',
27
+ PERPLEXITY: 'perplexity',
28
+ COHERE: 'cohere',
29
+ OLLAMA: 'ollama',
30
+ DOUBAO: 'doubao',
31
+ MINIMAX: 'minimax',
32
+ CUSTOM: 'custom',
33
+ } as const;
34
+
35
+ export type ProviderId = typeof PROVIDER_ID[keyof typeof PROVIDER_ID];
36
+
37
+ export const PROVIDERS: Record<string, Provider> = {
38
+ [PROVIDER_ID.OPENAI]: {
39
+ id: PROVIDER_ID.OPENAI,
40
+ name: 'OpenAI',
41
+ baseUrl: 'https://api.openai.com/v1',
42
+ needsApiKey: true,
43
+ apiFormat: 'openai',
44
+ supportsModelsApi: true,
45
+ icon: `${ICON_CDN_BASE}/openai.svg`,
46
+ },
47
+ [PROVIDER_ID.ANTHROPIC]: {
48
+ id: PROVIDER_ID.ANTHROPIC,
49
+ name: 'Anthropic (Claude)',
50
+ baseUrl: 'https://api.anthropic.com/v1',
51
+ needsApiKey: true,
52
+ apiFormat: 'anthropic',
53
+ supportsModelsApi: true,
54
+ icon: `${ICON_CDN_BASE}/anthropic.svg`,
55
+ },
56
+ [PROVIDER_ID.GEMINI]: {
57
+ id: PROVIDER_ID.GEMINI,
58
+ name: 'Google Gemini',
59
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
60
+ needsApiKey: true,
61
+ apiFormat: 'gemini',
62
+ supportsModelsApi: true,
63
+ icon: `${ICON_CDN_BASE}/gemini.svg`,
64
+ },
65
+ [PROVIDER_ID.OPENROUTER]: {
66
+ id: PROVIDER_ID.OPENROUTER,
67
+ name: 'OpenRouter',
68
+ baseUrl: 'https://openrouter.ai/api/v1',
69
+ needsApiKey: true,
70
+ apiFormat: 'openai',
71
+ supportsModelsApi: true,
72
+ icon: `${ICON_CDN_BASE}/openrouter.svg`,
73
+ },
74
+ [PROVIDER_ID.DEEPSEEK]: {
75
+ id: PROVIDER_ID.DEEPSEEK,
76
+ name: 'DeepSeek',
77
+ baseUrl: 'https://api.deepseek.com',
78
+ needsApiKey: true,
79
+ apiFormat: 'openai',
80
+ supportsModelsApi: true,
81
+ icon: `${ICON_CDN_BASE}/deepseek.svg`,
82
+ },
83
+ [PROVIDER_ID.MOONSHOT]: {
84
+ id: PROVIDER_ID.MOONSHOT,
85
+ name: 'Moonshot (Kimi)',
86
+ baseUrl: 'https://api.moonshot.cn/v1',
87
+ needsApiKey: true,
88
+ apiFormat: 'openai',
89
+ supportsModelsApi: true,
90
+ icon: `${ICON_CDN_BASE}/moonshot.svg`,
91
+ },
92
+ [PROVIDER_ID.QWEN]: {
93
+ id: PROVIDER_ID.QWEN,
94
+ name: '通义千问 (Qwen)',
95
+ baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
96
+ needsApiKey: true,
97
+ apiFormat: 'openai',
98
+ supportsModelsApi: true,
99
+ icon: `${ICON_CDN_BASE}/qwen.svg`,
100
+ },
101
+ [PROVIDER_ID.ZHIPU]: {
102
+ id: PROVIDER_ID.ZHIPU,
103
+ name: '智谱 AI (GLM)',
104
+ baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
105
+ needsApiKey: true,
106
+ apiFormat: 'openai',
107
+ supportsModelsApi: true,
108
+ icon: `${ICON_CDN_BASE}/zhipu.svg`,
109
+ },
110
+ [PROVIDER_ID.SILICONFLOW]: {
111
+ id: PROVIDER_ID.SILICONFLOW,
112
+ name: '硅基流动 (siliconflow)',
113
+ baseUrl: 'https://api.siliconflow.cn/v1',
114
+ needsApiKey: true,
115
+ apiFormat: 'openai',
116
+ supportsModelsApi: true,
117
+ icon: `${ICON_CDN_BASE}/siliconcloud.svg`,
118
+ },
119
+
120
+ [PROVIDER_ID.DOUBAO]: {
121
+ id: PROVIDER_ID.DOUBAO,
122
+ name: '火山方舟 (Doubao)',
123
+ baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
124
+ needsApiKey: true,
125
+ apiFormat: 'openai',
126
+ supportsModelsApi: true,
127
+ icon: `${ICON_CDN_BASE}/doubao.svg`,
128
+ },
129
+ [PROVIDER_ID.MINIMAX]: {
130
+ id: PROVIDER_ID.MINIMAX,
131
+ name: 'MiniMax',
132
+ baseUrl: 'https://api.minimax.io/v1',
133
+ needsApiKey: true,
134
+ apiFormat: 'openai',
135
+ supportsModelsApi: false,
136
+ icon: `${ICON_CDN_BASE}/minimax.svg`,
137
+ },
138
+ [PROVIDER_ID.XAI]: {
139
+ id: PROVIDER_ID.XAI,
140
+ name: 'xAI (Grok)',
141
+ baseUrl: 'https://api.x.ai/v1',
142
+ needsApiKey: true,
143
+ apiFormat: 'openai',
144
+ supportsModelsApi: true,
145
+ icon: `${ICON_CDN_BASE}/grok.svg`,
146
+ },
147
+ [PROVIDER_ID.GROQ]: {
148
+ id: PROVIDER_ID.GROQ,
149
+ name: 'Groq',
150
+ baseUrl: 'https://api.groq.com/openai/v1',
151
+ needsApiKey: true,
152
+ apiFormat: 'openai',
153
+ supportsModelsApi: true,
154
+ icon: `${ICON_CDN_BASE}/groq.svg`,
155
+ },
156
+ [PROVIDER_ID.MISTRAL]: {
157
+ id: PROVIDER_ID.MISTRAL,
158
+ name: 'Mistral AI',
159
+ baseUrl: 'https://api.mistral.ai/v1',
160
+ needsApiKey: true,
161
+ apiFormat: 'openai',
162
+ supportsModelsApi: true,
163
+ icon: `${ICON_CDN_BASE}/mistral.svg`,
164
+ },
165
+ [PROVIDER_ID.TOGETHER]: {
166
+ id: PROVIDER_ID.TOGETHER,
167
+ name: 'Together AI',
168
+ baseUrl: 'https://api.together.xyz/v1',
169
+ needsApiKey: true,
170
+ apiFormat: 'openai',
171
+ supportsModelsApi: true,
172
+ icon: `${ICON_CDN_BASE}/together.svg`,
173
+ },
174
+ [PROVIDER_ID.FIREWORKS]: {
175
+ id: PROVIDER_ID.FIREWORKS,
176
+ name: 'Fireworks AI',
177
+ baseUrl: 'https://api.fireworks.ai/inference/v1',
178
+ needsApiKey: true,
179
+ apiFormat: 'openai',
180
+ supportsModelsApi: true,
181
+ icon: `${ICON_CDN_BASE}/fireworks.svg`,
182
+ },
183
+ [PROVIDER_ID.DEEPINFRA]: {
184
+ id: PROVIDER_ID.DEEPINFRA,
185
+ name: 'DeepInfra',
186
+ baseUrl: 'https://api.deepinfra.com/v1/openai',
187
+ needsApiKey: true,
188
+ apiFormat: 'openai',
189
+ supportsModelsApi: true,
190
+ icon: `${ICON_CDN_BASE}/deepinfra.svg`,
191
+ },
192
+ [PROVIDER_ID.PERPLEXITY]: {
193
+ id: PROVIDER_ID.PERPLEXITY,
194
+ name: 'Perplexity',
195
+ baseUrl: 'https://api.perplexity.ai',
196
+ needsApiKey: true,
197
+ apiFormat: 'openai',
198
+ supportsModelsApi: false,
199
+ icon: `${ICON_CDN_BASE}/perplexity.svg`,
200
+ },
201
+ [PROVIDER_ID.COHERE]: {
202
+ id: PROVIDER_ID.COHERE,
203
+ name: 'Cohere',
204
+ baseUrl: 'https://api.cohere.com/v2',
205
+ needsApiKey: true,
206
+ apiFormat: 'cohere',
207
+ supportsModelsApi: true,
208
+ icon: `${ICON_CDN_BASE}/cohere.svg`,
209
+ },
210
+ [PROVIDER_ID.OLLAMA]: {
211
+ id: PROVIDER_ID.OLLAMA,
212
+ name: 'Ollama (Local)',
213
+ baseUrl: 'http://localhost:11434/v1',
214
+ needsApiKey: false,
215
+ apiFormat: 'openai',
216
+ supportsModelsApi: true,
217
+ icon: `${ICON_CDN_BASE}/ollama.svg`,
218
+ },
219
+ [PROVIDER_ID.CUSTOM]: {
220
+ id: PROVIDER_ID.CUSTOM,
221
+ name: '自定义 (Custom)',
222
+ baseUrl: '',
223
+ needsApiKey: false,
224
+ apiFormat: 'openai',
225
+ supportsModelsApi: true,
226
+ icon: '',
227
+ },
228
+ };
229
+
230
+ /**
231
+ * Get a provider by ID
232
+ */
233
+ export function getProvider(id: string): Provider | undefined {
234
+ return PROVIDERS[id];
235
+ }
236
+
237
+ /**
238
+ * Get all providers as an array
239
+ */
240
+ export function getAllProviders(): Provider[] {
241
+ return Object.values(PROVIDERS);
242
+ }
243
+
244
+ /**
245
+ * Get providers by API format
246
+ */
247
+ export function getProvidersByFormat(format: Provider['apiFormat']): Provider[] {
248
+ return getAllProviders().filter(p => p.apiFormat === format);
249
+ }
package/src/storage.ts ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * AI Provider Selector - Storage Utilities
3
+ * Abstraction layer for config persistence
4
+ */
5
+
6
+ import CryptoJS from 'crypto-js';
7
+ import type { AIConfig, StorageAdapter } from './types';
8
+
9
+ const STORAGE_KEY = 'ai_provider_config';
10
+
11
+ // 默认加密密钥(可在初始化时自定义)
12
+ const DEFAULT_SECRET = 'aiselector';
13
+
14
+ // AES 加密/解密
15
+ function encrypt(str: string, secret: string = DEFAULT_SECRET): string {
16
+ try {
17
+ return CryptoJS.AES.encrypt(str, secret).toString();
18
+ } catch {
19
+ return str;
20
+ }
21
+ }
22
+
23
+ function decrypt(str: string, secret: string = DEFAULT_SECRET): string {
24
+ try {
25
+ const bytes = CryptoJS.AES.decrypt(str, secret);
26
+ return bytes.toString(CryptoJS.enc.Utf8);
27
+ } catch {
28
+ return str;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * 加密存储适配器(默认)
34
+ * 使用 AES 加密存储,防止 API Key 明文泄露
35
+ */
36
+ export const encryptedStorageAdapter: StorageAdapter = {
37
+ get: (key: string) => {
38
+ if (typeof window === 'undefined') return null;
39
+ try {
40
+ const encrypted = localStorage.getItem(key);
41
+ if (!encrypted) return null;
42
+ return decrypt(encrypted);
43
+ } catch {
44
+ return null;
45
+ }
46
+ },
47
+ set: (key: string, value: string) => {
48
+ if (typeof window === 'undefined') return;
49
+ try {
50
+ localStorage.setItem(key, encrypt(value));
51
+ } catch {
52
+ // ignore
53
+ }
54
+ },
55
+ remove: (key: string) => {
56
+ if (typeof window === 'undefined') return;
57
+ localStorage.removeItem(key);
58
+ },
59
+ };
60
+
61
+ /**
62
+ * 明文存储适配器(不推荐)
63
+ */
64
+ export const plainStorageAdapter: StorageAdapter = {
65
+ get: (key: string) => {
66
+ if (typeof window === 'undefined') return null;
67
+ return localStorage.getItem(key);
68
+ },
69
+ set: (key: string, value: string) => {
70
+ if (typeof window === 'undefined') return;
71
+ localStorage.setItem(key, value);
72
+ },
73
+ remove: (key: string) => {
74
+ if (typeof window === 'undefined') return;
75
+ localStorage.removeItem(key);
76
+ },
77
+ };
78
+
79
+ /** @deprecated 使用 encryptedStorageAdapter 或 plainStorageAdapter */
80
+ export const localStorageAdapter = plainStorageAdapter;
81
+
82
+ /** 默认存储适配器(加密) */
83
+ export const defaultStorageAdapter = encryptedStorageAdapter;
84
+
85
+ /**
86
+ * Create a config storage instance
87
+ */
88
+ export interface StorageOptions {
89
+ serialize?: (data: any) => string;
90
+ deserialize?: (data: string) => any;
91
+ }
92
+
93
+ export function createConfigStorage(
94
+ adapter: StorageAdapter = defaultStorageAdapter,
95
+ options: StorageOptions = {}
96
+ ) {
97
+ const serialize = options.serialize || JSON.stringify;
98
+ const deserialize = options.deserialize || JSON.parse;
99
+
100
+ return {
101
+ /**
102
+ * Save AI config
103
+ */
104
+ save(config: AIConfig): void {
105
+ try {
106
+ const serialized = serialize(config);
107
+ adapter.set(STORAGE_KEY, serialized);
108
+ } catch (e) {
109
+ console.error('Failed to save config:', e);
110
+ }
111
+ },
112
+
113
+ /**
114
+ * Load AI config
115
+ */
116
+ load(): AIConfig | null {
117
+ const raw = adapter.get(STORAGE_KEY);
118
+ if (!raw) return null;
119
+ try {
120
+ return deserialize(raw) as AIConfig;
121
+ } catch (e) {
122
+ console.error('Failed to load config:', e);
123
+ return null;
124
+ }
125
+ },
126
+
127
+ /**
128
+ * Clear AI config
129
+ */
130
+ clear(): void {
131
+ adapter.remove(STORAGE_KEY);
132
+ },
133
+ };
134
+ }
135
+