fix install preset error

This commit is contained in:
musistudio
2026-01-01 17:53:26 +08:00
parent ec7ac8cc9f
commit 5ac38d3d0f
14 changed files with 1665 additions and 94 deletions

View File

@@ -14,7 +14,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Upload, Link, Trash2, Info, Download, CheckCircle2, AlertCircle, Loader2, ArrowLeft, Store, Search, Package } from "lucide-react";
import { Upload, Link, Trash2, Info, Download, Check, CheckCircle2, AlertCircle, Loader2, ArrowLeft, Store, Search, Package } from "lucide-react";
import { Toast } from "@/components/ui/toast";
import { DynamicConfigForm } from "./preset/DynamicConfigForm";
@@ -193,7 +193,13 @@ export function Presets() {
}
} catch (error: any) {
console.error('Failed to install preset:', error);
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
// Check if it's an "already installed" error
const errorMessage = error.message || '';
if (errorMessage.includes('already installed') || errorMessage.includes('已安装')) {
setToast({ message: t('presets.preset_already_installed'), type: 'warning' });
} else {
setToast({ message: t('presets.preset_install_failed', { error: errorMessage }), type: 'error' });
}
} finally {
setInstallingFromMarket(null);
}
@@ -345,7 +351,13 @@ export function Presets() {
}
} catch (error: any) {
console.error('Failed to install preset:', error);
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
// Check if it's an "already installed" error
const errorMessage = error.message || '';
if (errorMessage.includes('already installed') || errorMessage.includes('已安装')) {
setToast({ message: t('presets.preset_already_installed'), type: 'warning' });
} else {
setToast({ message: t('presets.preset_install_failed', { error: errorMessage }), type: 'error' });
}
} finally {
setIsInstalling(false);
}
@@ -636,56 +648,76 @@ export function Presets() {
</div>
) : (
<div className="space-y-3">
{filteredMarketPresets.map((preset) => (
<div
key={preset.id}
className="p-4 border rounded-lg hover:bg-gray-50 transition-colors"
>
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<h3 className="font-semibold text-lg">{preset.name}</h3>
</div>
{preset.description && (
<p className="text-sm text-gray-600 mb-2">{preset.description}</p>
)}
<div className="flex items-center gap-4 text-sm text-gray-500">
{preset.author && (
<div className="flex items-center gap-1.5">
<span className="font-medium">{t('presets.by', { author: preset.author })}</span>
<a
href={`https://github.com/${preset.repo}`}
target="_blank"
rel="noopener noreferrer"
className="text-gray-600 hover:text-gray-900 transition-colors"
title={t('presets.github_repository')}
>
<i className="ri-github-fill text-xl"></i>
</a>
</div>
{filteredMarketPresets.map((preset) => {
// Check if this preset is already installed by repo
const isInstalled = presets.some(p => {
// Extract repo from repository field (handle both formats)
let installedRepo = '';
if (p.repository) {
// Remove GitHub URL prefix if present
installedRepo = p.repository.replace(/^https:\/\/github\.com\//, '').replace(/\.git$/, '');
}
// Match by repo (preferred), or name as fallback
return installedRepo === preset.repo || p.name === preset.name;
});
return (
<div
key={preset.id}
className="p-4 border rounded-lg hover:bg-gray-50 transition-colors"
>
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<h3 className="font-semibold text-lg">{preset.name}</h3>
</div>
{preset.description && (
<p className="text-sm text-gray-600 mb-2">{preset.description}</p>
)}
<div className="flex items-center gap-4 text-sm text-gray-500">
{preset.author && (
<div className="flex items-center gap-1.5">
<span className="font-medium">{t('presets.by', { author: preset.author })}</span>
<a
href={`https://github.com/${preset.repo}`}
target="_blank"
rel="noopener noreferrer"
className="text-gray-600 hover:text-gray-900 transition-colors"
title={t('presets.github_repository')}
>
<i className="ri-github-fill text-xl"></i>
</a>
</div>
)}
</div>
</div>
<Button
onClick={() => handleInstallFromMarket(preset)}
disabled={installingFromMarket === preset.id || isInstalled}
variant={isInstalled ? "secondary" : "default"}
className="shrink-0"
>
{installingFromMarket === preset.id ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t('presets.installing')}
</>
) : isInstalled ? (
<>
<Check className="mr-2 h-4 w-4" />
{t('presets.installed_label')}
</>
) : (
<>
<Download className="mr-2 h-4 w-4" />
{t('presets.install')}
</>
)}
</Button>
</div>
<Button
onClick={() => handleInstallFromMarket(preset)}
disabled={installingFromMarket === preset.id}
className="shrink-0"
>
{installingFromMarket === preset.id ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t('presets.installing')}
</>
) : (
<>
<Download className="mr-2 h-4 w-4" />
{t('presets.install')}
</>
)}
</Button>
</div>
</div>
))}
);
})}
</div>
)}
</div>

View File

@@ -99,7 +99,17 @@ class ApiClient {
}
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
// Try to get detailed error message from response body
let errorMessage = `API request failed: ${response.status} ${response.statusText}`;
try {
const errorData = await response.json();
if (errorData.error || errorData.message) {
errorMessage = errorData.message || errorData.error || errorMessage;
}
} catch {
// If parsing fails, use default error message
}
throw new Error(errorMessage);
}
if (response.status === 204) {

View File

@@ -255,6 +255,7 @@
"view_details": "View Details",
"install": "Install",
"installing": "Installing...",
"installed_label": "Installed",
"apply": "Apply Preset",
"applying": "Applying...",
"close": "Close",
@@ -274,6 +275,7 @@
"delete_dialog_description": "Are you sure you want to delete preset \"{{name}}\"? This action cannot be undone.",
"preset_installed": "Preset installed successfully",
"preset_install_failed": "Failed to install preset: {{error}}",
"preset_already_installed": "Preset already installed. Please delete it first if you want to reinstall.",
"preset_applied": "Preset applied successfully",
"preset_apply_failed": "Failed to apply preset: {{error}}",
"preset_deleted": "Preset deleted successfully",

View File

@@ -255,6 +255,7 @@
"view_details": "查看详情",
"install": "安装",
"installing": "安装中...",
"installed_label": "已安装",
"apply": "应用预设",
"applying": "应用中...",
"close": "关闭",
@@ -262,7 +263,6 @@
"install_dialog_title": "安装预设",
"install_dialog_description": "从 GitHub 仓库安装预设",
"from_url": "从 GitHub",
"github_repository": "GitHub 仓库",
"preset_url": "仓库 URL",
"preset_url_placeholder": "https://github.com/owner/repo",
"preset_name": "预设名称 (可选)",
@@ -274,6 +274,7 @@
"delete_dialog_description": "您确定要删除预设 \"{{name}}\" 吗?此操作无法撤销。",
"preset_installed": "预设安装成功",
"preset_install_failed": "预设安装失败:{{error}}",
"preset_already_installed": "预设已经安装。如需重新安装,请先删除现有预设。",
"preset_applied": "预设应用成功",
"preset_apply_failed": "预设应用失败:{{error}}",
"preset_deleted": "预设删除成功",