From 08ad7a332e6520553e38cda5aeed6285644b2077 Mon Sep 17 00:00:00 2001 From: coso Date: Tue, 3 Feb 2026 12:42:19 +0800 Subject: [PATCH] =?UTF-8?q?release:=20v0.55.0=20-=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=B8=8E=20ESLint=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新版本号到 0.55.0 - 修复 ESLint 警告:将 CREATION_MODE_CONFIG 移至单独的 constants.tsx - 修复 ESLint 警告:将 CreationMode 类型移至 types.ts - 修复 ProviderPoolPage 未使用变量警告 - 修复 Rust 测试中缺少的 Config 字段 Co-Authored-By: Claude Opus 4.5 --- package.json | 2 +- src-tauri/Cargo.lock | 6 +- src-tauri/Cargo.toml | 4 +- src-tauri/src/config/tests.rs | 7 + src-tauri/src/config/types.rs | 69 +++++++ src/components/AppSidebar.tsx | 49 ++++- .../agent/chat/components/EmptyState.tsx | 168 ++++++++++------- .../agent/chat/components/constants.tsx | 46 +++++ src/components/agent/chat/components/types.ts | 5 + src/components/agent/chat/index.tsx | 3 +- src/components/projects/ProjectCategories.tsx | 59 +++++- .../provider-pool/ProviderPoolPage.tsx | 58 +++--- src/components/settings/GeneralSettings.tsx | 169 ++++++++++++++++++ src/components/ui/select.tsx | 5 +- src/hooks/useTauri.ts | 20 +++ 15 files changed, 558 insertions(+), 112 deletions(-) create mode 100644 src/components/agent/chat/components/constants.tsx create mode 100644 src/components/agent/chat/components/types.ts diff --git a/package.json b/package.json index effa62879..985d71541 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "proxycast", "private": true, - "version": "0.54.0", + "version": "0.55.0", "type": "module", "repository": { "type": "git", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f0e801ac7..0cccfa45f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6553,7 +6553,7 @@ dependencies = [ [[package]] name = "proxycast" -version = "0.54.0" +version = "0.55.0" dependencies = [ "anyhow", "arboard", @@ -6635,7 +6635,7 @@ dependencies = [ [[package]] name = "proxycast-core" -version = "0.54.0" +version = "0.55.0" dependencies = [ "chrono", "dirs 5.0.1", @@ -6651,7 +6651,7 @@ dependencies = [ [[package]] name = "proxycast-infra" -version = "0.54.0" +version = "0.55.0" dependencies = [ "chrono", "dashmap 5.5.3", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a20f58e76..3d8d7af29 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/*"] resolver = "2" [workspace.package] -version = "0.54.0" +version = "0.55.0" edition = "2021" authors = ["you"] repository = "https://github.com/aiclientproxy/proxycast" @@ -164,7 +164,7 @@ version = "2.4" [package] name = "proxycast" -version = "0.54.0" +version = "0.55.0" description = "AI API Proxy Desktop App" authors = ["you"] edition = "2021" diff --git a/src-tauri/src/config/tests.rs b/src-tauri/src/config/tests.rs index 120cd57a4..dd9d9e42b 100644 --- a/src-tauri/src/config/tests.rs +++ b/src-tauri/src/config/tests.rs @@ -2,6 +2,7 @@ //! //! 使用 proptest 进行属性测试 +use crate::config::types::{ContentCreatorConfig, NavigationConfig}; use crate::config::{ collapse_tilde, contains_tilde, expand_tilde, Config, ConfigManager, CustomProviderConfig, HotReloadManager, InjectionSettings, LoggingConfig, ProviderConfig, ProvidersConfig, @@ -189,6 +190,8 @@ fn arb_config() -> impl Strategy { agent: crate::config::NativeAgentConfig::default(), language: "zh".to_string(), experimental: crate::config::ExperimentalFeatures::default(), + content_creator: ContentCreatorConfig::default(), + navigation: NavigationConfig::default(), }) } @@ -427,6 +430,8 @@ fn arb_valid_config() -> impl Strategy { agent: crate::config::NativeAgentConfig::default(), language: "zh".to_string(), experimental: crate::config::ExperimentalFeatures::default(), + content_creator: ContentCreatorConfig::default(), + navigation: NavigationConfig::default(), }) } @@ -476,6 +481,8 @@ fn arb_invalid_config() -> impl Strategy { agent: crate::config::NativeAgentConfig::default(), language: "zh".to_string(), experimental: crate::config::ExperimentalFeatures::default(), + content_creator: ContentCreatorConfig::default(), + navigation: NavigationConfig::default(), }; // 根据类型使配置无效 match invalid_type { diff --git a/src-tauri/src/config/types.rs b/src-tauri/src/config/types.rs index ccc0fe278..3184d67c2 100644 --- a/src-tauri/src/config/types.rs +++ b/src-tauri/src/config/types.rs @@ -425,6 +425,12 @@ pub struct Config { /// 实验室功能配置 #[serde(default)] pub experimental: ExperimentalFeatures, + /// 内容创作配置 + #[serde(default)] + pub content_creator: ContentCreatorConfig, + /// 导航栏配置 + #[serde(default)] + pub navigation: NavigationConfig, } // ============ Native Agent 配置类型 ============ @@ -487,6 +493,67 @@ impl Default for NativeAgentConfig { } } +// ============ 内容创作配置类型 ============ + +/// 内容创作主题配置 +/// +/// 配置内容创作模式中显示的主题标签 +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ContentCreatorConfig { + /// 启用的主题列表 + #[serde(default = "default_enabled_themes")] + pub enabled_themes: Vec, +} + +fn default_enabled_themes() -> Vec { + vec![ + "general".to_string(), + "social-media".to_string(), + "poster".to_string(), + "music".to_string(), + "video".to_string(), + "novel".to_string(), + ] +} + +impl Default for ContentCreatorConfig { + fn default() -> Self { + Self { + enabled_themes: default_enabled_themes(), + } + } +} + +// ============ 导航栏配置类型 ============ + +/// 导航栏模块配置 +/// +/// 配置左侧导航栏中显示的功能模块 +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct NavigationConfig { + /// 启用的导航模块列表 + #[serde(default = "default_enabled_nav_items")] + pub enabled_items: Vec, +} + +fn default_enabled_nav_items() -> Vec { + vec![ + "agent".to_string(), + "projects".to_string(), + "image-gen".to_string(), + "api-server".to_string(), + "provider-pool".to_string(), + ] +} + +impl Default for NavigationConfig { + fn default() -> Self { + Self { + enabled_items: default_enabled_nav_items(), + } + } +} + // ============ 实验室功能配置类型 ============ /// 截图对话功能配置 @@ -1653,6 +1720,8 @@ impl Default for Config { models: ModelsConfig::default(), agent: NativeAgentConfig::default(), experimental: ExperimentalFeatures::default(), + content_creator: ContentCreatorConfig::default(), + navigation: NavigationConfig::default(), } } } diff --git a/src/components/AppSidebar.tsx b/src/components/AppSidebar.tsx index 53934a8d2..9f79acb4a 100644 --- a/src/components/AppSidebar.tsx +++ b/src/components/AppSidebar.tsx @@ -4,7 +4,7 @@ * 类似 cherry-studio 的图标导航栏,始终显示在应用左侧 */ -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import styled from "styled-components"; import { Tooltip, @@ -30,6 +30,7 @@ import { import * as LucideIcons from "lucide-react"; import { getPluginsForSurface, PluginUIInfo } from "@/lib/api/pluginUI"; import { Page, PageParams } from "@/types/page"; +import { getConfig } from "@/hooks/useTauri"; interface AppSidebarProps { currentPage: Page; @@ -143,6 +144,15 @@ function getIconByName(iconName: string): LucideIcon { return IconComponent || Activity; } +/** 默认启用的导航模块 */ +const DEFAULT_ENABLED_NAV_ITEMS = [ + "agent", + "projects", + "image-gen", + "api-server", + "provider-pool", +]; + export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) { const [theme, setTheme] = useState<"light" | "dark">(() => { if (typeof window !== "undefined") { @@ -153,11 +163,46 @@ export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) { return "light"; }); + // 启用的导航模块 + const [enabledNavItems, setEnabledNavItems] = useState( + DEFAULT_ENABLED_NAV_ITEMS, + ); + // 已安装的侧边栏插件列表 const [sidebarPlugins, setSidebarPlugins] = useState([]); // 刷新触发器 const [refreshTrigger, setRefreshTrigger] = useState(0); + // 加载导航配置 + useEffect(() => { + const loadNavConfig = async () => { + try { + const config = await getConfig(); + setEnabledNavItems( + config.navigation?.enabled_items || DEFAULT_ENABLED_NAV_ITEMS, + ); + } catch (error) { + console.error("加载导航配置失败:", error); + } + }; + loadNavConfig(); + + // 监听导航配置变更事件 + const handleNavConfigChange = () => { + loadNavConfig(); + }; + window.addEventListener("nav-config-changed", handleNavConfigChange); + + return () => { + window.removeEventListener("nav-config-changed", handleNavConfigChange); + }; + }, []); + + // 过滤后的导航项 + const filteredMenuItems = useMemo(() => { + return mainMenuItems.filter((item) => enabledNavItems.includes(item.id)); + }, [enabledNavItems]); + // 加载侧边栏插件 useEffect(() => { const loadSidebarPlugins = async () => { @@ -221,7 +266,7 @@ export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) { - {mainMenuItems.map((item) => ( + {filteredMenuItems.map((item) => ( = { - guided: { - name: "引导模式", - icon: , - aiRole: "教练(提问引导)", - userInvolvement: "high", - description: "追求真实性、个人经历类内容", - }, - fast: { - name: "快速模式", - icon: , - aiRole: "助手(生成初稿)", - userInvolvement: "low", - description: "信息整理、快速产出", - }, - hybrid: { - name: "混合模式", - icon: , - aiRole: "协作者(写框架)", - userInvolvement: "medium", - description: "平衡质量和效率", - }, - framework: { - name: "框架模式", - icon: , - aiRole: "填充者(按框架生成)", - userInvolvement: "medium", - description: "固定格式文档(报告、标书)", - }, -}; +import { getConfig } from "@/hooks/useTauri"; +import type { CreationMode } from "./types"; +import { CREATION_MODE_CONFIG } from "./constants"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { @@ -351,7 +300,7 @@ interface EmptyStateProps { } // Scenarios Configuration - 与 ProjectType 统一 -const CATEGORIES = [ +const ALL_CATEGORIES = [ { id: "general", label: "通用对话", @@ -379,6 +328,16 @@ const CATEGORIES = [ { id: "novel", label: "小说创作", icon: }, ]; +/** 默认启用的主题 */ +const DEFAULT_ENABLED_THEMES = [ + "general", + "social-media", + "poster", + "music", + "video", + "novel", +]; + // 需要显示创作模式选择器的主题 const CREATION_THEMES = [ "social-media", @@ -537,6 +496,44 @@ export const EmptyState: React.FC = ({ onThemeChange, onRecommendationClick, }) => { + // 从配置中读取启用的主题 + const [enabledThemes, setEnabledThemes] = useState( + DEFAULT_ENABLED_THEMES, + ); + + // 加载配置 + useEffect(() => { + const loadEnabledThemes = async () => { + try { + const config = await getConfig(); + if (config.content_creator?.enabled_themes) { + setEnabledThemes(config.content_creator.enabled_themes); + } + } catch (e) { + console.error("加载主题配置失败:", e); + } + }; + loadEnabledThemes(); + + // 监听主题配置变更事件 + const handleThemeConfigChange = () => { + loadEnabledThemes(); + }; + window.addEventListener("theme-config-changed", handleThemeConfigChange); + + return () => { + window.removeEventListener( + "theme-config-changed", + handleThemeConfigChange, + ); + }; + }, []); + + // 过滤后的主题列表 + const categories = ALL_CATEGORIES.filter((cat) => + enabledThemes.includes(cat.id), + ); + // 使用外部传入的 activeTheme,如果有 onThemeChange 则使用受控模式 const handleThemeChange = (theme: string) => { if (onThemeChange) { @@ -552,6 +549,9 @@ export const EmptyState: React.FC = ({ const [ratio, setRatio] = useState("3:4"); const [style, setStyle] = useState("minimal"); const [depth, setDepth] = useState("deep"); + // Popover 打开状态 + const [ratioPopoverOpen, setRatioPopoverOpen] = useState(false); + const [stylePopoverOpen, setStylePopoverOpen] = useState(false); const handleSend = () => { if (!input.trim()) return; @@ -635,7 +635,7 @@ export const EmptyState: React.FC = ({ - {CATEGORIES.map((cat) => ( + {categories.map((cat) => ( = ({ {getPlatformLabel(platform)} - +
选择要创作的内容平台
@@ -753,7 +753,7 @@ export const EmptyState: React.FC = ({ {CREATION_MODE_CONFIG[creationMode].name} - +
选择创作模式
@@ -788,7 +788,7 @@ export const EmptyState: React.FC = ({ - + 深度解析 快速概览 @@ -808,7 +808,13 @@ export const EmptyState: React.FC = ({ {activeTheme === "poster" && ( <> - + { + setRatioPopoverOpen(open); + if (open) setStylePopoverOpen(false); + }} + > - +
宽高比
@@ -832,7 +842,10 @@ export const EmptyState: React.FC = ({ setRatio(r)} + onClick={() => { + setRatio(r); + setRatioPopoverOpen(false); + }} >
{r} @@ -843,7 +856,13 @@ export const EmptyState: React.FC = ({
- + { + setStylePopoverOpen(open); + if (open) setRatioPopoverOpen(false); + }} + > - +
{error && ( @@ -372,20 +364,6 @@ export const ProviderPoolPage = forwardRef( {/* Category Tabs - 第一行:分类选择 */}
- +
{/* OAuth 凭证分类 - Provider 选择图标网格 */} diff --git a/src/components/settings/GeneralSettings.tsx b/src/components/settings/GeneralSettings.tsx index d084d5e8d..8e8be5750 100644 --- a/src/components/settings/GeneralSettings.tsx +++ b/src/components/settings/GeneralSettings.tsx @@ -11,6 +11,7 @@ import { Info, RotateCcw, Volume2, + Palette, } from "lucide-react"; import { cn, validateProxyUrl } from "@/lib/utils"; import { getConfig, saveConfig, Config } from "@/hooks/useTauri"; @@ -21,11 +22,61 @@ import { useSoundContext } from "@/contexts/useSoundContext"; type Theme = "light" | "dark" | "system"; +/** 所有可用的内容创作主题 */ +const ALL_CONTENT_THEMES = [ + { id: "general", label: "通用" }, + { id: "social-media", label: "社媒内容" }, + { id: "poster", label: "图文海报" }, + { id: "music", label: "歌词曲谱" }, + { id: "video", label: "短视频" }, + { id: "novel", label: "小说" }, + { id: "knowledge", label: "知识探索" }, + { id: "planning", label: "计划规划" }, + { id: "document", label: "办公文档" }, +] as const; + +/** 默认启用的主题 */ +const DEFAULT_ENABLED_THEMES = [ + "general", + "social-media", + "poster", + "music", + "video", + "novel", +]; + +/** 所有可用的导航模块 */ +const ALL_NAV_ITEMS = [ + { id: "agent", label: "AI Agent" }, + { id: "projects", label: "项目" }, + { id: "image-gen", label: "图片生成" }, + { id: "api-server", label: "API Server" }, + { id: "provider-pool", label: "凭证池" }, + { id: "terminal", label: "终端" }, + { id: "tools", label: "工具" }, + { id: "plugins", label: "插件中心" }, +] as const; + +/** 默认启用的导航模块 */ +const DEFAULT_ENABLED_NAV_ITEMS = [ + "agent", + "projects", + "image-gen", + "api-server", + "provider-pool", +]; + export function GeneralSettings() { const [theme, setTheme] = useState("system"); const [launchOnStartup, setLaunchOnStartup] = useState(false); const [minimizeToTray, setMinimizeToTray] = useState(true); const [language, setLanguageState] = useState("zh"); + const [enabledThemes, setEnabledThemes] = useState( + DEFAULT_ENABLED_THEMES, + ); + const [enabledNavItems, setEnabledNavItems] = useState( + DEFAULT_ENABLED_NAV_ITEMS, + ); const { resetOnboarding } = useOnboardingState(); const { setLanguage: setI18nLanguage } = useI18nPatch(); const { soundEnabled, setSoundEnabled, playToolcallSound } = @@ -64,6 +115,12 @@ export function GeneralSettings() { setProxyUrl(c.proxy_url || ""); setMinimizeToTray(c.minimize_to_tray ?? true); setLanguageState((c.language || "zh") as Language); + setEnabledThemes( + c.content_creator?.enabled_themes || DEFAULT_ENABLED_THEMES, + ); + setEnabledNavItems( + c.navigation?.enabled_items || DEFAULT_ENABLED_NAV_ITEMS, + ); } catch (e) { console.error("加载配置失败:", e); } finally { @@ -130,6 +187,60 @@ export function GeneralSettings() { } }; + // 切换内容创作主题 + const handleThemeToggle = async (themeId: string) => { + if (!config) return; + const newThemes = enabledThemes.includes(themeId) + ? enabledThemes.filter((t) => t !== themeId) + : [...enabledThemes, themeId]; + + // 至少保留一个主题 + if (newThemes.length === 0) return; + + setEnabledThemes(newThemes); + try { + const newConfig = { + ...config, + content_creator: { enabled_themes: newThemes }, + }; + await saveConfig(newConfig); + setConfig(newConfig); + // 触发内容创作主题更新 + window.dispatchEvent(new CustomEvent("theme-config-changed")); + } catch (err) { + console.error("保存主题设置失败:", err); + // 回滚 + setEnabledThemes(enabledThemes); + } + }; + + // 切换导航模块 + const handleNavItemToggle = async (itemId: string) => { + if (!config) return; + const newItems = enabledNavItems.includes(itemId) + ? enabledNavItems.filter((i) => i !== itemId) + : [...enabledNavItems, itemId]; + + // 至少保留一个模块 + if (newItems.length === 0) return; + + setEnabledNavItems(newItems); + try { + const newConfig = { + ...config, + navigation: { enabled_items: newItems }, + }; + await saveConfig(newConfig); + setConfig(newConfig); + // 触发侧边栏更新 + window.dispatchEvent(new CustomEvent("nav-config-changed")); + } catch (err) { + console.error("保存导航设置失败:", err); + // 回滚 + setEnabledNavItems(enabledNavItems); + } + }; + const themeOptions = [ { id: "light" as Theme, label: "浅色", icon: Sun }, { id: "dark" as Theme, label: "深色", icon: Moon }, @@ -291,6 +402,64 @@ export function GeneralSettings() { + {/* 内容创作主题 */} +
+
+ +
+

内容创作主题

+

+ 选择在内容创作模式中显示的主题标签 +

+
+
+
+ {ALL_CONTENT_THEMES.map((t) => ( + + ))} +
+
+ + {/* 导航模块 */} +
+
+ +
+

导航模块

+

+ 选择在左侧导航栏中显示的功能模块 +

+
+
+
+ {ALL_NAV_ITEMS.map((item) => ( + + ))} +
+
+ {/* 重新运行引导 */}
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index ca9776ff7..58d6cc93f 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -101,11 +101,13 @@ const SelectValue: React.FC = ({ placeholder }) => { interface SelectContentProps { className?: string; children: React.ReactNode; + side?: "top" | "bottom"; } const SelectContent: React.FC = ({ className, children, + side = "bottom", }) => { const context = useContext(SelectContext); if (!context) throw new Error("SelectContent must be used within Select"); @@ -117,7 +119,8 @@ const SelectContent: React.FC = ({ return (
diff --git a/src/hooks/useTauri.ts b/src/hooks/useTauri.ts index a9b533eff..3acf0472a 100644 --- a/src/hooks/useTauri.ts +++ b/src/hooks/useTauri.ts @@ -129,6 +129,22 @@ export interface ExperimentalFeatures { screenshot_chat: SmartInputConfig; } +/** + * 内容创作配置 + */ +export interface ContentCreatorConfig { + /** 启用的主题列表 */ + enabled_themes: string[]; +} + +/** + * 导航栏配置 + */ +export interface NavigationConfig { + /** 启用的导航模块列表 */ + enabled_items: string[]; +} + export interface Config { server: { host: string; @@ -173,6 +189,10 @@ export interface Config { language: string; /** 实验室功能配置 */ experimental?: ExperimentalFeatures; + /** 内容创作配置 */ + content_creator?: ContentCreatorConfig; + /** 导航栏配置 */ + navigation?: NavigationConfig; } export interface LogEntry {