diff --git a/README.md b/README.md index dd6e3ec..0439ae6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@
+Persona Agent + # Persona Agent **你的本地 AI Agent 工作站** @@ -7,22 +9,30 @@ 创建和管理多个 AI Agent,赋予它们工具、技能和性格,让它们帮你完成任务。 [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +![Platform](https://img.shields.io/badge/Platform-macOS%20%7C%20Windows-informational) +[![Release](https://img.shields.io/github/v/release/Code-MonkeyZhang/persona-agent?include_prereleases)](https://github.com/Code-MonkeyZhang/persona-agent/releases)
- +## 截图 -## 它能做什么 +![主界面](assets/screenshot-main.jpg) -Persona Agent 让你在本地创建多个 AI Agent,每个有独立的角色设定、模型配置和会话历史。你可以用一个 Agent 做编程助手,另一个做写作顾问,互不干扰。Agent 可以调用内置工具来执行命令、读写文件,也可以通过 MCP 协议连接外部工具服务器扩展能力,还可以加载 Skill 来获得专业知识。 +![Agent 形象](assets/screenshot-companion.jpg) -对话支持流式输出和完整的 Markdown 渲染,代码块一键复制,Agent 的推理过程和工具调用细节都可以展开查看。如果你想换一种氛围,可以打开伴侣模式:全屏展示角色立绘,Agent 会根据对话自动切换表情,配合语音合成朗读回复。 + -内置 Cloudflare Tunnel,一键把本地服务暴露到公网,从手机或其它设备远程访问你的 Agent。 + -**支持的模型供应商:** OpenAI、Anthropic、Google、DeepSeek、MiniMax、xAI、Groq、Mistral、OpenRouter、Cerebras、Fireworks 等 17+ 家。每个 Agent 独立配置默认模型,每个会话可以临时切换。 +## 核心功能 -**跨平台:** macOS(Apple Silicon / Intel)、Windows x64。下载安装包即可使用,无需安装任何运行时。 +- **多 Agent 管理** — 创建多个独立 Agent,每个有自己的角色设定、模型配置和会话历史,互不干扰 +- **17+ 模型供应商** — OpenAI、Anthropic、Google、DeepSeek、MiniMax、xAI、Groq、Mistral、OpenRouter、Cerebras、Fireworks 等,每个 Agent 独立配置默认模型,每个会话可以临时切换 +- **工具调用** — 内置工具(执行命令、读写文件),MCP 协议连接外部工具服务器,Skill 加载获得专业知识 +- **流式对话** — 完整 Markdown 渲染,代码块一键复制,推理过程和工具调用细节可展开查看 +- **Agent 形象** — 全屏展示角色立绘,Agent 根据对话自动切换表情,配合语音合成朗读回复 +- **远程访问** — 内置 Cloudflare Tunnel,一键把本地服务暴露到公网,从手机或其它设备远程访问 +- **跨平台** — macOS(Apple Silicon / Intel)、Windows x64,下载即用,无需安装运行时 ## 下载安装 diff --git a/assets/logo-rounded.png b/assets/logo-rounded.png new file mode 100644 index 0000000..1e6d7cb Binary files /dev/null and b/assets/logo-rounded.png differ diff --git a/assets/screenshot-agent-settings.jpg b/assets/screenshot-agent-settings.jpg new file mode 100644 index 0000000..082fafc Binary files /dev/null and b/assets/screenshot-agent-settings.jpg differ diff --git a/assets/screenshot-companion.jpg b/assets/screenshot-companion.jpg new file mode 100644 index 0000000..f310504 Binary files /dev/null and b/assets/screenshot-companion.jpg differ diff --git a/assets/screenshot-main.jpg b/assets/screenshot-main.jpg new file mode 100644 index 0000000..f514106 Binary files /dev/null and b/assets/screenshot-main.jpg differ diff --git a/assets/screenshot-model-config.jpg b/assets/screenshot-model-config.jpg new file mode 100644 index 0000000..67451b2 Binary files /dev/null and b/assets/screenshot-model-config.jpg differ diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 518ff7b..938f1c0 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -1,71 +1,110 @@ -# Animus Desktop +# Persona Desktop -A modern Electron desktop application for Animus. +Persona Agent 的跨平台桌面客户端。内嵌后端二进制,下载安装包后无需任何配置即可使用。 -## Tech Stack +## 技术栈 -- **Core**: Electron 28 + React 18 + TypeScript 5.3 -- **Build Tool**: electron-vite 2.0 -- **UI**: Tailwind CSS 3.4 -- **State Management**: Zustand 4.5 +- **框架**: Electron 28 + React 18 + TypeScript 5.3 +- **构建工具**: electron-vite 2.0 +- **UI**: Tailwind CSS 3.4 + Radix UI +- **状态管理**: Zustand 4.5 - **Markdown**: react-markdown 10 + remark-gfm + rehype-highlight +- **虚拟列表**: react-virtuoso -## Prerequisites +## 核心功能 -- Node.js 18+ -- npm or yarn +### Agent 管理 -## Development +- 创建、编辑、删除多个 Agent,每个有独立的角色设定和模型配置 +- Agent 头像自定义,支持上传 pose 表情和背景图 +- 左侧 Agent 列表一键切换 + +### 流式对话 + +- SSE 流式输出,实时渲染 Markdown(表格、代码块高亮、LaTeX) +- 代码块一键复制,Agent 推理过程和工具调用可折叠查看 +- 会话历史管理,支持创建、重命名、删除会话 + +### 伴侣模式 + +- 全屏展示角色立绘,背景图随 Agent 配置切换 +- Agent 根据对话自动切换表情(pose) +- 语音合成朗读回复(MiniMax TTS) + +### MCP & Skills + +- 通过 MCP 协议连接外部工具服务器扩展 Agent 能力 +- 加载 Skill 赋予 Agent 专业知识 +- 设置中心统一管理 MCP 服务和 Skills + +### 模型供应商 + +支持 OpenAI、Anthropic、Google、DeepSeek、MiniMax、xAI、Groq、Mistral、OpenRouter、Cerebras、Fireworks 等 17+ 家供应商。每个 Agent 独立配置默认模型,每个会话可以临时切换。 + +### 其他 + +- Cloudflare Tunnel 一键内网穿透,远程访问 Agent +- 自动启动内嵌的后端二进制,用户无需手动管理进程 +- 支持 macOS(Apple Silicon / Intel)和 Windows x64 + +## 开发 ```bash -# Install dependencies -npm install +# 安装依赖 +cd packages/server && bun install +cd packages/desktop && npm install -# Start development server +# 开发模式(自动编译 server + 启动 Electron) npm run dev -# Type check -npm run typecheck - -# Build for production +# 仅构建前端 npm run build -# Preview production build -npm run preview +# 构建安装包 +npm run dist # 当前平台 +npm run dist:mac # macOS (dmg) +npm run dist:win # Windows (nsis) ``` -## Project Structure +其他命令: + +```bash +npm run typecheck # 类型检查 +npm run test # 单元测试 (Vitest) +``` + +## 项目结构 ``` desktop/ ├── src/ -│ ├── main/ # Electron main process -│ ├── preload/ # Preload scripts -│ └── renderer/ # React renderer process -│ ├── components/ # React components -│ ├── stores/ # Zustand stores -│ ├── lib/ # Utilities and API -│ └── types/ # TypeScript types -├── out/ # Build output -├── resources/ # App resources -└── electron.vite.config.ts +│ ├── main/ # Electron 主进程 +│ │ ├── index.ts # 应用入口,窗口创建、进程管理 +│ │ ├── server-manager.ts # 后端进程生命周期管理 +│ │ └── store/ # electron-store 持久化配置 +│ ├── preload/ # 预加载脚本 +│ └── renderer/ # React 前端 +│ ├── components/ # UI 组件 +│ │ ├── AgentSidebar.tsx # Agent 列表侧边栏 +│ │ ├── AgentEditor.tsx # Agent 编辑器 +│ │ ├── MessageList.tsx # 消息列表(虚拟滚动) +│ │ ├── CompanionPanel.tsx # 伴侣模式全屏面板 +│ │ ├── SettingsPage.tsx # 设置中心(5 个 Tab) +│ │ ├── ModelSelector.tsx # 模型选择器 +│ │ └── ... +│ ├── stores/ # Zustand 状态管理 +│ ├── lib/ # API 请求、工具函数 +│ └── types/ # TypeScript 类型定义 +├── build/ # 图标等构建资源 +├── resources/ # 种子数据(默认 Agent) +├── electron.vite.config.ts +└── electron-builder.yml ``` -## Features (Phase 0) - -- ✅ Basic Electron app setup -- ✅ React + TypeScript configuration -- ✅ Tailwind CSS integration -- ✅ Connection to Animus server -- ✅ Real-time streaming chat (SSE) -- ✅ Message display (user/assistant/thinking/tooluse) -- ✅ Auto-scroll to latest messages -- ✅ Connection status indicator - -## Connection +## 与 Server 的关系 -The app connects to the Animus server at `http://localhost:3847`. +Desktop 的安装包内嵌了编译好的 Persona Agent Server 二进制。启动时主进程自动在后台拉起 Server,无需用户手动操作。开发模式下从项目构建产物加载 Server。 ## License -MIT +[MIT](../../LICENSE) diff --git a/packages/desktop/build/icon-mac.png b/packages/desktop/build/icon-mac.png index bee8c73..95d5115 100644 Binary files a/packages/desktop/build/icon-mac.png and b/packages/desktop/build/icon-mac.png differ diff --git a/packages/desktop/build/icon-win.png b/packages/desktop/build/icon-win.png index b82fce9..b4d187d 100644 Binary files a/packages/desktop/build/icon-win.png and b/packages/desktop/build/icon-win.png differ diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 570ae7d..ddcc175 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -29,6 +29,7 @@ "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^1.1.1", diff --git a/packages/desktop/src/main/index.ts b/packages/desktop/src/main/index.ts index 818531c..ac920fb 100644 --- a/packages/desktop/src/main/index.ts +++ b/packages/desktop/src/main/index.ts @@ -3,7 +3,7 @@ * @description Electron 主进程入口文件 - 负责应用程序生命周期管理、窗口创建、进程管理和 IPC 通信 */ -import { app, BrowserWindow, ipcMain, dialog } from 'electron'; +import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron'; import { join } from 'path'; import { spawn } from 'child_process'; import type { ChildProcess } from 'child_process'; @@ -290,6 +290,13 @@ app.whenReady().then(async () => { } ); + /** + * IPC 处理器:使用系统默认浏览器打开指定 URL + */ + ipcMain.handle('open-external', (_event, url: string) => { + return shell.openExternal(url); + }); + await startServer(); createWindow(); diff --git a/packages/desktop/src/preload/index.ts b/packages/desktop/src/preload/index.ts index 27a636a..5f033b8 100644 --- a/packages/desktop/src/preload/index.ts +++ b/packages/desktop/src/preload/index.ts @@ -63,6 +63,13 @@ const api = { body: ArrayBuffer; }> => ipcRenderer.invoke('proxy-fetch', url, options), + /** + * 使用系统默认浏览器打开指定 URL + * @param url - 要打开的 URL + */ + openExternal: (url: string): Promise => + ipcRenderer.invoke('open-external', url), + /** 窗口控制方法集合,每个方法通过 IPC 转发到主进程执行。 */ windowControls: { minimize: () => ipcRenderer.invoke('window:minimize'), diff --git a/packages/desktop/src/renderer/App.tsx b/packages/desktop/src/renderer/App.tsx index 562638a..10fc2a3 100644 --- a/packages/desktop/src/renderer/App.tsx +++ b/packages/desktop/src/renderer/App.tsx @@ -3,15 +3,15 @@ * @description Electron前端渲染进程根组件 * * 根据 viewStore.currentView 决定渲染哪个视图: - * - 'settings' → 设置页面(SettingsPage) - * - 'chat' → 主界面(WebSocketProvider + AppContent) + * - 'settings' → 设置页面(SettingsPage) + * - 'agent-editor' → Agent 编辑页面(AgentEditor) + * - 'chat' → 主界面(WebSocketProvider + AppContent) * * AppContent 是主界面的核心,包含: * - AgentSidebar:左侧 Agent 列表 * - SessionSidebar:会话列表 * - MessageList:消息展示 * - InputBox:输入框 - * - AgentEditor:Agent 编辑弹窗 */ import { useEffect, useRef, useState } from 'react'; import { Header } from './components/Header'; @@ -39,8 +39,6 @@ import { logger } from './lib/logger'; function AppContent() { /* 状态定义 */ - const [agentEditorOpen, setAgentEditorOpen] = useState(false); // Agent编辑弹窗是否打开 TODO: 如果要修改Agent编辑页面, 这个地方要改 - const [editingAgentId, setEditingAgentId] = useState(null); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const pendingProviderRef = useRef(); const messageListRef = useRef(null); @@ -68,22 +66,7 @@ function AppContent() { const { providers, loadProviders } = useProviderStore(); const companionVisible = useCompanionStore((s) => s.visible); const currentView = useViewStore((s) => s.currentView); - - /* 定义Agent弹窗的操作 */ - /** - * 打开 Agent 编辑弹窗,传入 null 表示新建,传入 id 表示编辑已有 Agent - * @param agentId - 要编辑的 Agent ID,null 时为新建模式 - */ - const handleOpenAgentEditor = (agentId: string | null) => { - setEditingAgentId(agentId); - setAgentEditorOpen(true); - }; - - /** 关闭 Agent 编辑弹窗并清空编辑状态 */ - const handleCloseAgentEditor = () => { - setAgentEditorOpen(false); - setEditingAgentId(null); - }; + const editingAgentId = useViewStore((s) => s.editingAgentId); /** * 删除指定 Agent @@ -224,14 +207,18 @@ function AppContent() { return (
- + {currentView === 'settings' ? (
+ ) : currentView === 'agent-editor' ? ( +
+ +
) : ( <> )} -
); diff --git a/packages/desktop/src/renderer/components/AgentEditor.tsx b/packages/desktop/src/renderer/components/AgentEditor.tsx index db2b133..e732cf0 100644 --- a/packages/desktop/src/renderer/components/AgentEditor.tsx +++ b/packages/desktop/src/renderer/components/AgentEditor.tsx @@ -1,21 +1,11 @@ /** * @file src/renderer/components/AgentEditor.tsx - * @description Agent 编辑面板,支持创建和编辑 Agent,包括基本信息、模型配置、工作空间、MCP、Skills 分配和语音配置 + * @description Agent 编辑全页面组件,支持创建和编辑 Agent,包括基本信息、模型配置、工作空间、MCP、Skills 分配和语音配置 */ import React, { useState, useEffect } from 'react'; -import { - X, - User, - Settings, - Zap, - Plus, - Wrench, - Trash2, - FolderOpen, - Upload, - Volume2, -} from 'lucide-react'; +import { ArrowLeft, Plus, Trash2, Upload, Volume2, X } from 'lucide-react'; import { useAgentStore } from '../stores/agentStore'; +import { useViewStore } from '../stores/viewStore'; import { listMcpServers, listSkills, @@ -28,6 +18,7 @@ import { import { ModelSelector } from './ModelSelector'; import { WorkspaceSelector } from './WorkspaceSelector'; import { AgentAvatar } from './AgentAvatar'; +import { SettingRow, SettingDivider } from './SettingRow'; import type { CreateAgentInput, UpdateAgentInput, Agent } from '../types/agent'; import { logger } from '../lib/logger'; import { PRESET_VOICES, synthesize } from '../lib/tts'; @@ -43,24 +34,17 @@ const PREVIEW_TEXTS = [ ]; interface AgentEditorProps { - isOpen: boolean; editingAgentId: string | null; - onClose: () => void; onDelete?: (id: string) => void; } /** - * Agent 编辑面板组件,以右侧抽屉形式展示,支持创建新 Agent 或编辑已有 Agent - * @param props.isOpen - 面板是否打开 + * Agent 编辑全页面组件,以独立页面形式展示(与 SettingsPage 同级),支持创建新 Agent 或编辑已有 Agent * @param props.editingAgentId - 正在编辑的 Agent ID,为 null 时进入新建模式 - * //TODO: 这个面板以后要换成单独的界面 - * @param props.onClose - 关闭面板回调 * @param props.onDelete - 删除 Agent 回调 */ export const AgentEditor: React.FC = ({ - isOpen, editingAgentId, - onClose, onDelete, }) => { const { @@ -70,6 +54,7 @@ export const AgentEditor: React.FC = ({ setAvatarPreview, removeAvatarPreview, } = useAgentStore(); + const closeAgentEditor = useViewStore((s) => s.closeAgentEditor); const editingAgent = editingAgentId ? agents.find((a) => a.id === editingAgentId) : null; @@ -99,10 +84,8 @@ export const AgentEditor: React.FC = ({ const [isLoading, setIsLoading] = useState(false); useEffect(() => { - if (isOpen) { - loadOptions(); - } - }, [isOpen]); + loadOptions(); + }, []); useEffect(() => { if (editingAgent) { @@ -285,7 +268,7 @@ export const AgentEditor: React.FC = ({ } } - onClose(); + closeAgentEditor(); } catch (error) { logger.error('Failed to save agent:', error); } finally { @@ -293,11 +276,11 @@ export const AgentEditor: React.FC = ({ } }; - /** 删除当前编辑的 Agent 并关闭面板 */ + /** 删除当前编辑的 Agent 并返回聊天页面 */ const handleDelete = () => { if (editingAgentId && onDelete) { onDelete(editingAgentId); - onClose(); + closeAgentEditor(); } }; @@ -314,129 +297,75 @@ export const AgentEditor: React.FC = ({ voiceId: voiceId || undefined, }; - if (!isOpen) return null; - return ( - <> -
- -
-
-

- {editingAgentId ? '编辑 Agent' : '添加 Agent'} -

- -
+
+
+
+
+ +

+ {editingAgentId ? '编辑 Agent' : '添加 Agent'} +

+
-
-
-
-
- +
+ {/* 基本信息 */} +
+

基本信息 -

- -
- -
- -
- -
-
-
- -
- + + setName(e.target.value)} placeholder="给 Agent 起个名字" - className="w-full px-3 py-2 border border-gray-200 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + className="rounded-lg border border-[#e0e0e0] h-8 w-64 px-3 text-[13px] focus:outline-none focus:ring-2 focus:ring-blue-500" /> -
- -
- + + + setDescription(e.target.value)} - placeholder="一句话描述这个 Agent 的职责" - className="w-full px-3 py-2 border border-gray-200 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="一句话描述" + className="rounded-lg border border-[#e0e0e0] h-8 w-64 px-3 text-[13px] focus:outline-none focus:ring-2 focus:ring-blue-500" /> -
-
- -
-
- - 语音配置 -
- -
- - -

- 选择后,Agent 回复时将使用该音色朗读 -

-
- - {voiceId && ( - - )} + + + +
+ + +
+
-
-
- + {/* 模型配置 */} +
+

模型配置 -

- -
- + + = ({ onProviderChange={handleProviderChange} showOnlyVerified={true} /> -
- -
-