move llms to core package

This commit is contained in:
musistudio
2025-12-28 22:41:56 +08:00
parent bd55450b1d
commit 60a1f94878
24 changed files with 742 additions and 220 deletions

View File

@@ -11,9 +11,11 @@
},
"dependencies": {
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.13",

View File

@@ -144,10 +144,45 @@ export function Presets() {
const handleInstallFromMarket = async (preset: MarketPreset) => {
try {
setInstallingFromMarket(preset.id);
// 第一步:安装预设(解压到目录)
await api.installPresetFromGitHub(preset.repo, preset.name);
setToast({ message: t('presets.preset_installed'), type: 'success' });
setMarketDialogOpen(false);
await loadPresets();
// 第二步:获取预设详情(检查是否需要配置)
try {
const detail = await api.getPreset(preset.name);
const presetDetail: PresetDetail = { ...preset, ...detail };
// 检查是否需要配置
if (detail.schema && detail.schema.length > 0) {
// 需要配置,打开配置对话框
setSelectedPreset(presetDetail);
// 初始化默认值
const initialValues: Record<string, any> = {};
for (const input of detail.schema) {
initialValues[input.id] = input.defaultValue ?? '';
}
setSecrets(initialValues);
// 关闭市场对话框,打开详情对话框
setMarketDialogOpen(false);
setDetailDialogOpen(true);
setToast({ message: t('presets.preset_installed_config_required'), type: 'warning' });
} else {
// 不需要配置,直接完成
setToast({ message: t('presets.preset_installed'), type: 'success' });
setMarketDialogOpen(false);
await loadPresets();
}
} catch (error) {
// 获取详情失败,但安装成功了,刷新列表
console.error('Failed to get preset details after installation:', error);
setToast({ message: t('presets.preset_installed'), type: 'success' });
setMarketDialogOpen(false);
await loadPresets();
}
} catch (error: any) {
console.error('Failed to install preset:', error);
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
@@ -214,21 +249,81 @@ export function Presets() {
try {
setIsInstalling(true);
if (installMethod === 'url' && installUrl) {
await api.installPresetFromUrl(installUrl, installName || undefined);
} else if (installMethod === 'file' && installFile) {
await api.uploadPresetFile(installFile, installName || undefined);
} else {
setToast({ message: t('presets.please_provide_file_or_url'), type: 'warning' });
// 验证输入
if (installMethod === 'url' && !installUrl) {
setToast({ message: t('presets.please_provide_url'), type: 'warning' });
return;
}
if (installMethod === 'file' && !installFile) {
setToast({ message: t('presets.please_provide_file'), type: 'warning' });
return;
}
setToast({ message: t('presets.preset_installed'), type: 'success' });
setInstallDialogOpen(false);
setInstallUrl('');
setInstallFile(null);
setInstallName('');
await loadPresets();
// 确定预设名称
const presetName = installName || (
installMethod === 'file'
? installFile!.name.replace('.ccrsets', '')
: installUrl!.split('/').pop()!.replace('.ccrsets', '')
);
// 第一步:安装预设(解压到目录)
if (installMethod === 'url' && installUrl) {
await api.installPresetFromUrl(installUrl, presetName);
} else if (installMethod === 'file' && installFile) {
await api.uploadPresetFile(installFile, presetName);
} else {
return;
}
// 第二步:获取预设详情(检查是否需要配置)
try {
const detail = await api.getPreset(presetName);
// 检查是否需要配置
if (detail.schema && detail.schema.length > 0) {
// 需要配置,打开配置对话框
setSelectedPreset({
id: presetName,
name: presetName,
version: detail.version || '1.0.0',
installed: true,
...detail
});
// 初始化默认值
const initialValues: Record<string, any> = {};
for (const input of detail.schema) {
initialValues[input.id] = input.defaultValue ?? '';
}
setSecrets(initialValues);
// 关闭安装对话框,打开详情对话框
setInstallDialogOpen(false);
setInstallUrl('');
setInstallFile(null);
setInstallName('');
setDetailDialogOpen(true);
setToast({ message: t('presets.preset_installed_config_required'), type: 'warning' });
} else {
// 不需要配置,直接完成
setToast({ message: t('presets.preset_installed'), type: 'success' });
setInstallDialogOpen(false);
setInstallUrl('');
setInstallFile(null);
setInstallName('');
await loadPresets();
}
} catch (error) {
// 获取详情失败,但安装成功了,刷新列表
console.error('Failed to get preset details after installation:', error);
setToast({ message: t('presets.preset_installed'), type: 'success' });
setInstallDialogOpen(false);
setInstallUrl('');
setInstallFile(null);
setInstallName('');
await loadPresets();
}
} catch (error: any) {
console.error('Failed to install preset:', error);
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
@@ -262,6 +357,8 @@ export function Presets() {
setToast({ message: t('presets.preset_applied'), type: 'success' });
setDetailDialogOpen(false);
setSecrets({});
// 刷新预设列表
await loadPresets();
} catch (error: any) {
console.error('Failed to apply preset:', error);
setToast({ message: t('presets.preset_apply_failed', { error: error.message }), type: 'error' });
@@ -443,7 +540,7 @@ export function Presets() {
)}
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto py-4">
<div className="flex-1 overflow-y-auto py-4 px-2">
{selectedPreset?.description && (
<p className="text-gray-700 mb-4">{selectedPreset.description}</p>
)}

View File

@@ -87,7 +87,7 @@ export function DynamicConfigForm({
const visible = new Set<string>();
for (const field of schema) {
if (shouldShowField(field, values)) {
if (shouldShowField(field)) {
visible.add(field.id);
}
}
@@ -334,7 +334,7 @@ export function DynamicConfigForm({
{field.type === 'select' && (
<Select
value={values[field.id] || ''}
onValueChange={(value) => updateValue(field.id, value)}
onValueChange={(value: string) => updateValue(field.id, value)}
disabled={isSubmitting}
>
<SelectTrigger id={`field-${field.id}`}>
@@ -367,9 +367,9 @@ export function DynamicConfigForm({
<Checkbox
id={`field-${field.id}-${option.value}`}
checked={Array.isArray(values[field.id]) && values[field.id].includes(option.value)}
onCheckedChange={(checked) => {
onCheckedChange={(checked: boolean | 'indeterminate') => {
const current = Array.isArray(values[field.id]) ? values[field.id] : [];
if (checked) {
if (checked === true) {
updateValue(field.id, [...current, option.value]);
} else {
updateValue(field.id, current.filter((v: any) => v !== option.value));
@@ -397,7 +397,7 @@ export function DynamicConfigForm({
<Checkbox
id={`field-${field.id}`}
checked={values[field.id] || false}
onCheckedChange={(checked) => updateValue(field.id, checked)}
onCheckedChange={(checked: boolean | 'indeterminate') => updateValue(field.id, checked)}
disabled={isSubmitting}
/>
<Label htmlFor={`field-${field.id}`} className="text-sm font-normal cursor-pointer">
@@ -412,7 +412,7 @@ export function DynamicConfigForm({
id={`field-${field.id}`}
placeholder={field.placeholder}
value={values[field.id] || ''}
onChange={(e) => updateValue(field.id, e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => updateValue(field.id, e.target.value)}
rows={field.rows || 5}
disabled={isSubmitting}
/>

View File

@@ -45,7 +45,7 @@ class ApiClient {
localStorage.removeItem('apiKey');
}
}
// Update temp API key
setTempApiKey(tempApiKey: string | null) {
this.tempApiKey = tempApiKey;
@@ -56,25 +56,25 @@ class ApiClient {
const headers: Record<string, string> = {
'Accept': 'application/json',
};
// Use temp API key if available, otherwise use regular API key
if (this.tempApiKey) {
headers['X-Temp-API-Key'] = this.tempApiKey;
} else if (this.apiKey) {
headers['X-API-Key'] = this.apiKey;
}
if (contentType) {
headers['Content-Type'] = contentType;
}
return headers;
}
// Generic fetch wrapper with base URL and authentication
private async apiFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const config: RequestInit = {
...options,
headers: {
@@ -82,10 +82,10 @@ class ApiClient {
...options.headers,
},
};
try {
const response = await fetch(url, config);
// Handle 401 Unauthorized responses
if (response.status === 401) {
// Remove API key when it's invalid
@@ -101,11 +101,11 @@ class ApiClient {
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
if (response.status === 204) {
return {} as T;
}
const text = await response.text();
return text ? JSON.parse(text) : ({} as T);
@@ -139,9 +139,10 @@ class ApiClient {
}
// DELETE request
async delete<T>(endpoint: string): Promise<T> {
async delete<T>(endpoint: string, body?: any): Promise<T> {
return this.apiFetch<T>(endpoint, {
method: 'DELETE',
body: JSON.stringify(body || {}),
});
}
@@ -150,87 +151,87 @@ class ApiClient {
async getConfig(): Promise<Config> {
return this.get<Config>('/config');
}
// Update entire configuration
async updateConfig(config: Config): Promise<Config> {
return this.post<Config>('/config', config);
}
// Get providers
async getProviders(): Promise<Provider[]> {
return this.get<Provider[]>('/api/providers');
}
// Add a new provider
async addProvider(provider: Provider): Promise<Provider> {
return this.post<Provider>('/api/providers', provider);
}
// Update a provider
async updateProvider(index: number, provider: Provider): Promise<Provider> {
return this.post<Provider>(`/api/providers/${index}`, provider);
}
// Delete a provider
async deleteProvider(index: number): Promise<void> {
return this.delete<void>(`/api/providers/${index}`);
}
// Get transformers
async getTransformers(): Promise<Transformer[]> {
return this.get<Transformer[]>('/api/transformers');
}
// Add a new transformer
async addTransformer(transformer: Transformer): Promise<Transformer> {
return this.post<Transformer>('/api/transformers', transformer);
}
// Update a transformer
async updateTransformer(index: number, transformer: Transformer): Promise<Transformer> {
return this.post<Transformer>(`/api/transformers/${index}`, transformer);
}
// Delete a transformer
async deleteTransformer(index: number): Promise<void> {
return this.delete<void>(`/api/transformers/${index}`);
}
// Get configuration (new endpoint)
async getConfigNew(): Promise<Config> {
return this.get<Config>('/config');
}
// Save configuration (new endpoint)
async saveConfig(config: Config): Promise<unknown> {
return this.post<Config>('/config', config);
}
// Restart service
async restartService(): Promise<unknown> {
return this.post<void>('/restart', {});
}
// Check for updates
async checkForUpdates(): Promise<{ hasUpdate: boolean; latestVersion?: string; changelog?: string }> {
return this.get<{ hasUpdate: boolean; latestVersion?: string; changelog?: string }>('/update/check');
}
// Perform update
async performUpdate(): Promise<{ success: boolean; message: string }> {
return this.post<{ success: boolean; message: string }>('/api/update/perform', {});
}
// Get log files list
async getLogFiles(): Promise<Array<{ name: string; path: string; size: number; lastModified: string }>> {
return this.get<Array<{ name: string; path: string; size: number; lastModified: string }>>('/logs/files');
}
// Get logs from specific file
async getLogs(filePath: string): Promise<string[]> {
return this.get<string[]>(`/logs?file=${encodeURIComponent(filePath)}`);
}
// Clear logs from specific file
async clearLogs(filePath: string): Promise<void> {
return this.delete<void>(`/logs?file=${encodeURIComponent(filePath)}`);
@@ -300,7 +301,7 @@ class ApiClient {
// Delete preset
async deletePreset(name: string): Promise<any> {
return this.delete<any>(`/presets/${encodeURIComponent(name)}`);
return this.delete<any>(`/presets/${encodeURIComponent(name)}`, {});
}
// Get market presets
@@ -318,4 +319,4 @@ class ApiClient {
export const api = new ApiClient();
// Export the class for creating custom instances
export default ApiClient;
export default ApiClient;

View File

@@ -282,6 +282,9 @@
"load_presets_failed": "Failed to load presets",
"load_preset_details_failed": "Failed to load preset details",
"please_fill_field": "Please fill in {{field}}",
"load_market_failed": "Failed to load market presets"
"load_market_failed": "Failed to load market presets",
"preset_installed_config_required": "Preset installed, please complete configuration",
"please_provide_file": "Please provide a preset file",
"please_provide_url": "Please provide a preset URL"
}
}

View File

@@ -282,6 +282,9 @@
"load_presets_failed": "加载预设失败",
"load_preset_details_failed": "加载预设详情失败",
"please_fill_field": "请填写 {{field}}",
"load_market_failed": "加载市场预设失败"
"load_market_failed": "加载市场预设失败",
"preset_installed_config_required": "预设已安装,请完成配置",
"please_provide_file": "请提供预设文件",
"please_provide_url": "请提供预设 URL"
}
}