markpdfdown 0.1.8-beta.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/index.js +398 -14
- package/dist/preload/index.js +20 -1
- package/dist/renderer/assets/{index-yTU2skrN.css → index-B_JfRqwM.css} +54 -0
- package/dist/renderer/assets/{index-iEK8qT5k.js → index-wHB9i2kW.js} +623 -320
- package/dist/renderer/index.html +2 -2
- package/package.json +10 -3
- package/dist/app/app.js +0 -49
- package/dist/app/controllers/completionController.js +0 -19
- package/dist/app/controllers/modelController.js +0 -53
- package/dist/app/controllers/providerController.js +0 -120
- package/dist/app/dal/modelDal.js +0 -44
- package/dist/app/dal/providerDal.js +0 -78
- package/dist/app/db/index.js +0 -56
- package/dist/app/db/migration.js +0 -157
- package/dist/app/logic/llm/AnthropicClient.js +0 -219
- package/dist/app/logic/llm/AzureOpenAIClient.js +0 -239
- package/dist/app/logic/llm/GeminiClient.js +0 -212
- package/dist/app/logic/llm/LLMClient.js +0 -80
- package/dist/app/logic/llm/OpenAIClient.js +0 -235
- package/dist/app/logic/llm/example-advanced.js +0 -232
- package/dist/app/logic/llm/index.js +0 -14
- package/dist/app/logic/model.js +0 -27
- package/dist/app/middleware/logger.js +0 -23
- package/dist/app/routes/routes.js +0 -16
- package/dist/app/types/Provider.js +0 -1
- package/dist/server/controllers/FileController.js +0 -64
- package/dist/server/controllers/TaskController.js +0 -57
- package/dist/server/controllers/completionController.js +0 -64
- package/dist/server/controllers/modelController.js +0 -74
- package/dist/server/controllers/providerController.js +0 -120
- package/dist/server/dal/TaskDal.js +0 -67
- package/dist/server/dal/modelDal.js +0 -44
- package/dist/server/dal/providerDal.js +0 -83
- package/dist/server/db/index.js +0 -57
- package/dist/server/db/migration.js +0 -157
- package/dist/server/index.js +0 -49
- package/dist/server/logic/File.js +0 -34
- package/dist/server/logic/Task.js +0 -21
- package/dist/server/logic/llm/AnthropicClient.js +0 -220
- package/dist/server/logic/llm/AzureOpenAIClient.js +0 -239
- package/dist/server/logic/llm/GeminiClient.js +0 -213
- package/dist/server/logic/llm/LLMClient.js +0 -83
- package/dist/server/logic/llm/OllamaClient.js +0 -220
- package/dist/server/logic/llm/OpenAIClient.js +0 -235
- package/dist/server/logic/llm/example-advanced.js +0 -231
- package/dist/server/logic/llm/index.js +0 -15
- package/dist/server/logic/model.js +0 -59
- package/dist/server/middleware/logger.js +0 -23
- package/dist/server/routes/routes.js +0 -30
- package/dist/server/types/Provider.js +0 -1
- package/dist/server/types/Task.js +0 -1
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import isDev from 'electron-is-dev';
|
|
4
|
-
import { app } from 'electron';
|
|
5
|
-
// 获取迁移文件目录
|
|
6
|
-
const getMigrationsDir = () => {
|
|
7
|
-
// 在开发环境使用项目目录
|
|
8
|
-
if (isDev) {
|
|
9
|
-
return path.join(process.cwd(), 'src', 'server', 'db', 'migrations');
|
|
10
|
-
}
|
|
11
|
-
// 在打包环境中,确保使用正确的路径
|
|
12
|
-
// 使用app.getAppPath()获取应用根目录
|
|
13
|
-
if (app) {
|
|
14
|
-
return path.join(app.getAppPath(), '..', 'migrations');
|
|
15
|
-
}
|
|
16
|
-
// 回退到__dirname相对路径
|
|
17
|
-
return path.join(__dirname, 'migrations');
|
|
18
|
-
};
|
|
19
|
-
// 创建 _prisma_migrations 表的SQL
|
|
20
|
-
const createMigrationsTableSQL = `
|
|
21
|
-
CREATE TABLE IF NOT EXISTS _prisma_migrations (
|
|
22
|
-
id VARCHAR(36) PRIMARY KEY NOT NULL,
|
|
23
|
-
checksum VARCHAR(64) NOT NULL,
|
|
24
|
-
finished_at DATETIME,
|
|
25
|
-
migration_name VARCHAR(255) NOT NULL,
|
|
26
|
-
logs TEXT,
|
|
27
|
-
rolled_back_at DATETIME,
|
|
28
|
-
started_at DATETIME NOT NULL DEFAULT current_timestamp,
|
|
29
|
-
applied_steps_count INTEGER UNSIGNED NOT NULL DEFAULT 0
|
|
30
|
-
);
|
|
31
|
-
`;
|
|
32
|
-
// 检查迁移是否已应用
|
|
33
|
-
const isMigrationApplied = async (prisma, migrationName) => {
|
|
34
|
-
try {
|
|
35
|
-
// 检查 _prisma_migrations 表是否存在
|
|
36
|
-
const tableExists = await prisma.$queryRaw `
|
|
37
|
-
SELECT name FROM sqlite_master
|
|
38
|
-
WHERE type='table' AND name='_prisma_migrations';
|
|
39
|
-
`;
|
|
40
|
-
if (!tableExists || tableExists.length === 0) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
// 检查特定迁移是否已应用
|
|
44
|
-
const migration = await prisma.$queryRaw `
|
|
45
|
-
SELECT * FROM _prisma_migrations
|
|
46
|
-
WHERE migration_name = ${migrationName}
|
|
47
|
-
AND finished_at IS NOT NULL;
|
|
48
|
-
`;
|
|
49
|
-
return migration && migration.length > 0;
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
console.error('Error checking migration status:', error);
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
// 记录已应用的迁移
|
|
57
|
-
const recordMigration = async (prisma, migrationName, checksum) => {
|
|
58
|
-
const id = generateUUID();
|
|
59
|
-
const now = new Date().toISOString();
|
|
60
|
-
try {
|
|
61
|
-
await prisma.$executeRaw `
|
|
62
|
-
INSERT INTO _prisma_migrations (
|
|
63
|
-
id, checksum, migration_name, started_at, finished_at, applied_steps_count
|
|
64
|
-
) VALUES (
|
|
65
|
-
${id}, ${checksum}, ${migrationName}, ${now}, ${now}, 1
|
|
66
|
-
);
|
|
67
|
-
`;
|
|
68
|
-
console.log(`Recorded migration: ${migrationName}`);
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
console.error(`Failed to record migration ${migrationName}:`, error);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
// 生成UUID
|
|
75
|
-
const generateUUID = () => {
|
|
76
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
77
|
-
const r = Math.random() * 16 | 0;
|
|
78
|
-
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
79
|
-
return v.toString(16);
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
// 计算迁移文件的checksum
|
|
83
|
-
const calculateChecksum = async (content) => {
|
|
84
|
-
// 使用ESM方式导入crypto
|
|
85
|
-
const crypto = await import('crypto');
|
|
86
|
-
return crypto.default.createHash('sha256').update(content).digest('hex');
|
|
87
|
-
};
|
|
88
|
-
// 应用单个迁移
|
|
89
|
-
const applyMigration = async (prisma, migrationDir, migrationName) => {
|
|
90
|
-
const sqlFilePath = path.join(migrationDir, migrationName, 'migration.sql');
|
|
91
|
-
try {
|
|
92
|
-
if (!fs.existsSync(sqlFilePath)) {
|
|
93
|
-
console.error(`Migration SQL file not found: ${sqlFilePath}`);
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
const sqlContent = fs.readFileSync(sqlFilePath, 'utf8');
|
|
97
|
-
const sqlStatements = sqlContent.split(';').filter(stmt => stmt.trim());
|
|
98
|
-
// 应用每条SQL语句
|
|
99
|
-
for (const statement of sqlStatements) {
|
|
100
|
-
if (statement.trim()) {
|
|
101
|
-
await prisma.$executeRawUnsafe(statement.trim());
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// 记录迁移
|
|
105
|
-
const checksum = await calculateChecksum(sqlContent);
|
|
106
|
-
await recordMigration(prisma, migrationName, checksum);
|
|
107
|
-
console.log(`Successfully applied migration: ${migrationName}`);
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
console.error(`Failed to apply migration ${migrationName}:`, error);
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
// 主要的迁移函数
|
|
116
|
-
const runMigrations = async (prisma = null) => {
|
|
117
|
-
console.log('Running database migrations...');
|
|
118
|
-
try {
|
|
119
|
-
// 确保_prisma_migrations表存在
|
|
120
|
-
await prisma.$executeRawUnsafe(createMigrationsTableSQL);
|
|
121
|
-
const migrationsDir = getMigrationsDir();
|
|
122
|
-
console.log('Migrations directory:', migrationsDir);
|
|
123
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
124
|
-
console.error(`Migrations directory not found: ${migrationsDir}`);
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
// 读取所有迁移目录并按名称排序
|
|
128
|
-
const migrationDirs = fs
|
|
129
|
-
.readdirSync(migrationsDir)
|
|
130
|
-
.filter(dir => {
|
|
131
|
-
const stat = fs.statSync(path.join(migrationsDir, dir));
|
|
132
|
-
return stat.isDirectory() && dir !== 'migration_lock.toml';
|
|
133
|
-
})
|
|
134
|
-
.sort(); // 按名称排序,确保按正确顺序应用
|
|
135
|
-
let migrationsApplied = 0;
|
|
136
|
-
// 应用每个迁移
|
|
137
|
-
for (const migrationName of migrationDirs) {
|
|
138
|
-
const isApplied = await isMigrationApplied(prisma, migrationName);
|
|
139
|
-
if (!isApplied) {
|
|
140
|
-
console.log(`Applying migration: ${migrationName}`);
|
|
141
|
-
const success = await applyMigration(prisma, migrationsDir, migrationName);
|
|
142
|
-
if (success)
|
|
143
|
-
migrationsApplied++;
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
console.log(`Migration already applied: ${migrationName}`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
console.log(`Database migration complete. Applied ${migrationsApplied} migrations.`);
|
|
150
|
-
return migrationsApplied > 0;
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
console.error('Migration error:', error);
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
export { runMigrations };
|
package/dist/server/index.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import cors from 'cors';
|
|
3
|
-
import bodyParser from 'body-parser';
|
|
4
|
-
import { requestLogger, errorLogger } from './middleware/Logger.js';
|
|
5
|
-
import { initDatabase, disconnect } from './db/index.js';
|
|
6
|
-
// 初始化Express应用
|
|
7
|
-
const app = express();
|
|
8
|
-
// 中间件配置
|
|
9
|
-
app.use(cors());
|
|
10
|
-
app.use(bodyParser.json());
|
|
11
|
-
app.use(bodyParser.urlencoded({ extended: true }));
|
|
12
|
-
app.use(requestLogger);
|
|
13
|
-
// 导入路由
|
|
14
|
-
import routes from './routes/Routes.js';
|
|
15
|
-
// 使用路由
|
|
16
|
-
app.use('/api', routes);
|
|
17
|
-
// 健康检查
|
|
18
|
-
app.get('/health', (_req, res) => {
|
|
19
|
-
res.status(200).json({ status: 'ok' });
|
|
20
|
-
});
|
|
21
|
-
// 错误日志中间件
|
|
22
|
-
app.use(errorLogger);
|
|
23
|
-
// 错误处理中间件
|
|
24
|
-
app.use((err, _req, res, _next) => {
|
|
25
|
-
res.status(500).json({
|
|
26
|
-
message: 'Internal Server Error',
|
|
27
|
-
error: process.env.NODE_ENV === 'development' ? err.message : undefined
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
// 优雅关闭
|
|
31
|
-
process.on('SIGINT', async () => {
|
|
32
|
-
await disconnect();
|
|
33
|
-
console.log('Backend server has been shut down');
|
|
34
|
-
process.exit(0);
|
|
35
|
-
});
|
|
36
|
-
let server = null;
|
|
37
|
-
export { app };
|
|
38
|
-
export const start = async () => {
|
|
39
|
-
// 确保不会重复启动服务器
|
|
40
|
-
if (server) {
|
|
41
|
-
return server;
|
|
42
|
-
}
|
|
43
|
-
// 在启动服务器前初始化数据库
|
|
44
|
-
await initDatabase();
|
|
45
|
-
// 启动服务器并监听随机端口
|
|
46
|
-
server = app.listen(0, 'localhost');
|
|
47
|
-
return server;
|
|
48
|
-
};
|
|
49
|
-
export const getServer = () => server;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { app } from "electron";
|
|
2
|
-
import isDev from "electron-is-dev";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import fs from "fs";
|
|
5
|
-
// 获取上传目录
|
|
6
|
-
const getUploadDir = () => {
|
|
7
|
-
// DEV目录
|
|
8
|
-
if (isDev) {
|
|
9
|
-
return path.join(process.cwd(), 'files');
|
|
10
|
-
}
|
|
11
|
-
// 打包目录
|
|
12
|
-
const userDataPath = app.getPath('userData');
|
|
13
|
-
return path.join(userDataPath, 'files');
|
|
14
|
-
};
|
|
15
|
-
// 删除任务对应的文件
|
|
16
|
-
const deleteTaskFiles = (id) => {
|
|
17
|
-
const baseUploadDir = getUploadDir();
|
|
18
|
-
const uploadDir = path.join(baseUploadDir, id);
|
|
19
|
-
// 检查路径是否存在
|
|
20
|
-
if (fs.existsSync(uploadDir)) {
|
|
21
|
-
// 如果是目录,使用递归删除
|
|
22
|
-
if (fs.lstatSync(uploadDir).isDirectory()) {
|
|
23
|
-
fs.rmSync(uploadDir, { recursive: true, force: true });
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
// 如果是文件,使用unlinkSync
|
|
27
|
-
fs.unlinkSync(uploadDir);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
export default {
|
|
32
|
-
getUploadDir,
|
|
33
|
-
deleteTaskFiles,
|
|
34
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
class TaskLogic {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.isRunning = false;
|
|
4
|
-
}
|
|
5
|
-
async start() {
|
|
6
|
-
this.isRunning = true;
|
|
7
|
-
while (this.isRunning) {
|
|
8
|
-
console.log('Task is running');
|
|
9
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
async stop() {
|
|
13
|
-
this.isRunning = false;
|
|
14
|
-
console.log('Task stopped');
|
|
15
|
-
}
|
|
16
|
-
// 获取状态
|
|
17
|
-
getStatus() {
|
|
18
|
-
return this.isRunning;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export default new TaskLogic();
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { LLMClient } from './LLMClient.js';
|
|
2
|
-
/**
|
|
3
|
-
* Anthropic客户端实现
|
|
4
|
-
*/
|
|
5
|
-
export class AnthropicClient extends LLMClient {
|
|
6
|
-
constructor(apiKey, baseUrl, apiVersion = '2023-06-01') {
|
|
7
|
-
super(apiKey, baseUrl || 'https://api.anthropic.com/v1/messages');
|
|
8
|
-
this.apiVersion = apiVersion;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* 执行Anthropic文本补全
|
|
12
|
-
*/
|
|
13
|
-
async completion(options) {
|
|
14
|
-
try {
|
|
15
|
-
// 标准化选项,处理向后兼容
|
|
16
|
-
const normalizedOptions = this.normalizeOptions(options);
|
|
17
|
-
const endpoint = `${this.baseUrl}`;
|
|
18
|
-
const modelName = normalizedOptions.model || 'claude-3-opus-20240229';
|
|
19
|
-
// 将消息转换为Anthropic格式
|
|
20
|
-
const anthropicMessages = this.convertMessagesToAnthropicFormat(normalizedOptions.messages);
|
|
21
|
-
const requestBody = {
|
|
22
|
-
model: modelName,
|
|
23
|
-
messages: anthropicMessages,
|
|
24
|
-
temperature: normalizedOptions.temperature ?? 0.7,
|
|
25
|
-
max_tokens: normalizedOptions.maxTokens,
|
|
26
|
-
stream: normalizedOptions.stream || false
|
|
27
|
-
};
|
|
28
|
-
// Anthropic支持System指令,而不是作为一个普通消息
|
|
29
|
-
const systemMessage = normalizedOptions.messages.find(msg => msg.role === 'system');
|
|
30
|
-
if (systemMessage) {
|
|
31
|
-
const systemContent = Array.isArray(systemMessage.content)
|
|
32
|
-
? systemMessage.content.filter(c => c.type === 'text').map(c => c.text).join('\n')
|
|
33
|
-
: systemMessage.content.type === 'text' ? systemMessage.content.text : '';
|
|
34
|
-
if (systemContent) {
|
|
35
|
-
requestBody.system = systemContent;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
// Claude没有直接的JSON响应格式,但可以通过系统指令引导
|
|
39
|
-
if (normalizedOptions.response_format?.type === 'json_object' && !requestBody.system) {
|
|
40
|
-
requestBody.system = "请以有效的JSON格式提供响应。";
|
|
41
|
-
}
|
|
42
|
-
else if (normalizedOptions.response_format?.type === 'json_object' && requestBody.system) {
|
|
43
|
-
requestBody.system += "\n\n请以有效的JSON格式提供响应。";
|
|
44
|
-
}
|
|
45
|
-
const response = await fetch(endpoint, {
|
|
46
|
-
method: 'POST',
|
|
47
|
-
headers: {
|
|
48
|
-
'Content-Type': 'application/json',
|
|
49
|
-
'x-api-key': normalizedOptions.apiKey || this.apiKey,
|
|
50
|
-
'anthropic-version': this.apiVersion
|
|
51
|
-
},
|
|
52
|
-
body: JSON.stringify(requestBody)
|
|
53
|
-
});
|
|
54
|
-
console.log(`[${new Date().toISOString()}] POST ${endpoint} ${response.status} - ${response.statusText}`);
|
|
55
|
-
if (!response.ok) {
|
|
56
|
-
const error = await response.json();
|
|
57
|
-
throw new Error(`Anthropic API错误: ${error.error?.message || response.statusText}`);
|
|
58
|
-
}
|
|
59
|
-
if (normalizedOptions.stream && response.body && normalizedOptions.onUpdate) {
|
|
60
|
-
// 处理流式响应
|
|
61
|
-
const reader = response.body.getReader();
|
|
62
|
-
const decoder = new TextDecoder('utf-8');
|
|
63
|
-
let content = '';
|
|
64
|
-
const processStream = async () => {
|
|
65
|
-
const { done, value } = await reader.read();
|
|
66
|
-
if (done) {
|
|
67
|
-
return {
|
|
68
|
-
content,
|
|
69
|
-
model: modelName,
|
|
70
|
-
finishReason: 'stop',
|
|
71
|
-
responseFormat: normalizedOptions.response_format?.type
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
// 解析流式响应数据
|
|
75
|
-
const chunk = decoder.decode(value);
|
|
76
|
-
const lines = chunk
|
|
77
|
-
.split('\n')
|
|
78
|
-
.filter(line => line.trim() !== '' && line.trim() !== 'data: [DONE]');
|
|
79
|
-
for (const line of lines) {
|
|
80
|
-
if (line.startsWith('data: ')) {
|
|
81
|
-
try {
|
|
82
|
-
const data = JSON.parse(line.slice(6));
|
|
83
|
-
// Claude 3使用content_block_delta类型流式传输内容
|
|
84
|
-
if (data.type === 'content_block_delta' && data.delta?.text) {
|
|
85
|
-
content += data.delta.text;
|
|
86
|
-
if (normalizedOptions.onUpdate) {
|
|
87
|
-
normalizedOptions.onUpdate(content);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
// 兼容Claude 2流式响应格式
|
|
91
|
-
if (data.completion) {
|
|
92
|
-
const newContent = data.completion;
|
|
93
|
-
content = newContent; // Claude 2返回完整内容,而不是增量
|
|
94
|
-
if (normalizedOptions.onUpdate) {
|
|
95
|
-
normalizedOptions.onUpdate(content);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (e) {
|
|
100
|
-
// 忽略解析错误
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return processStream();
|
|
105
|
-
};
|
|
106
|
-
return processStream();
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
// 处理普通响应
|
|
110
|
-
const data = await response.json();
|
|
111
|
-
// Claude API返回格式
|
|
112
|
-
let content = '';
|
|
113
|
-
// Claude 3格式
|
|
114
|
-
if (data.content) {
|
|
115
|
-
// 提取所有文本块
|
|
116
|
-
for (const block of data.content) {
|
|
117
|
-
if (block.type === 'text') {
|
|
118
|
-
content += block.text;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// 兼容Claude 2格式
|
|
123
|
-
else if (data.completion) {
|
|
124
|
-
content = data.completion;
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
content,
|
|
128
|
-
model: data.model,
|
|
129
|
-
finishReason: data.stop_reason,
|
|
130
|
-
responseFormat: normalizedOptions.response_format?.type,
|
|
131
|
-
rawResponse: data // 保留原始响应以便调试
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
137
|
-
throw new Error(`Anthropic补全请求失败: ${errorMessage}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 将消息转换为Anthropic API格式
|
|
142
|
-
* Claude API移除系统消息,这将在请求体中单独处理
|
|
143
|
-
*/
|
|
144
|
-
convertMessagesToAnthropicFormat(messages) {
|
|
145
|
-
return messages
|
|
146
|
-
.filter(msg => msg.role !== 'system') // 系统消息会在请求体中单独处理
|
|
147
|
-
.map(message => {
|
|
148
|
-
// Anthropic只支持user和assistant角色
|
|
149
|
-
let role = message.role === 'user' ? 'user' : 'assistant';
|
|
150
|
-
// 将tool角色视为user角色
|
|
151
|
-
if (message.role === 'tool') {
|
|
152
|
-
role = 'user';
|
|
153
|
-
}
|
|
154
|
-
const anthropicMessage = {
|
|
155
|
-
role
|
|
156
|
-
};
|
|
157
|
-
// 处理内容
|
|
158
|
-
if (Array.isArray(message.content)) {
|
|
159
|
-
anthropicMessage.content = this.convertContentArrayToAnthropicFormat(message.content);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
anthropicMessage.content = this.convertContentToAnthropicFormat(message.content);
|
|
163
|
-
}
|
|
164
|
-
return anthropicMessage;
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* 将内容数组转换为Anthropic内容格式
|
|
169
|
-
*/
|
|
170
|
-
convertContentArrayToAnthropicFormat(contentArray) {
|
|
171
|
-
return contentArray.map(content => this.convertContentToAnthropicFormat(content)).flat();
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* 将单个内容转换为Anthropic内容格式
|
|
175
|
-
*/
|
|
176
|
-
convertContentToAnthropicFormat(content) {
|
|
177
|
-
switch (content.type) {
|
|
178
|
-
case 'text':
|
|
179
|
-
return [{
|
|
180
|
-
type: 'text',
|
|
181
|
-
text: content.text
|
|
182
|
-
}];
|
|
183
|
-
case 'image_url':
|
|
184
|
-
const imageContent = content;
|
|
185
|
-
let source;
|
|
186
|
-
if (imageContent.image_url.url.startsWith('data:')) {
|
|
187
|
-
// 处理Base64数据URI
|
|
188
|
-
const parts = imageContent.image_url.url.split(';base64,');
|
|
189
|
-
if (parts.length === 2) {
|
|
190
|
-
const mediaType = parts[0].replace('data:', '');
|
|
191
|
-
source = {
|
|
192
|
-
type: 'base64',
|
|
193
|
-
media_type: mediaType,
|
|
194
|
-
data: parts[1]
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
// 处理URL
|
|
200
|
-
source = {
|
|
201
|
-
type: 'url',
|
|
202
|
-
url: imageContent.image_url.url
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
return [{
|
|
206
|
-
type: 'image',
|
|
207
|
-
source
|
|
208
|
-
}];
|
|
209
|
-
// Anthropic不支持工具调用和工具结果,将其转换为文本
|
|
210
|
-
case 'tool_call':
|
|
211
|
-
case 'tool_result':
|
|
212
|
-
return [{
|
|
213
|
-
type: 'text',
|
|
214
|
-
text: JSON.stringify(content)
|
|
215
|
-
}];
|
|
216
|
-
default:
|
|
217
|
-
throw new Error(`Anthropic不支持的内容类型: ${content.type}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|