diff --git a/python_core/services/model_manager.py b/python_core/services/model_manager.py new file mode 100644 index 0000000..8939910 --- /dev/null +++ b/python_core/services/model_manager.py @@ -0,0 +1,277 @@ +""" +模特管理服务 +管理模特信息,包括模特编号、模特图片等 +""" + +import json +import uuid +import os +from pathlib import Path +from typing import List, Dict, Optional +from dataclasses import dataclass, asdict +from datetime import datetime + +from python_core.config import settings +from python_core.utils.logger import logger +from python_core.utils.jsonrpc import create_response_handler + + +@dataclass +class Model: + """模特数据结构""" + id: str + model_number: str # 模特编号 + model_image: str # 模特图片路径 + created_at: str + updated_at: str + is_active: bool = True + + +class ModelManager: + """模特管理器""" + + def __init__(self): + self.cache_dir = settings.temp_dir / "cache" + self.cache_dir.mkdir(parents=True, exist_ok=True) + + # 模特数据文件 + self.models_file = self.cache_dir / "models.json" + self.models = self._load_models() + + # 如果没有数据,创建默认模特 + if not self.models: + self._create_default_models() + + def _load_models(self) -> List[Model]: + """加载模特数据""" + if self.models_file.exists(): + try: + with open(self.models_file, 'r', encoding='utf-8') as f: + data = json.load(f) + return [Model(**item) for item in data] + except Exception as e: + logger.error(f"Failed to load models: {e}") + return [] + else: + return [] + + def _save_models(self, models: List[Model] = None): + """保存模特数据""" + if models is None: + models = self.models + + try: + data = [asdict(model) for model in models] + with open(self.models_file, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + logger.info(f"Models saved to {self.models_file}") + except Exception as e: + logger.error(f"Failed to save models: {e}") + raise + + def _create_default_models(self): + """创建默认模特数据""" + default_models = [ + { + "model_number": "M001", + "model_image": "", + }, + { + "model_number": "M002", + "model_image": "", + }, + { + "model_number": "M003", + "model_image": "", + }, + ] + + now = datetime.now().isoformat() + + for model_data in default_models: + model = Model( + id=str(uuid.uuid4()), + model_number=model_data["model_number"], + model_image=model_data["model_image"], + created_at=now, + updated_at=now + ) + self.models.append(model) + + self._save_models() + logger.info("Created default models") + + def get_all_models(self) -> List[Dict]: + """获取所有模特""" + return [asdict(model) for model in self.models if model.is_active] + + def get_model_by_id(self, model_id: str) -> Optional[Dict]: + """根据ID获取模特""" + for model in self.models: + if model.id == model_id and model.is_active: + return asdict(model) + return None + + def get_model_by_number(self, model_number: str) -> Optional[Dict]: + """根据编号获取模特""" + for model in self.models: + if model.model_number == model_number and model.is_active: + return asdict(model) + return None + + def create_model(self, model_number: str, model_image: str = "") -> Dict: + """创建新模特""" + # 检查编号是否已存在 + if self.get_model_by_number(model_number): + raise ValueError(f"Model number {model_number} already exists") + + now = datetime.now().isoformat() + + new_model = Model( + id=str(uuid.uuid4()), + model_number=model_number, + model_image=model_image, + created_at=now, + updated_at=now + ) + + self.models.append(new_model) + self._save_models() + + logger.info(f"Created new model: {model_number}") + return asdict(new_model) + + def update_model(self, model_id: str, model_number: str = None, + model_image: str = None) -> Optional[Dict]: + """更新模特""" + for i, model in enumerate(self.models): + if model.id == model_id and model.is_active: + # 如果更新编号,检查是否与其他模特冲突 + if model_number is not None and model_number != model.model_number: + existing = self.get_model_by_number(model_number) + if existing and existing['id'] != model_id: + raise ValueError(f"Model number {model_number} already exists") + model.model_number = model_number + + if model_image is not None: + model.model_image = model_image + + model.updated_at = datetime.now().isoformat() + + self.models[i] = model + self._save_models() + + logger.info(f"Updated model: {model_id}") + return asdict(model) + return None + + def delete_model(self, model_id: str) -> bool: + """删除模特(真删除)""" + for i, model in enumerate(self.models): + if model.id == model_id: + # 真删除:从列表中移除 + deleted_model = self.models.pop(i) + self._save_models() + + logger.info(f"Deleted model: {model_id} - {deleted_model.model_number}") + return True + return False + + def search_models(self, keyword: str) -> List[Dict]: + """搜索模特""" + keyword = keyword.lower() + results = [] + + for model in self.models: + if (model.is_active and + keyword in model.model_number.lower()): + results.append(asdict(model)) + + return results + + +# 全局实例 +model_manager = ModelManager() + + +def main(): + """命令行接口 - 使用JSON-RPC协议""" + import sys + import json + + # 创建响应处理器 + rpc = create_response_handler() + + if len(sys.argv) < 2: + rpc.error("INVALID_REQUEST", "No command specified") + return + + command = sys.argv[1] + + try: + if command == "get_all_models": + models = model_manager.get_all_models() + rpc.success(models) + + elif command == "get_model_by_id": + if len(sys.argv) < 3: + rpc.error("INVALID_REQUEST", "Model ID required") + return + model_id = sys.argv[2] + model = model_manager.get_model_by_id(model_id) + if model: + rpc.success(model) + else: + rpc.error("NOT_FOUND", "Model not found") + + elif command == "create_model": + if len(sys.argv) < 3: + rpc.error("INVALID_REQUEST", "Model number required") + return + model_number = sys.argv[2] + model_image = sys.argv[3] if len(sys.argv) > 3 else "" + result = model_manager.create_model(model_number, model_image) + rpc.success(result) + + elif command == "update_model": + if len(sys.argv) < 4: + rpc.error("INVALID_REQUEST", "Model ID and update data required") + return + model_id = sys.argv[2] + update_data = json.loads(sys.argv[3]) + result = model_manager.update_model( + model_id, + update_data.get('model_number'), + update_data.get('model_image') + ) + if result: + rpc.success(result) + else: + rpc.error("NOT_FOUND", "Model not found") + + elif command == "delete_model": + if len(sys.argv) < 3: + rpc.error("INVALID_REQUEST", "Model ID required") + return + model_id = sys.argv[2] + success = model_manager.delete_model(model_id) + rpc.success(success) + + elif command == "search_models": + if len(sys.argv) < 3: + rpc.error("INVALID_REQUEST", "Search keyword required") + return + keyword = sys.argv[2] + results = model_manager.search_models(keyword) + rpc.success(results) + + else: + rpc.error("INVALID_REQUEST", f"Unknown command: {command}") + + except Exception as e: + logger.error(f"Command execution failed: {e}") + rpc.error("INTERNAL_ERROR", str(e)) + + +if __name__ == "__main__": + main() diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 852f235..bd79304 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -6,6 +6,7 @@ pub mod file_system; pub mod project; pub mod template; pub mod file_utils; +pub mod model; pub mod resource_category; // Re-export all commands for easy access diff --git a/src-tauri/src/commands/model.rs b/src-tauri/src/commands/model.rs new file mode 100644 index 0000000..558a871 --- /dev/null +++ b/src-tauri/src/commands/model.rs @@ -0,0 +1,101 @@ +use serde::{Deserialize, Serialize}; +use tauri::{command, AppHandle}; +use crate::python_executor::execute_python_command; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateModelRequest { + pub model_number: String, + pub model_image: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UpdateModelRequest { + pub model_number: Option, + pub model_image: Option, +} + +/// 获取所有模特 +#[command] +pub async fn get_all_models(app: AppHandle) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.model_manager".to_string(), + "get_all_models".to_string(), + ]; + + execute_python_command(app, &args, None).await +} + +/// 根据ID获取模特 +#[command] +pub async fn get_model_by_id(app: AppHandle, model_id: String) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.model_manager".to_string(), + "get_model_by_id".to_string(), + model_id, + ]; + + execute_python_command(app, &args, None).await +} + +/// 创建新模特 +#[command] +pub async fn create_model(app: AppHandle, request: CreateModelRequest) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.model_manager".to_string(), + "create_model".to_string(), + request.model_number, + request.model_image.unwrap_or_default(), + ]; + + execute_python_command(app, &args, None).await +} + +/// 更新模特 +#[command] +pub async fn update_model( + app: AppHandle, + model_id: String, + request: UpdateModelRequest, +) -> Result { + let request_json = serde_json::to_string(&request) + .map_err(|e| format!("Failed to serialize request: {}", e))?; + + let args = vec![ + "-m".to_string(), + "python_core.services.model_manager".to_string(), + "update_model".to_string(), + model_id, + request_json, + ]; + + execute_python_command(app, &args, None).await +} + +/// 删除模特 +#[command] +pub async fn delete_model(app: AppHandle, model_id: String) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.model_manager".to_string(), + "delete_model".to_string(), + model_id, + ]; + + execute_python_command(app, &args, None).await +} + +/// 搜索模特 +#[command] +pub async fn search_models(app: AppHandle, keyword: String) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.model_manager".to_string(), + "search_models".to_string(), + keyword, + ]; + + execute_python_command(app, &args, None).await +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index db3a0cb..0170c55 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -56,6 +56,12 @@ pub fn run() { commands::delete_project, commands::search_projects, commands::open_project_directory, + commands::model::get_all_models, + commands::model::get_model_by_id, + commands::model::create_model, + commands::model::update_model, + commands::model::delete_model, + commands::model::search_models, commands::file_utils::read_image_as_data_url ]) .run(tauri::generate_context!()) diff --git a/src/App.tsx b/src/App.tsx index 76f4213..c1205c4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import TemplateManagePage from './pages/TemplateManagePage' import TemplateDetailPage from './pages/TemplateDetailPage' import ResourceCategoryPage from './pages/ResourceCategoryPage' import ProjectManagePage from './pages/ProjectManagePage' +import ModelManagePage from './pages/ModelManagePage' import KVTestPage from './pages/KVTestPage' function App() { @@ -22,6 +23,7 @@ function App() { } /> } /> } /> + } /> } /> } /> diff --git a/src/components/ModelCard.tsx b/src/components/ModelCard.tsx new file mode 100644 index 0000000..3fd17e8 --- /dev/null +++ b/src/components/ModelCard.tsx @@ -0,0 +1,65 @@ +import React from 'react' +import { Edit, Trash2, User } from 'lucide-react' +import { Model } from '../services/modelService' +import ProjectImage from './ProjectImage' + +interface ModelCardProps { + model: Model + onEdit: (model: Model) => void + onDelete: (modelId: string) => void +} + +const ModelCard: React.FC = ({ + model, + onEdit, + onDelete +}) => { + return ( +
+ {/* 模特图片 */} +
+ {model.model_image ? ( + + ) : ( +
+ +
+ )} + + {/* 操作按钮 */} +
+ + +
+
+ +
+ {/* 模特编号 */} +
+

