素材分类管理

This commit is contained in:
root 2025-07-11 00:29:09 +08:00
parent 197f083e3d
commit 010080f61e
8 changed files with 853 additions and 2 deletions

View File

@ -0,0 +1,281 @@
"""
素材分类管理服务
管理素材分类信息包括分类标题AI识别提示词和展示颜色
"""
import json
import uuid
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
@dataclass
class ResourceCategory:
"""素材分类数据结构"""
id: str
title: str # 分类标题
ai_prompt: str # AI识别提示词
color: str # 展示颜色 (hex格式如 #FF5733)
created_at: str
updated_at: str
is_active: bool = True
class ResourceCategoryManager:
"""素材分类管理器"""
def __init__(self):
self.cache_dir = settings.temp_dir / "cache"
self.cache_dir.mkdir(parents=True, exist_ok=True)
# 分类数据文件
self.categories_file = self.cache_dir / "resource_category.json"
self.categories = self._load_categories()
def _load_categories(self) -> List[ResourceCategory]:
"""加载分类数据"""
if self.categories_file.exists():
try:
with open(self.categories_file, 'r', encoding='utf-8') as f:
data = json.load(f)
return [ResourceCategory(**item) for item in data]
except Exception as e:
logger.error(f"Failed to load categories: {e}")
return self._create_default_categories()
else:
return self._create_default_categories()
def _create_default_categories(self) -> List[ResourceCategory]:
"""创建默认分类"""
now = datetime.now().isoformat()
default_categories = [
ResourceCategory(
id=str(uuid.uuid4()),
title="人物视频",
ai_prompt="包含人物、人脸、人体的视频素材",
color="#FF6B6B",
created_at=now,
updated_at=now
),
ResourceCategory(
id=str(uuid.uuid4()),
title="风景视频",
ai_prompt="自然风景、城市景观、建筑物的视频素材",
color="#4ECDC4",
created_at=now,
updated_at=now
),
ResourceCategory(
id=str(uuid.uuid4()),
title="动物视频",
ai_prompt="动物、宠物、野生动物的视频素材",
color="#45B7D1",
created_at=now,
updated_at=now
),
ResourceCategory(
id=str(uuid.uuid4()),
title="音乐音效",
ai_prompt="背景音乐、音效、声音素材",
color="#96CEB4",
created_at=now,
updated_at=now
),
ResourceCategory(
id=str(uuid.uuid4()),
title="文字图片",
ai_prompt="文字、标题、图标、图形素材",
color="#FFEAA7",
created_at=now,
updated_at=now
),
ResourceCategory(
id=str(uuid.uuid4()),
title="特效素材",
ai_prompt="转场、特效、动画、滤镜素材",
color="#DDA0DD",
created_at=now,
updated_at=now
)
]
self._save_categories(default_categories)
return default_categories
def _save_categories(self, categories: List[ResourceCategory] = None):
"""保存分类数据"""
if categories is None:
categories = self.categories
try:
data = [asdict(category) for category in categories]
with open(self.categories_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
logger.info(f"Categories saved to {self.categories_file}")
except Exception as e:
logger.error(f"Failed to save categories: {e}")
raise
def get_all_categories(self) -> List[Dict]:
"""获取所有分类"""
return [asdict(category) for category in self.categories if category.is_active]
def get_category_by_id(self, category_id: str) -> Optional[Dict]:
"""根据ID获取分类"""
for category in self.categories:
if category.id == category_id and category.is_active:
return asdict(category)
return None
def create_category(self, title: str, ai_prompt: str, color: str) -> Dict:
"""创建新分类"""
now = datetime.now().isoformat()
new_category = ResourceCategory(
id=str(uuid.uuid4()),
title=title,
ai_prompt=ai_prompt,
color=color,
created_at=now,
updated_at=now
)
self.categories.append(new_category)
self._save_categories()
logger.info(f"Created new category: {title}")
return asdict(new_category)
def update_category(self, category_id: str, title: str = None,
ai_prompt: str = None, color: str = None) -> Optional[Dict]:
"""更新分类"""
for i, category in enumerate(self.categories):
if category.id == category_id and category.is_active:
if title is not None:
category.title = title
if ai_prompt is not None:
category.ai_prompt = ai_prompt
if color is not None:
category.color = color
category.updated_at = datetime.now().isoformat()
self.categories[i] = category
self._save_categories()
logger.info(f"Updated category: {category_id}")
return asdict(category)
return None
def delete_category(self, category_id: str) -> bool:
"""删除分类(软删除)"""
for i, category in enumerate(self.categories):
if category.id == category_id and category.is_active:
category.is_active = False
category.updated_at = datetime.now().isoformat()
self.categories[i] = category
self._save_categories()
logger.info(f"Deleted category: {category_id}")
return True
return False
def search_categories(self, keyword: str) -> List[Dict]:
"""搜索分类"""
keyword = keyword.lower()
results = []
for category in self.categories:
if (category.is_active and
(keyword in category.title.lower() or
keyword in category.ai_prompt.lower())):
results.append(asdict(category))
return results
# 全局实例
resource_category_manager = ResourceCategoryManager()
def main():
"""命令行接口"""
import sys
import json
if len(sys.argv) < 2:
print(json.dumps({"status": False, "msg": "No command specified"}))
return
command = sys.argv[1]
try:
if command == "get_all_categories":
categories = resource_category_manager.get_all_categories()
print(json.dumps({"status": True, "data": categories}))
elif command == "get_category_by_id":
if len(sys.argv) < 3:
print(json.dumps({"status": False, "msg": "Category ID required"}))
return
category_id = sys.argv[2]
category = resource_category_manager.get_category_by_id(category_id)
if category:
print(json.dumps({"status": True, "data": category}))
else:
print(json.dumps({"status": False, "msg": "Category not found"}))
elif command == "create_category":
if len(sys.argv) < 5:
print(json.dumps({"status": False, "msg": "Title, prompt and color required"}))
return
title, ai_prompt, color = sys.argv[2], sys.argv[3], sys.argv[4]
result = resource_category_manager.create_category(title, ai_prompt, color)
print(json.dumps({"status": True, "data": result}))
elif command == "update_category":
if len(sys.argv) < 4:
print(json.dumps({"status": False, "msg": "Category ID and update data required"}))
return
category_id = sys.argv[2]
update_data = json.loads(sys.argv[3])
result = resource_category_manager.update_category(
category_id,
update_data.get('title'),
update_data.get('ai_prompt'),
update_data.get('color')
)
if result:
print(json.dumps({"status": True, "data": result}))
else:
print(json.dumps({"status": False, "msg": "Category not found"}))
elif command == "delete_category":
if len(sys.argv) < 3:
print(json.dumps({"status": False, "msg": "Category ID required"}))
return
category_id = sys.argv[2]
success = resource_category_manager.delete_category(category_id)
print(json.dumps({"status": True, "data": success}))
elif command == "search_categories":
if len(sys.argv) < 3:
print(json.dumps({"status": False, "msg": "Search keyword required"}))
return
keyword = sys.argv[2]
results = resource_category_manager.search_categories(keyword)
print(json.dumps({"status": True, "data": results}))
else:
print(json.dumps({"status": False, "msg": f"Unknown command: {command}"}))
except Exception as e:
logger.error(f"Command execution failed: {e}")
print(json.dumps({"status": False, "msg": str(e)}))
if __name__ == "__main__":
main()

View File

@ -5,6 +5,7 @@ pub mod ai_video;
pub mod file_system;
pub mod project;
pub mod template;
pub mod resource_category;
// Re-export all commands for easy access
pub use basic::*;
@ -13,3 +14,4 @@ pub use ai_video::*;
pub use file_system::*;
pub use project::*;
pub use template::*;
pub use resource_category::*;

View File

@ -0,0 +1,98 @@
use serde::{Deserialize, Serialize};
use tauri::{command, AppHandle};
use crate::python_executor::execute_python_command;
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateCategoryRequest {
pub title: String,
pub ai_prompt: String,
pub color: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateCategoryRequest {
pub title: Option<String>,
pub ai_prompt: Option<String>,
pub color: Option<String>,
}
/// 获取所有资源分类
#[command]
pub async fn get_all_resource_categories(app: AppHandle) -> Result<String, String> {
let args = vec![
"python_core.services.resource_category_manager".to_string(),
"get_all_categories".to_string(),
];
execute_python_command(app, &args, None).await
}
/// 根据ID获取资源分类
#[command]
pub async fn get_resource_category_by_id(app: AppHandle, category_id: String) -> Result<String, String> {
let args = vec![
"python_core.services.resource_category_manager".to_string(),
"get_category_by_id".to_string(),
category_id,
];
execute_python_command(app, &args, None).await
}
/// 创建新的资源分类
#[command]
pub async fn create_resource_category(app: AppHandle, request: CreateCategoryRequest) -> Result<String, String> {
let args = vec![
"python_core.services.resource_category_manager".to_string(),
"create_category".to_string(),
request.title,
request.ai_prompt,
request.color,
];
execute_python_command(app, &args, None).await
}
/// 更新资源分类
#[command]
pub async fn update_resource_category(
app: AppHandle,
category_id: String,
request: UpdateCategoryRequest,
) -> Result<String, String> {
let request_json = serde_json::to_string(&request)
.map_err(|e| format!("Failed to serialize request: {}", e))?;
let args = vec![
"python_core.services.resource_category_manager".to_string(),
"update_category".to_string(),
category_id,
request_json,
];
execute_python_command(app, &args, None).await
}
/// 删除资源分类
#[command]
pub async fn delete_resource_category(app: AppHandle, category_id: String) -> Result<String, String> {
let args = vec![
"python_core.services.resource_category_manager".to_string(),
"delete_category".to_string(),
category_id,
];
execute_python_command(app, &args, None).await
}
/// 搜索资源分类
#[command]
pub async fn search_resource_categories(app: AppHandle, keyword: String) -> Result<String, String> {
let args = vec![
"python_core.services.resource_category_manager".to_string(),
"search_categories".to_string(),
keyword,
];
execute_python_command(app, &args, None).await
}

View File

@ -42,7 +42,13 @@ pub fn run() {
commands::get_templates,
commands::get_template,
commands::get_template_detail,
commands::delete_template
commands::delete_template,
commands::get_all_resource_categories,
commands::get_resource_category_by_id,
commands::create_resource_category,
commands::update_resource_category,
commands::delete_resource_category,
commands::search_resource_categories
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@ -7,6 +7,7 @@ import AIVideoPage from './pages/AIVideoPage'
import SettingsPage from './pages/SettingsPage'
import TemplateManagePage from './pages/TemplateManagePage'
import TemplateDetailPage from './pages/TemplateDetailPage'
import ResourceCategoryPage from './pages/ResourceCategoryPage'
import KVTestPage from './pages/KVTestPage'
function App() {
@ -18,6 +19,7 @@ function App() {
<Route path="/ai-video" element={<AIVideoPage />} />
<Route path="/templates" element={<TemplateManagePage />} />
<Route path="/templates/:templateId" element={<TemplateDetailPage />} />
<Route path="/resource-categories" element={<ResourceCategoryPage />} />
<Route path="/kv-test" element={<KVTestPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>

View File

@ -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 } from 'lucide-react'
import { Home, Video, Settings, FolderOpen, Music, Image, Sparkles, Layout, Clock, CheckCircle, XCircle, List, Tags } from 'lucide-react'
import { clsx } from 'clsx'
const Sidebar: React.FC = () => {
@ -23,6 +23,7 @@ const Sidebar: React.FC = () => {
{ path: '/editor', icon: Video, label: '编辑器' },
{ path: '/ai-video', icon: Sparkles, label: 'AI 视频' },
{ path: '/templates', icon: Layout, label: '模板管理' },
{ path: '/resource-categories', icon: Tags, label: '分类管理' },
{ path: '/projects', icon: FolderOpen, label: '项目' },
{ path: '/media', icon: Image, label: '媒体库' },
{ path: '/audio', icon: Music, label: '音频' },

View File

@ -0,0 +1,331 @@
import React, { useState, useEffect } from 'react'
import { Plus, Edit, Trash2, Search, Save, X, Palette } from 'lucide-react'
import { ResourceCategoryService, ResourceCategory } from '../services/resourceCategoryService'
const ResourceCategoryPage: React.FC = () => {
const [categories, setCategories] = useState<ResourceCategory[]>([])
const [loading, setLoading] = useState(true)
const [searchTerm, setSearchTerm] = useState('')
const [editingCategory, setEditingCategory] = useState<ResourceCategory | null>(null)
const [showCreateForm, setShowCreateForm] = useState(false)
const [formData, setFormData] = useState({
title: '',
ai_prompt: '',
color: '#FF6B6B'
})
// 预设颜色选项
const presetColors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD',
'#FF7675', '#74B9FF', '#00B894', '#FDCB6E', '#E17055', '#A29BFE',
'#FD79A8', '#6C5CE7', '#00CEC9', '#55A3FF', '#FF9F43', '#26DE81'
]
useEffect(() => {
loadCategories()
}, [])
const loadCategories = async () => {
try {
setLoading(true)
const response = await ResourceCategoryService.getAllCategories()
if (response.status && response.data) {
setCategories(response.data)
} else {
console.error('Failed to load categories:', response.msg)
}
} catch (error) {
console.error('Failed to load categories:', error)
} finally {
setLoading(false)
}
}
const handleCreateCategory = async () => {
try {
const response = await ResourceCategoryService.createCategory(formData)
if (response.status && response.data) {
setCategories([...categories, response.data])
setShowCreateForm(false)
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B' })
} else {
alert('创建失败: ' + (response.msg || '未知错误'))
}
} catch (error) {
console.error('Failed to create category:', error)
alert('创建失败: ' + (error instanceof Error ? error.message : '未知错误'))
}
}
const handleUpdateCategory = async () => {
if (!editingCategory) return
try {
const response = await ResourceCategoryService.updateCategory(editingCategory.id, formData)
if (response.status && response.data) {
const updatedCategories = categories.map(cat =>
cat.id === editingCategory.id ? response.data! : cat
)
setCategories(updatedCategories)
setEditingCategory(null)
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B' })
} else {
alert('更新失败: ' + (response.msg || '未知错误'))
}
} catch (error) {
console.error('Failed to update category:', error)
alert('更新失败: ' + (error instanceof Error ? error.message : '未知错误'))
}
}
const handleDeleteCategory = async (categoryId: string) => {
if (!confirm('确定要删除这个分类吗?')) return
try {
const response = await ResourceCategoryService.deleteCategory(categoryId)
if (response.status) {
setCategories(categories.filter(cat => cat.id !== categoryId))
} else {
alert('删除失败: ' + (response.msg || '未知错误'))
}
} catch (error) {
console.error('Failed to delete category:', error)
alert('删除失败: ' + (error instanceof Error ? error.message : '未知错误'))
}
}
const startEdit = (category: ResourceCategory) => {
setEditingCategory(category)
setFormData({
title: category.title,
ai_prompt: category.ai_prompt,
color: category.color
})
setShowCreateForm(false)
}
const cancelEdit = () => {
setEditingCategory(null)
setShowCreateForm(false)
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B' })
}
const filteredCategories = categories.filter(category =>
category.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
category.ai_prompt.toLowerCase().includes(searchTerm.toLowerCase())
)
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">...</div>
</div>
)
}
return (
<div className="p-6">
{/* 页面标题 */}
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900"></h1>
<p className="text-gray-600 mt-2">AI识别提示词和展示颜色</p>
</div>
{/* 搜索和创建按钮 */}
<div className="flex items-center justify-between mb-6">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
<input
type="text"
placeholder="搜索分类..."
value={searchTerm}
onChange={(e) => setSearchTerm(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"
/>
</div>
<button
onClick={() => {
setShowCreateForm(true)
setEditingCategory(null)
setFormData({ title: '', ai_prompt: '', color: '#FF6B6B' })
}}
className="ml-4 flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Plus size={20} className="mr-2" />
</button>
</div>
{/* 分类列表 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredCategories.map((category) => (
<div key={category.id} className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
{/* 分类标题和颜色 */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center">
<div
className="w-4 h-4 rounded-full mr-3"
style={{ backgroundColor: category.color }}
/>
<h3 className="text-lg font-semibold text-gray-900">{category.title}</h3>
</div>
<div className="flex items-center space-x-2">
<button
onClick={() => startEdit(category)}
className="p-2 text-gray-400 hover:text-blue-600 transition-colors"
title="编辑"
>
<Edit size={16} />
</button>
<button
onClick={() => handleDeleteCategory(category.id)}
className="p-2 text-gray-400 hover:text-red-600 transition-colors"
title="删除"
>
<Trash2 size={16} />
</button>
</div>
</div>
{/* AI提示词 */}
<div className="mb-4">
<p className="text-sm text-gray-600 leading-relaxed">{category.ai_prompt}</p>
</div>
{/* 创建时间 */}
<div className="text-xs text-gray-400">
{new Date(category.created_at).toLocaleDateString()}
</div>
</div>
))}
</div>
{/* 空状态 */}
{filteredCategories.length === 0 && (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
{searchTerm ? '没有找到匹配的分类' : '暂无分类'}
</div>
{!searchTerm && (
<button
onClick={() => setShowCreateForm(true)}
className="text-blue-600 hover:text-blue-700"
>
</button>
)}
</div>
)}
{/* 创建/编辑表单弹窗 */}
{(showCreateForm || editingCategory) && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl w-full max-w-md mx-4">
{/* 表单标题 */}
<div className="flex items-center justify-between p-6 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">
{editingCategory ? '编辑分类' : '新建分类'}
</h2>
<button
onClick={cancelEdit}
className="text-gray-400 hover:text-gray-600"
>
<X size={24} />
</button>
</div>
{/* 表单内容 */}
<div className="p-6 space-y-4">
{/* 分类标题 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
placeholder="请输入分类标题"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
{/* AI识别提示词 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
AI识别提示词
</label>
<textarea
value={formData.ai_prompt}
onChange={(e) => setFormData({ ...formData, ai_prompt: e.target.value })}
placeholder="描述这类素材的特征用于AI自动分类"
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
/>
</div>
{/* 展示颜色 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<div className="flex items-center space-x-3 mb-3">
<div
className="w-8 h-8 rounded-lg border-2 border-gray-300"
style={{ backgroundColor: formData.color }}
/>
<input
type="color"
value={formData.color}
onChange={(e) => setFormData({ ...formData, color: e.target.value })}
className="w-16 h-8 border border-gray-300 rounded cursor-pointer"
/>
<span className="text-sm text-gray-600">{formData.color}</span>
</div>
{/* 预设颜色 */}
<div className="grid grid-cols-9 gap-2">
{presetColors.map((color) => (
<button
key={color}
onClick={() => setFormData({ ...formData, color })}
className={`w-8 h-8 rounded-lg border-2 transition-all ${
formData.color === color
? 'border-gray-800 scale-110'
: 'border-gray-300 hover:border-gray-400'
}`}
style={{ backgroundColor: color }}
title={color}
/>
))}
</div>
</div>
</div>
{/* 表单按钮 */}
<div className="flex items-center justify-end space-x-3 p-6 border-t border-gray-200">
<button
onClick={cancelEdit}
className="px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
>
</button>
<button
onClick={editingCategory ? handleUpdateCategory : handleCreateCategory}
disabled={!formData.title.trim() || !formData.ai_prompt.trim()}
className="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
<Save size={16} className="mr-2" />
{editingCategory ? '保存' : '创建'}
</button>
</div>
</div>
</div>
)}
</div>
)
}
export default ResourceCategoryPage

View File

@ -0,0 +1,130 @@
import { invoke } from '@tauri-apps/api/core'
export interface ResourceCategory {
id: string
title: string
ai_prompt: string
color: string
created_at: string
updated_at: string
is_active: boolean
}
export interface CreateCategoryRequest {
title: string
ai_prompt: string
color: string
}
export interface UpdateCategoryRequest {
title?: string
ai_prompt?: string
color?: string
}
export interface ApiResponse<T> {
status: boolean
data?: T
msg?: string
}
export class ResourceCategoryService {
/**
*
*/
static async getAllCategories(): Promise<ApiResponse<ResourceCategory[]>> {
try {
const result = await invoke('get_all_resource_categories')
return result as ApiResponse<ResourceCategory[]>
} catch (error) {
console.error('Failed to get all categories:', error)
return {
status: false,
msg: error instanceof Error ? error.message : 'Unknown error'
}
}
}
/**
* ID获取分类
*/
static async getCategoryById(categoryId: string): Promise<ApiResponse<ResourceCategory>> {
try {
const result = await invoke('get_resource_category_by_id', { categoryId })
return result as ApiResponse<ResourceCategory>
} catch (error) {
console.error('Failed to get category by id:', error)
return {
status: false,
msg: error instanceof Error ? error.message : 'Unknown error'
}
}
}
/**
*
*/
static async createCategory(request: CreateCategoryRequest): Promise<ApiResponse<ResourceCategory>> {
try {
const result = await invoke('create_resource_category', { request })
return result as ApiResponse<ResourceCategory>
} catch (error) {
console.error('Failed to create category:', error)
return {
status: false,
msg: error instanceof Error ? error.message : 'Unknown error'
}
}
}
/**
*
*/
static async updateCategory(
categoryId: string,
request: UpdateCategoryRequest
): Promise<ApiResponse<ResourceCategory>> {
try {
const result = await invoke('update_resource_category', { categoryId, request })
return result as ApiResponse<ResourceCategory>
} catch (error) {
console.error('Failed to update category:', error)
return {
status: false,
msg: error instanceof Error ? error.message : 'Unknown error'
}
}
}
/**
*
*/
static async deleteCategory(categoryId: string): Promise<ApiResponse<boolean>> {
try {
const result = await invoke('delete_resource_category', { categoryId })
return result as ApiResponse<boolean>
} catch (error) {
console.error('Failed to delete category:', error)
return {
status: false,
msg: error instanceof Error ? error.message : 'Unknown error'
}
}
}
/**
*
*/
static async searchCategories(keyword: string): Promise<ApiResponse<ResourceCategory[]>> {
try {
const result = await invoke('search_resource_categories', { keyword })
return result as ApiResponse<ResourceCategory[]>
} catch (error) {
console.error('Failed to search categories:', error)
return {
status: false,
msg: error instanceof Error ? error.message : 'Unknown error'
}
}
}
}