Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions components/Main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,27 @@
</el-col>
</el-row>

<!-- 自定义请求体 -->
<el-row v-show="compute.showModel" class="margin-bottom margin-left-2em">
<el-col :span="12" class="lightblue rounded-corner">
<el-tooltip class="box-item" effect="dark"
content='以 JSON 对象形式追加到请求体(顶层合并,会覆盖同名默认字段)。'
placement="top-start" :show-after="500">
<span class="popup-text popup-vertical-left">自定义请求体<el-icon class="icon-margin">
<ChatDotRound />
</el-icon></span>
</el-tooltip>
</el-col>
<el-col :span="12">
<el-input v-model="config.customBody[config.service]"
:class="{ 'input-error': !isValidCustomBody(config.customBody[config.service]) }"
placeholder='例如:{"thinking": {"type": "disabled"}}' />
<div v-if="!isValidCustomBody(config.customBody[config.service])" class="error-text">
请输入合法的 JSON 对象,否则该配置将被忽略
</div>
</el-col>
</el-row>

<!-- 高级选项-->
<el-collapse class="margin-left-2em margin-bottom">
<el-collapse-item title="高级选项">
Expand Down Expand Up @@ -1079,6 +1100,17 @@ const isValidAzureEndpoint = (endpoint: string) => {
return hasHttps && hasAzureDomain && hasChatCompletions;
};

// 自定义请求体(JSON)校验:留空视为合法(不启用),否则必须是 JSON 对象
const isValidCustomBody = (str: string | undefined): boolean => {
if (!str || !str.trim()) return true;
try {
const v = JSON.parse(str);
return v !== null && typeof v === 'object' && !Array.isArray(v);
} catch {
return false;
}
};