{model.model_number}

+

+ 创建于 {new Date(model.created_at).toLocaleDateString()} +

+
+
+
+ ) +} + +export default ModelCard diff --git a/src/components/ModelForm.tsx b/src/components/ModelForm.tsx new file mode 100644 index 0000000..b7e2b58 --- /dev/null +++ b/src/components/ModelForm.tsx @@ -0,0 +1,144 @@ +import React from 'react' +import { Save, X, Package } from 'lucide-react' +import { open } from '@tauri-apps/plugin-dialog' +import ProjectImage from './ProjectImage' + +interface ModelFormData { + model_number: string + model_image: string +} + +interface ModelFormProps { + isOpen: boolean + isEditing: boolean + formData: ModelFormData + onFormDataChange: (data: ModelFormData) => void + onSubmit: () => void + onCancel: () => void +} + +const ModelForm: React.FC = ({ + isOpen, + isEditing, + formData, + onFormDataChange, + onSubmit, + onCancel +}) => { + if (!isOpen) return null + + const selectImage = async () => { + try { + const imagePath = await open({ + multiple: false, + title: '选择模特图片', + filters: [ + { + name: '图片文件', + extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg'] + } + ] + }) + + if (imagePath && typeof imagePath === 'string') { + onFormDataChange({ ...formData, model_image: imagePath }) + } + } catch (error) { + console.error('Failed to select image:', error) + } + } + + return ( +
+
+ {/* 表单标题 */} +
+

+ {isEditing ? '编辑模特' : '新建模特'} +

+ +
+ + {/* 表单内容 */} +
+ {/* 模特编号 */} +
+ + onFormDataChange({ ...formData, model_number: e.target.value })} + placeholder="请输入模特编号,如:M001" + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+ + {/* 模特图片 */} +
+ +
+ onFormDataChange({ ...formData, model_image: e.target.value })} + placeholder="请选择模特图片文件" + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + readOnly + /> + +
+ {/* 图片预览 */} + {formData.model_image && ( +
+

预览:

+
+ +
+
+ )} +
+
+ + {/* 表单按钮 */} +
+ + +
+
+
+ ) +} + +export default ModelForm diff --git a/src/components/ModelList.tsx b/src/components/ModelList.tsx new file mode 100644 index 0000000..ee40eaf --- /dev/null +++ b/src/components/ModelList.tsx @@ -0,0 +1,80 @@ +import React from 'react' +import { Plus, User } from 'lucide-react' +import { Model } from '../services/modelService' +import ModelCard from './ModelCard' + +interface ModelListProps { + models: Model[] + loading: boolean + searchTerm: string + onEdit: (model: Model) => void + onDelete: (modelId: string) => void + onCreateNew: () => void +} + +const ModelList: React.FC = ({ + models, + loading, + searchTerm, + onEdit, + onDelete, + onCreateNew +}) => { + const filteredModels = models.filter(model => + model.model_number.toLowerCase().includes(searchTerm.toLowerCase()) + ) + + // 加载状态 + if (loading) { + return ( +
+ {Array.from({ length: 8 }).map((_, index) => ( +
+
+
+
+
+
+
+ ))} +
+ ) + } + + // 空状态 + if (filteredModels.length === 0) { + return ( +
+ +
+ {searchTerm ? '没有找到匹配的模特' : '暂无模特'} +
+ {!searchTerm && ( + + )} +
+ ) + } + + // 模特列表 + return ( +
+ {filteredModels.map((model) => ( + + ))} +
+ ) +} + +export default ModelList diff --git a/src/components/ModelSearchBar.tsx b/src/components/ModelSearchBar.tsx new file mode 100644 index 0000000..80ec48f --- /dev/null +++ b/src/components/ModelSearchBar.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { Search, Plus } from 'lucide-react' + +interface ModelSearchBarProps { + searchTerm: string + onSearchChange: (value: string) => void + onCreateNew: () => void +} + +const ModelSearchBar: React.FC = ({ + searchTerm, + onSearchChange, + onCreateNew +}) => { + return ( +
+
+ + onSearchChange(e.target.value)} + className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent w-full" + /> +
+ + +
+ ) +} + +export default ModelSearchBar diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 9dd5fbc..ba9a6cd 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import { Link, useLocation } from 'react-router-dom' -import { Home, Video, Settings, FolderOpen, Music, Image, Sparkles, Layout, Clock, CheckCircle, XCircle, List, Tags } from 'lucide-react' +import { Home, Video, Settings, FolderOpen, Music, Image, Sparkles, Layout, Clock, CheckCircle, XCircle, List, Tags, User } from 'lucide-react' import { clsx } from 'clsx' const Sidebar: React.FC = () => { @@ -25,6 +25,7 @@ const Sidebar: React.FC = () => { { path: '/templates', icon: Layout, label: '模板管理' }, { path: '/resource-categories', icon: Tags, label: '分类管理' }, { path: '/projects', icon: FolderOpen, label: '项目' }, + { path: '/models', icon: User, label: '模特管理' }, { path: '/media', icon: Image, label: '媒体库' }, { path: '/audio', icon: Music, label: '音频' }, { path: '/settings', icon: Settings, label: '设置' }, diff --git a/src/pages/ModelManagePage.tsx b/src/pages/ModelManagePage.tsx new file mode 100644 index 0000000..f23328b --- /dev/null +++ b/src/pages/ModelManagePage.tsx @@ -0,0 +1,164 @@ +import React, { useState, useEffect } from 'react' +import { ModelService, Model } from '../services/modelService' +import ModelSearchBar from '../components/ModelSearchBar' +import ModelList from '../components/ModelList' +import ModelForm from '../components/ModelForm' + +interface FormData { + model_number: string + model_image: string +} + +const ModelManagePage: React.FC = () => { + const [models, setModels] = useState([]) + const [loading, setLoading] = useState(true) + const [searchTerm, setSearchTerm] = useState('') + const [editingModel, setEditingModel] = useState(null) + const [showCreateForm, setShowCreateForm] = useState(false) + const [formData, setFormData] = useState({ + model_number: '', + model_image: '' + }) + + useEffect(() => { + loadModels() + }, []) + + const loadModels = async () => { + try { + setLoading(true) + const response = await ModelService.getAllModels() + if (response.status && response.data) { + setModels(response.data) + } else { + console.error('Failed to load models:', response.msg) + } + } catch (error) { + console.error('Failed to load models:', error) + } finally { + setLoading(false) + } + } + + const handleCreateModel = async () => { + try { + const response = await ModelService.createModel(formData) + if (response.status && response.data) { + setModels([...models, response.data]) + handleCancelForm() + } else { + console.error('创建失败:', response.msg || '未知错误') + alert('创建失败: ' + (response.msg || '未知错误')) + } + } catch (error) { + console.error('Failed to create model:', error) + alert('创建失败: ' + (error instanceof Error ? error.message : '未知错误')) + } + } + + const handleUpdateModel = async () => { + if (!editingModel) return + + try { + const response = await ModelService.updateModel(editingModel.id, formData) + if (response.status && response.data) { + const updatedModels = models.map(model => + model.id === editingModel.id ? response.data! : model + ) + setModels(updatedModels) + handleCancelForm() + } else { + console.error('更新失败:', response.msg || '未知错误') + alert('更新失败: ' + (response.msg || '未知错误')) + } + } catch (error) { + console.error('Failed to update model:', error) + alert('更新失败: ' + (error instanceof Error ? error.message : '未知错误')) + } + } + + const handleDeleteModel = async (modelId: string) => { + if (!confirm('确定要删除这个模特吗?')) return + + try { + const response = await ModelService.deleteModel(modelId) + if (response.status) { + setModels(models.filter(model => model.id !== modelId)) + } else { + console.error('删除失败:', response.msg || '未知错误') + alert('删除失败: ' + (response.msg || '未知错误')) + } + } catch (error) { + console.error('Failed to delete model:', error) + alert('删除失败: ' + (error instanceof Error ? error.message : '未知错误')) + } + } + + const handleEditModel = (model: Model) => { + setEditingModel(model) + setFormData({ + model_number: model.model_number, + model_image: model.model_image + }) + setShowCreateForm(true) + } + + const handleCreateNew = () => { + setEditingModel(null) + setFormData({ model_number: '', model_image: '' }) + setShowCreateForm(true) + } + + const handleCancelForm = () => { + setEditingModel(null) + setShowCreateForm(false) + setFormData({ model_number: '', model_image: '' }) + } + + const handleSubmitForm = () => { + if (editingModel) { + handleUpdateModel() + } else { + handleCreateModel() + } + } + + return ( +
+ {/* 页面标题 */} +
+

模特管理

+

管理模特信息,包括模特编号、模特图片等

+
+ + {/* 搜索和操作栏 */} + + + {/* 模特列表 */} + + + {/* 模特表单 */} + +
+ ) +} + +export default ModelManagePage diff --git a/src/services/modelService.ts b/src/services/modelService.ts new file mode 100644 index 0000000..8e0639c --- /dev/null +++ b/src/services/modelService.ts @@ -0,0 +1,213 @@ +import { invoke } from '@tauri-apps/api/core' + +export interface Model { + id: string + model_number: string + model_image: string + created_at: string + updated_at: string + is_active: boolean +} + +export interface CreateModelRequest { + model_number: string + model_image?: string +} + +export interface UpdateModelRequest { + model_number?: string + model_image?: string +} + +export interface ApiResponse { + status: boolean + data?: T + msg?: string +} + +export class ModelService { + /** + * 获取所有模特 + */ + static async getAllModels(): Promise> { + try { + console.log('Calling get_all_models...') + const result = await invoke('get_all_models') + console.log('Raw result from Tauri:', result) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + console.log('Parsed result:', parsed) + return parsed as ApiResponse + } catch (parseError) { + console.error('Failed to parse JSON response:', parseError) + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to get all models:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 根据ID获取模特 + */ + static async getModelById(modelId: string): Promise> { + try { + const result = await invoke('get_model_by_id', { modelId }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to get model by id:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 创建新模特 + */ + static async createModel(request: CreateModelRequest): Promise> { + try { + console.log('Calling create_model with:', request) + const result = await invoke('create_model', { request }) + console.log('Raw result from Tauri:', result) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + console.log('Parsed result:', parsed) + return parsed as ApiResponse + } catch (parseError) { + console.error('Failed to parse JSON response:', parseError) + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to create model:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 更新模特 + */ + static async updateModel( + modelId: string, + request: UpdateModelRequest + ): Promise> { + try { + const result = await invoke('update_model', { modelId, request }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to update model:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 删除模特 + */ + static async deleteModel(modelId: string): Promise> { + try { + const result = await invoke('delete_model', { modelId }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to delete model:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } + + /** + * 搜索模特 + */ + static async searchModels(keyword: string): Promise> { + try { + const result = await invoke('search_models', { keyword }) + + if (typeof result === 'string') { + try { + const parsed = JSON.parse(result) + return parsed as ApiResponse + } catch (parseError) { + return { + status: false, + msg: `Invalid JSON response: ${result}` + } + } + } + + return result as ApiResponse + } catch (error) { + console.error('Failed to search models:', error) + return { + status: false, + msg: error instanceof Error ? error.message : 'Unknown error' + } + } + } +}