feat: MetonaAI Desktop 初始项目
- Electron + React + TypeScript 架构 - 三栏布局: Sidebar | ChatPanel | DetailPanel - 9 个内置工具 (文件系统/网络/记忆/命令) - SQLite 持久化 (better-sqlite3) - MUI 暗色/亮色主题系统 - Agent Loop ReAct 状态机引擎 - DeepSeek / Agnes AI / Ollama Provider 适配器 - MCP 协议集成 - 系统托盘 + 全局快捷键 - Tailwind CSS v4 + Tailwind Merge - 修复: Sidebar 缺失 TextField 导入导致黑屏
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Agnes AI Provider Adapter
|
||||
*
|
||||
* OpenAI 兼容 API,免费使用。
|
||||
* 支持 Tool Calling、Thinking 模式、512K 上下文、多模态(图片 base64)。
|
||||
*
|
||||
* 差异于 DeepSeek:
|
||||
* - Thinking 模式使用 chat_template_kwargs(非 thinking 字段)
|
||||
* - 图片使用 base64 格式(与 Ollama 一致,通过 images 字段传递)
|
||||
* - 512K 上下文,65.5K 最大输出
|
||||
*
|
||||
* @see apis/agnes-ai-api-docs-20260625.html
|
||||
*/
|
||||
|
||||
import { DeepSeekAdapter } from './deepseek.adapter';
|
||||
import type { MetonaRequest } from '../types';
|
||||
|
||||
export class AgnesAdapter extends DeepSeekAdapter {
|
||||
override readonly provider: string = 'agnes';
|
||||
readonly supportedModels = ['agnes-2.0-flash'];
|
||||
readonly supportsToolCalling = true;
|
||||
readonly supportsThinking = true;
|
||||
|
||||
/**
|
||||
* 覆盖原生请求构建
|
||||
*
|
||||
* 差异:
|
||||
* 1. Thinking 模式使用 chat_template_kwargs
|
||||
* 2. 图片使用 OpenAI 多模态 content 数组格式
|
||||
*/
|
||||
protected override toNativeRequest(request: MetonaRequest): Record<string, unknown> {
|
||||
const base = super.toNativeRequest(request) as Record<string, unknown>;
|
||||
|
||||
// Agnes Thinking 模式:使用 chat_template_kwargs
|
||||
if (request.params?.thinkingEnabled) {
|
||||
delete base.thinking;
|
||||
delete base.reasoning_effort;
|
||||
base.chat_template_kwargs = { enable_thinking: true };
|
||||
}
|
||||
|
||||
// Agnes 默认 max_tokens 更大
|
||||
if (!request.params.maxTokens) {
|
||||
base.max_tokens = 65536;
|
||||
}
|
||||
|
||||
// 图片处理:将 images 转为 OpenAI 多模态 content 数组格式
|
||||
const messages = base.messages as Array<Record<string, unknown>>;
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i];
|
||||
// 找到对应的原始消息(跳过 system 消息)
|
||||
const origMsg = request.messages.filter((m) => m.role !== 'system')[i];
|
||||
if (!origMsg?.images || origMsg.images.length === 0) continue;
|
||||
|
||||
// 将 content 转为数组格式:[{type: "text", text: ...}, {type: "image_url", ...}]
|
||||
const contentParts: Array<Record<string, unknown>> = [];
|
||||
if (msg.content && typeof msg.content === 'string') {
|
||||
contentParts.push({ type: 'text', text: msg.content });
|
||||
}
|
||||
for (const img of origMsg.images) {
|
||||
contentParts.push({
|
||||
type: 'image_url',
|
||||
image_url: { url: img.url, detail: img.detail ?? 'auto' },
|
||||
});
|
||||
}
|
||||
msg.content = contentParts;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user