const handleExport = async () => {
const configStr = await storage.getItem('local:config');
if (!configStr) {
Expand Down Expand Up @@ -1117,6 +1149,19 @@ const handleExport = async () => {
}
}

// Clean empty customBody entries
if (cleanedConfig.customBody) {
for (const service in cleanedConfig.customBody) {
const value = cleanedConfig.customBody[service];
if (!value || !String(value).trim()) {
delete cleanedConfig.customBody[service];
}
}
if (Object.keys(cleanedConfig.customBody).length === 0) {
delete cleanedConfig.customBody;
}
}

exportData.value = JSON.stringify(cleanedConfig, null, 2);
showExportBox.value = !showExportBox.value;
showImportBox.value = false;
Expand Down
2 changes: 2 additions & 0 deletions entrypoints/utils/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class Config {
key: string;
model: IMapping;
customModel: IMapping; // 自定义模型名称
customBody: IMapping; // 自定义请求体(JSON 字符串,按服务存储),会合并进请求体
proxy: IMapping; // 代理地址
custom: string; // 本地服务地址
extra: IExtra; // 额外信息(内包信息)
Expand Down Expand Up @@ -70,6 +71,7 @@ export class Config {
this.key = '';
this.model = {};
this.customModel = {};
this.customBody = {};
this.proxy = {};
this.custom = defaultOption.custom;
this.extra = {};
Expand Down
71 changes: 54 additions & 17 deletions entrypoints/utils/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@
import {customModelString, defaultOption, services} from "./option";
import {config} from "@/entrypoints/utils/config";

// 将用户自定义的 JSON 请求体合并进 payload(顶层浅合并,用户字段优先)。
// 主要用于向请求体补充额外参数(例如 {"thinking": {"type": "disabled"}} 这类控制字段)。
export function mergeCustomBody(payload: Record<string, any>, raw?: string | null): Record<string, any> {
if (!raw || !raw.trim()) return payload;
try {
const extra = JSON.parse(raw);
// 仅接受 JSON 对象(排除数组、null 及基本类型)
if (extra && typeof extra === 'object' && !Array.isArray(extra)) {
Object.assign(payload, extra);
} else {
console.warn('[FluentRead] 自定义请求体必须是 JSON 对象,已忽略:', raw);
}
} catch (e) {
console.warn('[FluentRead] 自定义请求体不是合法的 JSON,已忽略:', raw, e);
}
return payload;
}

// 读取当前服务的自定义请求体(JSON 字符串)
function currentCustomBody(): string | undefined {
return config.customBody?.[config.service];
}

// openai 格式的消息模板(通用模板)
export function commonMsgTemplate(origin: string) {
// 检测是否使用自定义模型
Expand All @@ -14,14 +37,16 @@ export function commonMsgTemplate(origin: string) {
let user = (config.user_role[config.service] || defaultOption.user_role)
.replace('{{to}}', config.to).replace('{{origin}}', origin);

return JSON.stringify({
const payload: any = {
'model': model,
"temperature": 1.0,
'messages': [
{'role': 'system', 'content': system},
{'role': 'user', 'content': user},
]
})
};

return JSON.stringify(mergeCustomBody(payload, currentCustomBody()))
}

// deepseek
Expand Down Expand Up @@ -49,19 +74,21 @@ export function deepseekMsgTemplate(origin: string) {
payload.temperature = 0.7;
}

return JSON.stringify(payload);
return JSON.stringify(mergeCustomBody(payload, currentCustomBody()));
}

// gemini
export function geminiMsgTemplate(origin: string) {
let user = (config.user_role[config.service] || defaultOption.user_role)
.replace('{{to}}', config.to).replace('{{origin}}', origin);

return JSON.stringify({
const payload: any = {
"contents": [
{"role": "user", "parts": [{"text": user}]},
]
})
};

return JSON.stringify(mergeCustomBody(payload, currentCustomBody()))
}

// claude
Expand All @@ -75,15 +102,17 @@ export function claudeMsgTemplate(origin: string) {
let user = (config.user_role[config.service] || defaultOption.user_role)
.replace('{{to}}', config.to).replace('{{origin}}', origin);

return JSON.stringify({
const payload: any = {
model: model,
max_tokens: 4096,
stream: false,
system: system,
messages: [
{role: "user", content: user},
]
})
};

return JSON.stringify(mergeCustomBody(payload, currentCustomBody()))
}

// 通义千问
Expand All @@ -94,14 +123,15 @@ export function tongyiMsgTemplate(origin: string) {
let user = (config.user_role[config.service] || defaultOption.user_role)
.replace('{{to}}', config.to).replace('{{origin}}', origin);

return JSON.stringify({
const payload: any = {
"model": model,
"enable_thinking": false,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
]
})
};
return JSON.stringify(mergeCustomBody(payload, currentCustomBody()))
}
// 翻译模型qwen-mt-plus和qwen-mt-turbo的格式和通用的不同
const mtModelTemplate = () => {
Expand All @@ -115,7 +145,7 @@ export function tongyiMsgTemplate(origin: string) {
]
let targetItem = langMap.find(i => i.value === config.to) || langMap[0]
let targetLang = targetItem.target || targetItem.value
return JSON.stringify({
const payload: any = {
"model": model,
"messages": [
{"role": "user", "content": origin},
Expand All @@ -124,7 +154,8 @@ export function tongyiMsgTemplate(origin: string) {
"source_lang": "auto",
"target_lang": targetLang
}
})
};
return JSON.stringify(mergeCustomBody(payload, currentCustomBody()))
}
return model.startsWith("qwen-mt") ? mtModelTemplate() : normalTemplate()

Expand All @@ -135,13 +166,15 @@ export function yiyanMsgTemplate(origin: string) {
let user = (config.user_role[config.service] || defaultOption.user_role)
.replace('{{to}}', config.to).replace('{{origin}}', origin);

return JSON.stringify({
const payload: any = {
'temperature': 0.7,
'disable_search': true, // 禁用搜索
'messages': [
{"role": "user", "content": user},
],
})
};

return JSON.stringify(mergeCustomBody(payload, currentCustomBody()))
}

export function minimaxTemplate(origin: string) {
Expand All @@ -150,15 +183,17 @@ export function minimaxTemplate(origin: string) {
let user = (config.user_role[config.service] || defaultOption.user_role)
.replace('{{to}}', config.to).replace('{{origin}}', origin);

return JSON.stringify({
const payload: any = {
model: "MiniMax-Text-01",
stream: false,
temperature: 0.7,
messages: [
{role: 'system', content: system},
{role: 'user', content: user},
]
})
};

return JSON.stringify(mergeCustomBody(payload, currentCustomBody()))
}

export function cozeTemplate(origin: string) {
Expand All @@ -167,10 +202,12 @@ export function cozeTemplate(origin: string) {
let user = (config.user_role[config.service] || defaultOption.user_role)
.replace('{{to}}', config.to).replace('{{origin}}', origin);

return JSON.stringify({
const payload: any = {
bot_id: config.robot_id[config.service],
user: "FluentRead",
query: system + user,
stream: false
});
};

return JSON.stringify(mergeCustomBody(payload, currentCustomBody()));
}
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"zip": "wxt zip",
"zip:firefox": "wxt zip -b firefox",
"compile": "vue-tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"postinstall": "wxt prepare",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
Expand All @@ -37,10 +39,16 @@
"vite": "^5.4.11",
"vite-plugin-imagemin": "^0.6.1",
"vitepress": "^1.6.3",
"vitest": "^2.1.9",
"vue": "^3.5.13",
"vue-tsc": "^2.2.0",
"vuepress": "2.0.0-rc.19",
"wxt": "^0.20.18"
},
"pnpm": {
"overrides": {
"vite": "5.4.11"
}
},
"packageManager": "pnpm@9.12.1+sha1.7084e7df42ee1d221994c2c2599277a2a937f050"
}
Loading