mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 11:37:20 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f6b0f9484 | ||
|
|
e401303f64 | ||
|
|
799dbda7e0 | ||
|
|
f3cbf1e6c3 | ||
|
|
2380a0d692 | ||
|
|
1da1faa4d4 | ||
|
|
ce05b0bf7a | ||
|
|
44a47992d9 | ||
|
|
319bd3d7d8 | ||
|
|
12c7973a06 | ||
|
|
f93b18740d | ||
|
|
c6ee26b12d | ||
|
|
556ea21f38 | ||
|
|
315aba49c2 | ||
|
|
50ab63de3d | ||
|
|
802daa8fcd |
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: i18n-requirements
|
||||
description: Use when adding or changing frontend UI text, locale support, translation JSON, the static i18n scanner, language selection, or native tray labels in this repository; keeps source messages, generated catalogs, translations, and runtime locale registration consistent.
|
||||
---
|
||||
|
||||
# I18n Requirements
|
||||
|
||||
## Source Messages
|
||||
|
||||
- Treat `zh-CN` as the only source locale.
|
||||
- Write user-visible frontend text as Chinese source literals in scanned files under `frontend/src/`.
|
||||
- Do not branch on locale or hard-code English, Japanese, Russian, or other translated UI text in components or state modules.
|
||||
- Let `frontend/plugins/static-i18n-plugin.js` replace source literals with runtime helpers. Do not hand-write generated message IDs in application code.
|
||||
- Keep internal matching tokens out of the catalog. Use a regex for Chinese protocol/error matching instead of a user-visible string literal when the text is not intended for display.
|
||||
- Do not place ordinary user-visible source messages under `frontend/src/i18n/`; the scanner excludes that directory. Native language names in `LOCALE_OPTIONS` are an intentional exception.
|
||||
|
||||
## Generated Catalogs
|
||||
|
||||
- Treat `frontend/src/i18n/generated/catalog.json` and the source-locale entries as scanner output. Do not manually edit catalog references or message IDs.
|
||||
- Run `npm run build` from `frontend/` after changing UI text. The build must run with `--scan` and update every locale file.
|
||||
- Preserve every placeholder exactly across locales, including `{0}`, `{1}`, newlines, and formula fragments such as `${1}`.
|
||||
- Provide a non-empty translation for every catalog key in every non-source locale. Do not rely on the Chinese fallback for completed locale support.
|
||||
|
||||
## Adding A Locale
|
||||
|
||||
Update all of these integration points together:
|
||||
|
||||
- `SUPPORTED_LOCALES` in `frontend/plugins/static-i18n-plugin.js`.
|
||||
- `SUPPORTED_LOCALES` and `LOCALE_OPTIONS` in `frontend/src/i18n/config.js`.
|
||||
- The locale JSON import, `localeMessages`, and primary-language mapping in `frontend/src/i18n/runtime.js`.
|
||||
- `frontend/src/i18n/locales/<locale>.json` with the complete catalog key set.
|
||||
- Native tray labels in `internal/app/runner.go`.
|
||||
|
||||
## Verification
|
||||
|
||||
After the scan build:
|
||||
|
||||
1. Confirm `npm run build` succeeds.
|
||||
2. Confirm every locale JSON has the same keys as `catalog.json`.
|
||||
3. Confirm non-source locale files contain no empty values.
|
||||
4. Confirm translated placeholders match the source entry placeholders.
|
||||
5. Run the build twice when scanner behavior changed and confirm generated files are stable.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "I18n Requirements"
|
||||
short_description: "Keep UI translations and generated catalogs in sync"
|
||||
default_prompt: "Use $i18n-requirements to update localized UI text safely."
|
||||
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User') + ';$HOME\go\bin'; task build"
|
||||
pause
|
||||
@@ -0,0 +1,2 @@
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + ";$HOME\go\bin"
|
||||
task build
|
||||
+1
-1
@@ -8,7 +8,7 @@ info:
|
||||
description: "Cursor助手"
|
||||
copyright: "© 2026, Cursor助手"
|
||||
comments: "Cursor助手"
|
||||
version: "0.0.40"
|
||||
version: "0.0.41"
|
||||
|
||||
dev_mode:
|
||||
root_path: .
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.39</string>
|
||||
<string>0.0.41</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.39</string>
|
||||
<string>0.0.41</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0.0</string>
|
||||
<key>LSUIElement</key>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.39</string>
|
||||
<string>0.0.41</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.39</string>
|
||||
<string>0.0.41</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0.0</string>
|
||||
<key>LSUIElement</key>
|
||||
|
||||
+15
-28
@@ -6,7 +6,7 @@
|
||||
name: "Cursor助手"
|
||||
arch: ${GOARCH}
|
||||
platform: "linux"
|
||||
version: "0.0.39"
|
||||
version: "0.0.41"
|
||||
section: "default"
|
||||
priority: "extra"
|
||||
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
||||
@@ -24,24 +24,24 @@ contents:
|
||||
- src: "./build/linux/Cursor助手.desktop"
|
||||
dst: "/usr/share/applications/Cursor助手.desktop"
|
||||
|
||||
# Default dependencies for the GTK4 + WebKitGTK 6.0 stack (Ubuntu 24.04+ / Debian 13+)
|
||||
# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1
|
||||
depends:
|
||||
- libgtk-4-1
|
||||
- libwebkitgtk-6.0-4
|
||||
- libgtk-3-0
|
||||
- libwebkit2gtk-4.1-0
|
||||
|
||||
# Distribution-specific overrides for different package formats
|
||||
# Distribution-specific overrides for different package formats and WebKit versions
|
||||
overrides:
|
||||
# RPM packages for Fedora / RHEL / AlmaLinux / Rocky Linux
|
||||
# RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0)
|
||||
rpm:
|
||||
depends:
|
||||
- gtk4
|
||||
- webkitgtk6.0
|
||||
|
||||
# Arch Linux packages
|
||||
- gtk3
|
||||
- webkit2gtk4.1
|
||||
|
||||
# Arch Linux packages (WebKit 4.1)
|
||||
archlinux:
|
||||
depends:
|
||||
- gtk4
|
||||
- webkitgtk-6.0
|
||||
- gtk3
|
||||
- webkit2gtk-4.1
|
||||
|
||||
# scripts section to ensure desktop database is updated after install
|
||||
scripts:
|
||||
@@ -50,26 +50,13 @@ scripts:
|
||||
# preremove: "./build/linux/nfpm/scripts/preremove.sh"
|
||||
# postremove: "./build/linux/nfpm/scripts/postremove.sh"
|
||||
|
||||
# If you build your app with -tags gtk3 (legacy WebKit2GTK 4.1 stack — supported through v3.0.x, removed in v3.1),
|
||||
# replace the depends/overrides above with these:
|
||||
#
|
||||
# depends:
|
||||
# - libgtk-3-0
|
||||
# - libwebkit2gtk-4.1-0
|
||||
# overrides:
|
||||
# rpm:
|
||||
# depends:
|
||||
# - gtk3
|
||||
# - webkit2gtk4.1
|
||||
# archlinux:
|
||||
# depends:
|
||||
# - gtk3
|
||||
# - webkit2gtk-4.1
|
||||
#
|
||||
# replaces:
|
||||
# - foobar
|
||||
# provides:
|
||||
# - bar
|
||||
# depends:
|
||||
# - gtk3
|
||||
# - libwebkit2gtk
|
||||
# recommends:
|
||||
# - whatever
|
||||
# suggests:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fixed": {
|
||||
"file_version": "0.0.39"
|
||||
"file_version": "0.0.41"
|
||||
},
|
||||
"info": {
|
||||
"0000": {
|
||||
"ProductVersion": "0.0.39",
|
||||
"ProductVersion": "0.0.41",
|
||||
"CompanyName": "Cursor助手",
|
||||
"FileDescription": "Cursor助手",
|
||||
"LegalCopyright": "© 2026, Cursor助手",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
!define INFO_PRODUCTNAME "Cursor助手"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTVERSION
|
||||
!define INFO_PRODUCTVERSION "0.0.39"
|
||||
!define INFO_PRODUCTVERSION "0.0.41"
|
||||
!endif
|
||||
!ifndef INFO_COPYRIGHT
|
||||
!define INFO_COPYRIGHT "© 2026, Cursor助手"
|
||||
@@ -27,16 +27,8 @@
|
||||
!endif
|
||||
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
|
||||
|
||||
!ifndef WAILS_INSTALL_SCOPE
|
||||
!define WAILS_INSTALL_SCOPE "machine"
|
||||
!endif
|
||||
|
||||
!ifndef REQUEST_EXECUTION_LEVEL
|
||||
!if "${WAILS_INSTALL_SCOPE}" == "user"
|
||||
!define REQUEST_EXECUTION_LEVEL "user"
|
||||
!else
|
||||
!define REQUEST_EXECUTION_LEVEL "admin"
|
||||
!endif
|
||||
!define REQUEST_EXECUTION_LEVEL "admin"
|
||||
!endif
|
||||
|
||||
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
||||
@@ -123,40 +115,23 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
SetRegView 64
|
||||
!if "${WAILS_INSTALL_SCOPE}" == "user"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||
!else
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||
!endif
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||
!macroend
|
||||
|
||||
!macro wails.deleteUninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
SetRegView 64
|
||||
!if "${WAILS_INSTALL_SCOPE}" == "user"
|
||||
DeleteRegKey HKCU "${UNINST_KEY}"
|
||||
!else
|
||||
DeleteRegKey HKLM "${UNINST_KEY}"
|
||||
!endif
|
||||
DeleteRegKey HKLM "${UNINST_KEY}"
|
||||
!macroend
|
||||
|
||||
!macro wails.setShellContext
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<assemblyIdentity type="win32" name="com.cursor.wuxianxubei" version="0.0.39" processorArchitecture="*"/>
|
||||
<assemblyIdentity type="win32" name="com.cursor.wuxianxubei" version="0.0.41" processorArchitecture="*"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build:dev": "node ./scripts/run-vite-build.mjs --minify false --mode development",
|
||||
"build": "node ./scripts/run-vite-build.mjs --mode production",
|
||||
"build:dev": "node ./scripts/run-vite-build.mjs --scan --minify false --mode development",
|
||||
"build": "node ./scripts/run-vite-build.mjs --scan --mode production",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { normalizePath } from "vite";
|
||||
const traverse = traverseModule.default ?? traverseModule;
|
||||
|
||||
const SOURCE_LANGUAGE = "zh-CN";
|
||||
const SUPPORTED_LOCALES = ["zh-CN", "en-US", "ja-JP"];
|
||||
const SUPPORTED_LOCALES = ["zh-CN", "en-US", "ja-JP", "ru-RU"];
|
||||
const HAN_REGEX = /\p{Script=Han}/u;
|
||||
const JS_HELPERS = {
|
||||
localized: "__i18nLocalized",
|
||||
@@ -490,22 +490,34 @@ function walkTemplateNode(node, visitor) {
|
||||
}
|
||||
}
|
||||
|
||||
function transformVueTemplate(templateCode, filePath, rootDir) {
|
||||
function resolveAbsoluteLoc(sourceCode, sourceOffset, relativeOffset) {
|
||||
const absoluteOffset = sourceOffset + relativeOffset;
|
||||
const prefix = sourceCode.slice(0, absoluteOffset);
|
||||
const lastLineBreak = prefix.lastIndexOf("\n");
|
||||
return {
|
||||
line: prefix.split("\n").length,
|
||||
column: absoluteOffset - lastLineBreak,
|
||||
};
|
||||
}
|
||||
|
||||
function transformVueTemplate(templateCode, filePath, rootDir, options = {}) {
|
||||
const ast = parseTemplate(templateCode, { comments: true });
|
||||
const replacements = [];
|
||||
const records = [];
|
||||
const sourceCode = options.sourceCode ?? templateCode;
|
||||
const sourceOffset = options.sourceOffset ?? 0;
|
||||
const resolveLoc = (node, offset = 0) =>
|
||||
resolveAbsoluteLoc(sourceCode, sourceOffset, node.loc.start.offset + offset);
|
||||
|
||||
walkTemplateNode(ast, (node, parent) => {
|
||||
if (node.type === 2 && containsHan(node.content)) {
|
||||
const canonical = node.content.trim();
|
||||
const record = buildMessageRecord(
|
||||
filePath,
|
||||
rootDir,
|
||||
node.content.trim(),
|
||||
canonical,
|
||||
0,
|
||||
{
|
||||
line: node.loc.start.line,
|
||||
column: node.loc.start.column + 1,
|
||||
},
|
||||
resolveLoc(node, node.content.indexOf(canonical)),
|
||||
);
|
||||
const replacement = createTextNodeReplacement(node.loc.source, record);
|
||||
if (!replacement) {
|
||||
@@ -527,10 +539,7 @@ function transformVueTemplate(templateCode, filePath, rootDir) {
|
||||
rootDir,
|
||||
node.value.content,
|
||||
0,
|
||||
{
|
||||
line: node.loc.start.line,
|
||||
column: node.loc.start.column + 1,
|
||||
},
|
||||
resolveLoc(node),
|
||||
);
|
||||
records.push(record);
|
||||
replacements.push({
|
||||
@@ -552,10 +561,7 @@ function transformVueTemplate(templateCode, filePath, rootDir) {
|
||||
node.content,
|
||||
filePath,
|
||||
rootDir,
|
||||
{
|
||||
line: node.loc.start.line,
|
||||
column: node.loc.start.column + 1,
|
||||
},
|
||||
resolveLoc(node),
|
||||
);
|
||||
if (!transformed.changed) {
|
||||
return;
|
||||
@@ -585,7 +591,10 @@ function transformVueSFC(code, filePath, rootDir) {
|
||||
let changed = false;
|
||||
|
||||
if (descriptor.template) {
|
||||
const templateResult = transformVueTemplate(descriptor.template.content, filePath, rootDir);
|
||||
const templateResult = transformVueTemplate(descriptor.template.content, filePath, rootDir, {
|
||||
sourceCode: code,
|
||||
sourceOffset: descriptor.template.loc.start.offset,
|
||||
});
|
||||
records.push(...templateResult.records);
|
||||
if (templateResult.changed) {
|
||||
changed = true;
|
||||
@@ -651,7 +660,7 @@ function collectCatalogRecords(rootDir) {
|
||||
return records;
|
||||
}
|
||||
|
||||
function syncCatalogFiles(rootDir) {
|
||||
export function syncCatalogFiles(rootDir) {
|
||||
const records = collectCatalogRecords(rootDir);
|
||||
const catalog = mergeMessageRecords(records);
|
||||
const generatedDir = path.join(rootDir, "src/i18n/generated");
|
||||
|
||||
@@ -273,7 +273,6 @@ const hasHomeAd = computed(() => normalizedHomeAds.value.length > 0);
|
||||
<div
|
||||
class="flex-1 center-row justify-end shrink-0 gap-2 text-xs text-[#6f6f6f] pr-4 w-[200px]"
|
||||
>
|
||||
<span>刷新统计</span>
|
||||
<button
|
||||
type="button"
|
||||
class="center-row justify-center h-[24px] w-[24px] rounded-[6px] border border-[#3b3b3b] bg-[#242424] text-[#9d9d9d] transition-colors duration-150 hover:border-[#4c4c4c] hover:text-white disabled:cursor-not-allowed disabled:opacity-60"
|
||||
|
||||
@@ -2,9 +2,10 @@ export const LOCALE_STORAGE_KEY = "cursor-client:locale:v1";
|
||||
export const LOCALE_STORAGE_SOURCE_KEY = "cursor-client:locale-source:v1";
|
||||
export const SOURCE_LOCALE = "zh-CN";
|
||||
export const DEFAULT_LOCALE = "en-US";
|
||||
export const SUPPORTED_LOCALES = ["zh-CN", "en-US", "ja-JP"];
|
||||
export const SUPPORTED_LOCALES = ["zh-CN", "en-US", "ja-JP", "ru-RU"];
|
||||
export const LOCALE_OPTIONS = [
|
||||
{ label: "简体中文", value: "zh-CN" },
|
||||
{ label: "English", value: "en-US" },
|
||||
{ label: "日本語", value: "ja-JP" },
|
||||
{ label: "Русский", value: "ru-RU" },
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@
|
||||
"02216368edc68816": "No release notes",
|
||||
"02bc2e95bf49e587": "No",
|
||||
"03b11112dc970014": "Base URL",
|
||||
"045261cc748d4300": "Token budget allowed during Anthropic's thinking phase. Leave blank to use the default.",
|
||||
"047ec6b71d0cec08": "Control whether requests on the whitelist main path go through the local service or return to the original Cursor upstream endpoint",
|
||||
"04f632dd4f034d5e": "{0} context window must be a positive integer",
|
||||
"051836569928a9f9": "Edit",
|
||||
@@ -10,34 +9,39 @@
|
||||
"05c8a9238c702efa": "Direct Cursor Mode",
|
||||
"0647728439b5da2e": "You can configure the routing mode and model channels. Runtime logs are stored in",
|
||||
"092b520558eff5f2": "Not tested",
|
||||
"09ebc2643631ba25": "Cost Estimate",
|
||||
"0b0e7478e41fe677": "{0} tooltip text cannot be empty",
|
||||
"0c3b4cf7aa259edb": "Operation failed",
|
||||
"0dde813d719dbd01": "Failed to open homepage",
|
||||
"0e40a09ab4e664ab": "Duplicate model channel detected. Check the combination of url, modelID, apiKey, and displayName",
|
||||
"1117a2f86030d03b": "Cache reads and writes are included in Prompt-side statistics.",
|
||||
"11afd2a534395b18": "Valid",
|
||||
"124be3f86f197802": "Token Usage",
|
||||
"13b61c5f697b6700": "Cache Hit Rate",
|
||||
"15d124b200ddabed": "Maximum number of context tokens the model can accept in a single request. Leave blank to use the default.",
|
||||
"185aebe19c77425d": "{0} must be a JSON object",
|
||||
"18b7312022cd1840": "Start Service",
|
||||
"1af38868896cf53d": "Routing mode only supports local or upstream",
|
||||
"1baddde657dd2720": "Current outbound requests use system proxy",
|
||||
"1bc77f5ab979f4c1": "Add Model Settings",
|
||||
"1e238093b79b3165": "Uses 65536 by default when left blank",
|
||||
"21296ab18ad9af25": "Extra Params JSON",
|
||||
"24343a2096988d42": "Failed to open",
|
||||
"253d4a3428c648fb": "Cache write tokens: {0}",
|
||||
"258e4620e2108793": "Uses 4096 by default when left blank",
|
||||
"26a3855aed1d8d17": "Service not running",
|
||||
"281eb6d08c9960d0": "{0} thinking budget token must be a positive integer",
|
||||
"28aeffc70ceb4267": "Change the display language for this interface. The setting takes effect immediately and is saved on this device.",
|
||||
"2caeaec539e78898": "Thinking Budget Token",
|
||||
"2a24519398684ed5": "Visit Homepage",
|
||||
"2cd0f3be8738a86c": "Cancel",
|
||||
"2d706f7981b45a7b": "Local settings saved",
|
||||
"2f9daa828907b93f": "Delete",
|
||||
"30bb57a50caedc38": "e.g. For everyday code completion and Q&A",
|
||||
"3275d7e70bbdc3d5": "{0}'s {1}",
|
||||
"32b3c9a50003f77a": "Output tokens are estimated",
|
||||
"33d2d273e2bd5f88": "Failed to open user guide",
|
||||
"3463c5585c246df9": "Total: {0}",
|
||||
"3468b57e3edbc599": "Aggregated from turn summaries scanned from the history.",
|
||||
"35076178fe79a210": "Configuration changed. Please test again.",
|
||||
"358f07b2c1445ab1": "Author's Message",
|
||||
"36c149a9b3e8dca0": "models configured yet.",
|
||||
"37d23612f78a2e63": "Restart Now to Update",
|
||||
"392d0dceb45998d3": "Extreme",
|
||||
@@ -48,100 +52,137 @@
|
||||
"3d13868593ae4eeb": "Interface Language",
|
||||
"3ea83f9f55062582": "Release date: {0}",
|
||||
"3edda85621fd03b2": "model adapters",
|
||||
"3fd47edce45b3603": "Close",
|
||||
"42aa8e01e98c0d8c": "Total Duration",
|
||||
"438bb77c1ecdfab0": "Cache Write: {0} × ${1}/1M = {2}",
|
||||
"46056425a48ef05b": "Currently displayed using the reuse rate definition",
|
||||
"468adaa418ee1475": "e.g. https://api.openai.com/v1",
|
||||
"472642d58d3d5a6d": "Formula: {0}",
|
||||
"4923eeb7bd75cccd": "{0} model ID cannot be empty",
|
||||
"497c85690c4cc0fc": "No data",
|
||||
"4b8d11bf235e9213": "Current hit rate: {0}",
|
||||
"4c0a929bb86ce912": "Current: {0}",
|
||||
"4d2b6e53be6002e5": "Cache Statistics Strategy: {0} ({1})",
|
||||
"4d8c1c5b42830791": "Unknown",
|
||||
"4f0982ba1d37e51b": "Current outbound requests use environment variable proxy",
|
||||
"51194c3ad014fb29": "Retest required",
|
||||
"5205125c0e91d346": "Maximum tokens an Anthropic model may generate in a single response. Leave blank to use the default.",
|
||||
"56627c94a9decee6": "Max Output Tokens",
|
||||
"593a972852ba0004": "Cursor Assistant | Permanently Free | Custom API",
|
||||
"59a2195a01a8b35b": "{0} must be a valid JSON object",
|
||||
"5aa8f5590c940829": "Non-cache Input: {0}",
|
||||
"5beb1206c532729f": "Maximum number of tokens allowed in a single response. Leave blank to use the default.",
|
||||
"5d1687a4a41883fd": "Stopping...",
|
||||
"5e709712ce012f5d": "Current outbound requests use environment variable proxy: {0}",
|
||||
"6106f0a12583a334": "Refresh failed",
|
||||
"62873083fcaed27d": "Session Statistics",
|
||||
"6309a3bb5ba4c714": "Save failed",
|
||||
"636e3deffc1e960a": "Model {0}",
|
||||
"63d90d977348ab1f": "Duplicate",
|
||||
"64857b4338678314": "The value of custom header {0} must be a string",
|
||||
"64d2730f2ae37997": "Raw Response",
|
||||
"65cc5fd2e6ce6e75": "Backend started, proxy not started",
|
||||
"66af574b8948fe83": "{0} API key cannot be empty",
|
||||
"6744b4c6a9aa0038": "Disabled",
|
||||
"675109292da4eb36": "Not tested yet",
|
||||
"699fe7ade5407687": "Direct Mode",
|
||||
"6a7b96f399e58138": "e.g. sk-xxxxxx",
|
||||
"6aa8f49cc992dfd7": "Test",
|
||||
"6ae23d6d7cb18592": "Service error",
|
||||
"6d7872164df9b36e": "Normal Input: {0} × ${1}/1M = {2}",
|
||||
"6e584e3d5ce64aa0": "Save Settings",
|
||||
"6ec87609a8769425": "Default {0} / Count as creation {1}",
|
||||
"72f6c3525c0192a7": "When enabled, cache creation is included in the denominator",
|
||||
"737225e2904673fc": "Estimated output tokens: {0}",
|
||||
"7520bd50a5ee5471": "Stop testing {0}/{1}",
|
||||
"753d8bb0da9913ce": "Duplication failed",
|
||||
"774d6e1b7cb89751": "Default Definition",
|
||||
"77c9e582e85583af": "Test failed",
|
||||
"7a26bf794e9fb6bf": "Used only for display in the UI, so you can distinguish different models.",
|
||||
"7b6187c41e88b70c": "Testing...",
|
||||
"7bf8e2c07e084d09": "Model Editor",
|
||||
"7df7641e5e741346": "Cache Read / (Cache Read + Cache Creation + Non-cache Input)",
|
||||
"7e9e334aeb0bdc07": "Service operation failed",
|
||||
"7f68ebad19ba6bcd": "Check for Updates",
|
||||
"80296f4aa3f4543b": "Cache Read/Write",
|
||||
"81123c56d5d880d0": "API Key",
|
||||
"8139cb3dd11f5a67": "When enabled, the JSON object will override the final request headers. Duplicate headers are determined by this field, and values must be strings.",
|
||||
"8672864e90417138": "Max",
|
||||
"86df7ec743047234": "Service running",
|
||||
"87ed126f7bd1121e": "Routing Mode",
|
||||
"899add6275682210": "Uses 200000 by default when left blank",
|
||||
"8a4ef3e48e4e8a5a": "Enabled",
|
||||
"8c0d84831a3c3d5b": "Currently in Local Service Mode",
|
||||
"8c1935935600e336": "Model Test",
|
||||
"8cbcf741e727dbf7": "Model Settings",
|
||||
"8d1de152be6360ce": "Valid ratio: {0}",
|
||||
"8e2dc7b0d2e8f6f8": "e.g. OpenAI - GPT-4.1",
|
||||
"8f6f8d979c981ced": "Copied",
|
||||
"8f8baf5d18dd0492": "When enabled, the JSON object will override the OpenAI request body. Duplicate fields are determined by this field. OpenAI service_tier supports auto, default, flex, scale, priority; priority can be used for high-priority/Fast scenarios.",
|
||||
"8faa670b512b6b9b": "Open Model Settings",
|
||||
"907395551d0f3a93": "{0} OpenAI endpoint only supports /v1/responses, /v1/chat/completions, or a custom path starting with /",
|
||||
"917b1c1f18d0276b": "Saving...",
|
||||
"9196835e388d2550": "Test All",
|
||||
"91cba5c107a51892": "/ Invalid",
|
||||
"92059fe6cd713db4": "The model name actually sent to the server, for example gpt-4.1 or claude-sonnet.",
|
||||
"93e08803675e378b": "Model ID",
|
||||
"93faf55cd25c8319": "This software is completely free. If you were charged, you were likely scammed.\nWelcome to visit the author's homepage at https://space.bilibili.com/311706663/upload/video\nto see more updates, sharing guides, and future content.",
|
||||
"942ff2d88baca0c6": "Checking for updates...",
|
||||
"970388573a3c88c9": "Cache Read: {0} × ${1}/1M = {2}",
|
||||
"9730c15f3c1963a1": "Max",
|
||||
"986678eccf56dc28": "Service status is being updated. Please try again later.",
|
||||
"991e374fce0f4492": "Cursor Assistant",
|
||||
"9970736b36ff2b68": "The base URL of the model service, usually an OpenAI- or Anthropic-compatible endpoint.",
|
||||
"9a6e7d6c17471711": "Currently displayed using the default hit rate definition",
|
||||
"9b17fa889b307f7f": "Valid turns: {0}",
|
||||
"9c38b6e9bf94abec": "Switched to Direct Cursor Mode",
|
||||
"9cd4ac17428b86e4": "Cache hit rate = cache read tokens / prompt tokens",
|
||||
"9c41b3a9e12ac994": "Thinking Effort",
|
||||
"9d2ca261281a158a": "Later",
|
||||
"9d2fb46c0ba890b9": "Custom Path",
|
||||
"9dc0825fba5422e4": "Loading...",
|
||||
"9e02529bcaef36c6": "Duplicate model channel detected. Please check the combination of url, modelID, apiKey, displayName, and endpoint",
|
||||
"a026f37e613cf48b": "Output Tokens",
|
||||
"a0d36236c523667c": "{0} Anthropic thinking effort only supports low, medium, high, xhigh, max",
|
||||
"a1a038dfa16c3ede": "You're already on the latest version (v{0}).",
|
||||
"a3030bf8f16dc63c": "Save",
|
||||
"a325d25c69e7256d": "Model settings not found; cannot duplicate",
|
||||
"a4dd8bb7e8b6eb31": "Show API Key",
|
||||
"a54d745d9a9249e2": "When enabled, the JSON object will override the Anthropic request body. Duplicate fields are determined by this field.",
|
||||
"a55a88237df85d98": "Currently in Direct Mode",
|
||||
"a567bdaa11367f26": "Medium",
|
||||
"a5f1bd344c92e195": "The API key required to call this model service.",
|
||||
"a693d69af48bfe48": "Save and Test",
|
||||
"a98585871c5313ff": "Display Name",
|
||||
"aa9e366f68d3d097": "Low",
|
||||
"ab607d54d86dc789": "Author leookun",
|
||||
"ac217e4d1ca410f1": "New version available",
|
||||
"ad79540418be700a": "Open the settings folder, or manage model settings separately",
|
||||
"ae5a738238463a92": "Hide API Key",
|
||||
"aed55419ce62f08e": "Switching...",
|
||||
"b10041a13f5c55b1": "Model Output: {0} × ${1}/1M = {2}",
|
||||
"b1c27820fec23edb": "High",
|
||||
"b42049dcf8a05ef7": "Switched to Local Service Mode",
|
||||
"b5409d4049286061": "Custom Path (Please enter the full request URL)",
|
||||
"b571037dc396a00c": "Total request tokens include both prompt and model output.",
|
||||
"b765005f69fa971f": "e.g. gpt-4.1",
|
||||
"b76a22622020f849": "Select interface protocol endpoint. When selecting 'Custom Path', please enter the complete request URL in the API address bar (including the /chat/completions or /responses suffix). The system will automatically detect the protocol type based on the trailing segment.",
|
||||
"b870928f8f9a24c4": "API Endpoint",
|
||||
"b90a8ac9c488ce46": "Select language",
|
||||
"ba3c66f90fd11725": "System proxy identified",
|
||||
"ba40014ff496f64e": "Type",
|
||||
"bb074b86a98f6911": "Context Window",
|
||||
"bbacfde55a92869f": "A higher hit rate means more repeated context is being reused.",
|
||||
"bc87a4121a0873b3": "Refresh Stats",
|
||||
"bd4464ea88d3f24a": "Total turns: {0}",
|
||||
"bddd504af0c92fd0": "System PAC/automatic proxy detected; current version is handled as a direct connection",
|
||||
"bef280f9eb392495": "Conversation Turns",
|
||||
"c228558cf257fc49": "Delete failed",
|
||||
"c3e9c3c60020b8b7": "Select Mode",
|
||||
"c5af02060847d167": "Thinking effort for Anthropic adaptive thinking. Requests will consistently use the new thinking.type=adaptive.",
|
||||
"c69f5bce63b9f14c": "Settings Folder",
|
||||
"c6f743953145f40b": "e.g. 4096 (leave blank to use the default)",
|
||||
"c8c14507b2d37395": "Reasoning Effort",
|
||||
"c98e118e0a43f078": "Model",
|
||||
"c9dd59beefd7144f": "Cache Read / (Cache Read + Non-cache Input)",
|
||||
"ca00a39fcea70dc6": "Starting...",
|
||||
"ca1d1059408b3837": "Invalid turns: {0}",
|
||||
"cd7ca5fb221e1c53": "{0} cannot be empty",
|
||||
"ce46f23cea3bf3c5": "When enabled, Cursor connects directly to the official service. Do not enable this.",
|
||||
"d0325067fed88e5a": "Cache hit rate {0}",
|
||||
"d08fd4224abcd69d": "Switch failed",
|
||||
@@ -151,26 +192,35 @@
|
||||
"d3209b935ae86797": "Model settings not found; cannot delete",
|
||||
"d373809ab86ba93b": "Copy",
|
||||
"d3b1da3088ddd334": "Model test failed",
|
||||
"d53d32f1a1211371": "Custom Headers JSON",
|
||||
"d7889896c5b7732a": "Anthropic Extra Params JSON",
|
||||
"d7da2aabd35772ec": "e.g. 200000 (leave blank to use the default)",
|
||||
"d95e5cb6bdcee553": "Include Cache Creation",
|
||||
"da590a8fe3ce4de0": "Please select",
|
||||
"daede9881787abe7": "Notes",
|
||||
"dbb4b5be9b5723dc": "{0} reasoning effort only supports low, medium, high, xhigh, and max",
|
||||
"dbee6e7139243362": "{0} base URL cannot be empty",
|
||||
"dc82c5e8fb2ab777": "Version: v{0}",
|
||||
"de8184da1ef88d03": "Configured",
|
||||
"e01c5dae36cf8c35": "When enabled, the JSON object will override the OpenAI request body. Duplicate fields are determined by this field. OpenAI service_tier supports auto, default, flex, scale, priority.",
|
||||
"e14c41ef2b7253c9": "Total request tokens: {0}",
|
||||
"e406825e0a72d2c2": "Local Settings",
|
||||
"e552c2accdbf5178": "Add Model",
|
||||
"e6faccfddce722e8": "Cache read tokens: {0}",
|
||||
"eaffd48cd2ea9f1a": "e.g. https://api.anthropic.com",
|
||||
"eb1be07f2ca6e506": "Estimated based on Claude Opus 4.7 pricing.",
|
||||
"ec3b17a75db49e24": "{0} t/s | First token {1}",
|
||||
"ec99e5c45d648fd6": "Update failed",
|
||||
"ee95057c6b0335d2": "Current outbound requests use system proxy: {0}",
|
||||
"f1e0fc261d42fe29": "Notes shown when hovering over the model list.",
|
||||
"f363622480699c52": "Reasoning effort only applies to some models that support reasoning_effort. Not all models do. Higher values are usually more stable, but may also be slower.",
|
||||
"f3a76d896853c1df": "Miss",
|
||||
"f3fae6cccb9004b1": "Custom header name cannot be empty",
|
||||
"f474a4108aba4c4c": "Stop Service",
|
||||
"f4f0ead1116b5b62": "Enable",
|
||||
"f56c6c82203b33f6": "Notice",
|
||||
"f61e03f047b786d5": "{0} max output tokens must be a positive integer",
|
||||
"f6e1c8b1a6970db5": "Current outbound requests do not use system proxy",
|
||||
"fac2a67ad87807c4": "OK",
|
||||
"fb7a4c81729ed0ca": "Stopping...",
|
||||
"fec45092945f8790": "User Guide"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"02216368edc68816": "更新内容はありません",
|
||||
"02bc2e95bf49e587": "まだ",
|
||||
"03b11112dc970014": "ベース URL",
|
||||
"045261cc748d4300": "Anthropic の思考フェーズで使用できる予算 Token 数。空欄の場合はデフォルト値を使用します。",
|
||||
"047ec6b71d0cec08": "ホワイトリストのメイン経路のリクエストをローカルサービス経由にするか、元の Cursor 上流アドレスに戻すかを制御します",
|
||||
"04f632dd4f034d5e": "{0} のコンテキストウィンドウは正の整数である必要があります",
|
||||
"051836569928a9f9": "編集",
|
||||
@@ -10,34 +9,39 @@
|
||||
"05c8a9238c702efa": "Cursor 直結モード",
|
||||
"0647728439b5da2e": "ルーティングモードとモデルチャネルを設定できます。実行ログは次にあります",
|
||||
"092b520558eff5f2": "未テスト",
|
||||
"09ebc2643631ba25": "価値見積もり",
|
||||
"0b0e7478e41fe677": "{0} のツールチップは必須です",
|
||||
"0c3b4cf7aa259edb": "操作に失敗しました",
|
||||
"0dde813d719dbd01": "ホームページを開けませんでした",
|
||||
"0e40a09ab4e664ab": "モデルチャネルが重複しています。url、modelID、apiKey、displayName の組み合わせを確認してください",
|
||||
"1117a2f86030d03b": "キャッシュの読み書きは Prompt 側の統計に含まれます。",
|
||||
"11afd2a534395b18": "有効",
|
||||
"124be3f86f197802": "Token 使用量",
|
||||
"13b61c5f697b6700": "キャッシュヒット率",
|
||||
"15d124b200ddabed": "モデルが1回のリクエストで受け取れる最大コンテキスト Token 数。空欄の場合はデフォルト値を使用します。",
|
||||
"185aebe19c77425d": "{0}はJSONオブジェクトである必要があります",
|
||||
"18b7312022cd1840": "サービスを開始",
|
||||
"1af38868896cf53d": "ルーティングモードは local または upstream のみサポートします",
|
||||
"1baddde657dd2720": "現在のアウトバウンドリクエストはシステムプロキシを使用しています",
|
||||
"1bc77f5ab979f4c1": "モデル設定を追加",
|
||||
"1e238093b79b3165": "空欄で 65536",
|
||||
"21296ab18ad9af25": "追加パラメータ JSON",
|
||||
"24343a2096988d42": "開けませんでした",
|
||||
"253d4a3428c648fb": "キャッシュ書き込み Token: {0}",
|
||||
"258e4620e2108793": "空欄で 4096",
|
||||
"26a3855aed1d8d17": "サービスは起動していません",
|
||||
"281eb6d08c9960d0": "{0} の思考予算 Token は正の整数である必要があります",
|
||||
"28aeffc70ceb4267": "この画面の表示言語を切り替えます。設定はすぐに反映され、この端末に保存されます",
|
||||
"2caeaec539e78898": "思考予算 Token",
|
||||
"2a24519398684ed5": "ホームページへ",
|
||||
"2cd0f3be8738a86c": "キャンセル",
|
||||
"2d706f7981b45a7b": "ローカル設定を保存しました",
|
||||
"2f9daa828907b93f": "削除",
|
||||
"30bb57a50caedc38": "例: 日常的なコード補完や Q&A 用",
|
||||
"3275d7e70bbdc3d5": "{0} の {1}",
|
||||
"32b3c9a50003f77a": "出力 Token は推定値です",
|
||||
"33d2d273e2bd5f88": "ユーザーガイドを開けませんでした",
|
||||
"3463c5585c246df9": "合計:{0}",
|
||||
"3468b57e3edbc599": "履歴からスキャンした各ターンの summary を集計しています。",
|
||||
"35076178fe79a210": "設定が変更されました。再テストしてください",
|
||||
"358f07b2c1445ab1": "著者からのメッセージ",
|
||||
"36c149a9b3e8dca0": "モデルは設定されていません。",
|
||||
"37d23612f78a2e63": "今すぐ再起動して更新",
|
||||
"392d0dceb45998d3": "最高",
|
||||
@@ -48,100 +52,137 @@
|
||||
"3d13868593ae4eeb": "表示言語",
|
||||
"3ea83f9f55062582": "公開日時: {0}",
|
||||
"3edda85621fd03b2": "件のモデルアダプター",
|
||||
"3fd47edce45b3603": "閉じる",
|
||||
"42aa8e01e98c0d8c": "総所要時間",
|
||||
"438bb77c1ecdfab0": "キャッシュ書き込み:{0} × ${1}/1M = {2}",
|
||||
"46056425a48ef05b": "現在、再利用率の定義に従って表示されています",
|
||||
"468adaa418ee1475": "例: https://api.openai.com/v1",
|
||||
"472642d58d3d5a6d": "数式:{0}",
|
||||
"4923eeb7bd75cccd": "{0} のモデル ID は必須です",
|
||||
"497c85690c4cc0fc": "データなし",
|
||||
"4b8d11bf235e9213": "現在のヒット率: {0}",
|
||||
"4c0a929bb86ce912": "現在:{0}",
|
||||
"4d2b6e53be6002e5": "キャッシュ統計ポリシー:{0}({1})",
|
||||
"4d8c1c5b42830791": "不明",
|
||||
"4f0982ba1d37e51b": "現在のアウトバウンドリクエストは環境変数プロキシを使用しています",
|
||||
"51194c3ad014fb29": "再テストが必要",
|
||||
"5205125c0e91d346": "Anthropic モデルが1回の応答で生成できる最大 Token 数。空欄の場合はデフォルト値を使用します。",
|
||||
"56627c94a9decee6": "最大出力 Token",
|
||||
"593a972852ba0004": "Cursor アシスタント | 永久無料 | カスタム API",
|
||||
"59a2195a01a8b35b": "{0}は有効なJSONオブジェクトである必要があります",
|
||||
"5aa8f5590c940829": "非キャッシュ入力:{0}",
|
||||
"5beb1206c532729f": "1回の応答で生成できる最大 Token 数。空欄の場合はデフォルト値を使用します。",
|
||||
"5d1687a4a41883fd": "停止中...",
|
||||
"5e709712ce012f5d": "現在のアウトバウンドリクエストは環境変数プロキシを使用しています:{0}",
|
||||
"6106f0a12583a334": "再読み込みに失敗しました",
|
||||
"62873083fcaed27d": "セッション統計",
|
||||
"6309a3bb5ba4c714": "保存に失敗しました",
|
||||
"636e3deffc1e960a": "モデル {0}",
|
||||
"63d90d977348ab1f": "複製",
|
||||
"64857b4338678314": "カスタムヘッダー {0} の値は文字列である必要があります",
|
||||
"64d2730f2ae37997": "生のレスポンス",
|
||||
"65cc5fd2e6ce6e75": "バックエンドは起動済み、プロキシは未起動です",
|
||||
"66af574b8948fe83": "{0} の API キーは必須です",
|
||||
"6744b4c6a9aa0038": "無効化",
|
||||
"675109292da4eb36": "まだテストしていません",
|
||||
"699fe7ade5407687": "直結モード",
|
||||
"6a7b96f399e58138": "例: sk-xxxxxx",
|
||||
"6aa8f49cc992dfd7": "テスト",
|
||||
"6ae23d6d7cb18592": "サービスエラー",
|
||||
"6d7872164df9b36e": "通常入力:{0} × ${1}/1M = {2}",
|
||||
"6e584e3d5ce64aa0": "設定を保存",
|
||||
"6ec87609a8769425": "デフォルト {0} / 作成カウント {1}",
|
||||
"72f6c3525c0192a7": "有効にすると、キャッシュ作成が分母に含まれます",
|
||||
"737225e2904673fc": "推定出力 Token: {0}",
|
||||
"7520bd50a5ee5471": "テスト停止 {0}/{1}",
|
||||
"753d8bb0da9913ce": "複製に失敗しました",
|
||||
"774d6e1b7cb89751": "デフォルト定義",
|
||||
"77c9e582e85583af": "テスト失敗",
|
||||
"7a26bf794e9fb6bf": "UI 上の表示専用で、異なるモデルを見分けやすくします。",
|
||||
"7b6187c41e88b70c": "テスト中...",
|
||||
"7bf8e2c07e084d09": "モデル編集",
|
||||
"7df7641e5e741346": "キャッシュ読み取り / (キャッシュ読み取り + キャッシュ作成 + 非キャッシュ入力)",
|
||||
"7e9e334aeb0bdc07": "サービス操作に失敗しました",
|
||||
"7f68ebad19ba6bcd": "アップデートを確認",
|
||||
"80296f4aa3f4543b": "キャッシュ読み書き",
|
||||
"81123c56d5d880d0": "API キー",
|
||||
"8139cb3dd11f5a67": "有効にすると、JSONオブジェクトが最終的なリクエストヘッダーを上書きします。同名のヘッダーはこの設定が優先され、値は文字列である必要があります。",
|
||||
"8672864e90417138": "最大",
|
||||
"86df7ec743047234": "サービス稼働中",
|
||||
"87ed126f7bd1121e": "ルーティングモード",
|
||||
"899add6275682210": "空欄で 200000",
|
||||
"8a4ef3e48e4e8a5a": "有効",
|
||||
"8c0d84831a3c3d5b": "現在はローカルサービスモードです",
|
||||
"8c1935935600e336": "モデルテスト",
|
||||
"8cbcf741e727dbf7": "モデル設定",
|
||||
"8d1de152be6360ce": "有効率: {0}",
|
||||
"8e2dc7b0d2e8f6f8": "例: OpenAI - GPT-4.1",
|
||||
"8f6f8d979c981ced": "コピーしました",
|
||||
"8f8baf5d18dd0492": "有効にすると、JSONオブジェクトがOpenAIのリクエストボディを上書きします。同名のフィールドはこの設定が優先されます。OpenAIのservice_tierはauto、default、flex、scale、priorityをサポートしており、priorityは高優先度/Fastのシナリオで使用できます。",
|
||||
"8faa670b512b6b9b": "モデル設定を開く",
|
||||
"907395551d0f3a93": "{0} のOpenAIエンドポイントは、/v1/responses、/v1/chat/completions、または / で始まるカスタムパスのみをサポートしています",
|
||||
"917b1c1f18d0276b": "保存中...",
|
||||
"9196835e388d2550": "すべてテスト",
|
||||
"91cba5c107a51892": "/ 異常",
|
||||
"92059fe6cd713db4": "実際にサーバーへ送信されるモデル名です。例: gpt-4.1 または claude-sonnet。",
|
||||
"93e08803675e378b": "モデル ID",
|
||||
"93faf55cd25c8319": "このソフトウェアは完全に無料です。もし料金を請求された場合は、詐欺の可能性が高いです。\n著者のホームページ https://space.bilibili.com/311706663/upload/video にアクセスして、更新情報や利用方法などを確認してください。",
|
||||
"942ff2d88baca0c6": "アップデートを確認中...",
|
||||
"970388573a3c88c9": "キャッシュ読み取り:{0} × ${1}/1M = {2}",
|
||||
"9730c15f3c1963a1": "最大",
|
||||
"986678eccf56dc28": "サービス状態を更新中です。しばらくしてからもう一度お試しください",
|
||||
"991e374fce0f4492": "Cursorアシスタント",
|
||||
"9970736b36ff2b68": "モデルサービスの API ルート URL。通常は OpenAI または Anthropic 互換のエンドポイントです。",
|
||||
"9a6e7d6c17471711": "現在、デフォルトのヒット率定義に従って表示されています",
|
||||
"9b17fa889b307f7f": "有効ターン: {0}",
|
||||
"9c38b6e9bf94abec": "Cursor 直結モードに切り替えました",
|
||||
"9cd4ac17428b86e4": "キャッシュヒット率 = キャッシュ読込 Token / Prompt Token",
|
||||
"9c41b3a9e12ac994": "思考強度",
|
||||
"9d2ca261281a158a": "後で",
|
||||
"9d2fb46c0ba890b9": "カスタムパス",
|
||||
"9dc0825fba5422e4": "読み込み中...",
|
||||
"9e02529bcaef36c6": "モデルチャネルが重複しています。url、modelID、apiKey、displayName、endpointの組み合わせを確認してください",
|
||||
"a026f37e613cf48b": "出力 Token",
|
||||
"a1a038dfa16c3ede": "すでに最新バージョンです(v{0})。",
|
||||
"a0d36236c523667c": "{0} のAnthropic思考強度はlow、medium、high、xhigh、maxのみをサポートしています",
|
||||
"a1a038dfa16c3ede": "すでに最新バージョン(v{0})です。",
|
||||
"a3030bf8f16dc63c": "保存",
|
||||
"a325d25c69e7256d": "モデル設定が存在しないため複製できません",
|
||||
"a4dd8bb7e8b6eb31": "API キーを表示",
|
||||
"a54d745d9a9249e2": "有効にすると、JSONオブジェクトがAnthropicのリクエストボディを上書きします。同名のフィールドはこの設定が優先されます。",
|
||||
"a55a88237df85d98": "現在は直結モードです",
|
||||
"a567bdaa11367f26": "中",
|
||||
"a5f1bd344c92e195": "このモデルサービスを呼び出すために必要な API キーです。",
|
||||
"a693d69af48bfe48": "保存してテスト",
|
||||
"a98585871c5313ff": "表示名",
|
||||
"aa9e366f68d3d097": "低",
|
||||
"ab607d54d86dc789": "著者 leookun",
|
||||
"ac217e4d1ca410f1": "新しいバージョンがあります",
|
||||
"ad79540418be700a": "設定フォルダーを開くか、モデル設定を個別に管理できます",
|
||||
"ae5a738238463a92": "API キーを隠す",
|
||||
"aed55419ce62f08e": "切替中...",
|
||||
"b10041a13f5c55b1": "モデル出力:{0} × ${1}/1M = {2}",
|
||||
"b1c27820fec23edb": "高",
|
||||
"b42049dcf8a05ef7": "ローカルサービスモードに切り替えました",
|
||||
"b5409d4049286061": "カスタムパス(完全なリクエストURLを入力してください)",
|
||||
"b571037dc396a00c": "総リクエスト Token には Prompt とモデル出力の両方が含まれます。",
|
||||
"b765005f69fa971f": "例: gpt-4.1",
|
||||
"b76a22622020f849": "インターフェースプロトコルのエンドポイントを選択します。「カスタムパス」を選択する場合は、APIアドレスバーに完全なリクエストURL(/chat/completions または /responses のサフィックスを含む)を入力してください。システムは末尾 of セグメントに基づいてプロトコルタイプを自動的に判断します。",
|
||||
"b870928f8f9a24c4": "APIエンドポイント",
|
||||
"b90a8ac9c488ce46": "言語を選択",
|
||||
"ba3c66f90fd11725": "システムプロキシを認識しました",
|
||||
"ba40014ff496f64e": "タイプ",
|
||||
"bb074b86a98f6911": "コンテキストウィンドウ",
|
||||
"bbacfde55a92869f": "ヒット率が高いほど、重複するコンテキストがより多く再利用されていることを示します。",
|
||||
"bc87a4121a0873b3": "統計を更新",
|
||||
"bd4464ea88d3f24a": "総ターン: {0}",
|
||||
"bddd504af0c92fd0": "システムのPAC/自動プロキシが検出されました。現在のバージョンは直接接続として処理されます",
|
||||
"bef280f9eb392495": "会話ターン",
|
||||
"c228558cf257fc49": "削除に失敗しました",
|
||||
"c3e9c3c60020b8b7": "モードを選択",
|
||||
"c5af02060847d167": "Anthropic adaptive thinkingの思考強度。リクエストは一貫して新しいthinking.type=adaptiveを使用します。",
|
||||
"c69f5bce63b9f14c": "設定フォルダー",
|
||||
"c6f743953145f40b": "例: 4096(空欄でデフォルト値)",
|
||||
"c8c14507b2d37395": "推論強度",
|
||||
"c98e118e0a43f078": "モデル",
|
||||
"c9dd59beefd7144f": "キャッシュ読み取り / (キャッシュ読み取り + 非キャッシュ入力)",
|
||||
"ca00a39fcea70dc6": "起動中...",
|
||||
"ca1d1059408b3837": "異常ターン: {0}",
|
||||
"cd7ca5fb221e1c53": "{0}は空にできません",
|
||||
"ce46f23cea3bf3c5": "有効にすると、Cursor は公式サービスへ直接接続します。オンにしないでください",
|
||||
"d0325067fed88e5a": "キャッシュヒット率 {0}",
|
||||
"d08fd4224abcd69d": "切替に失敗しました",
|
||||
@@ -151,26 +192,35 @@
|
||||
"d3209b935ae86797": "モデル設定が存在しないため削除できません",
|
||||
"d373809ab86ba93b": "コピー",
|
||||
"d3b1da3088ddd334": "モデルテストに失敗しました",
|
||||
"d53d32f1a1211371": "カスタムヘッダー JSON",
|
||||
"d7889896c5b7732a": "Anthropic 追加パラメータ JSON",
|
||||
"d7da2aabd35772ec": "例: 200000(空欄でデフォルト値)",
|
||||
"d95e5cb6bdcee553": "キャッシュ作成を含める",
|
||||
"da590a8fe3ce4de0": "選択してください",
|
||||
"daede9881787abe7": "メモ",
|
||||
"dbb4b5be9b5723dc": "{0} の推論強度は low、medium、high、xhigh、max のみサポートします",
|
||||
"dbee6e7139243362": "{0} のベース URL は必須です",
|
||||
"dc82c5e8fb2ab777": "バージョン: v{0}",
|
||||
"de8184da1ef88d03": "設定済み",
|
||||
"e01c5dae36cf8c35": "有効にすると、JSONオブジェクトがOpenAIのリクエストボディを上書きします。同名のフィールドはこの設定が優先されます。OpenAIのservice_tierはauto、default、flex、scale、priorityをサポートしています。",
|
||||
"e14c41ef2b7253c9": "総リクエスト Token: {0}",
|
||||
"e406825e0a72d2c2": "ローカル設定",
|
||||
"e552c2accdbf5178": "モデルを追加",
|
||||
"e6faccfddce722e8": "キャッシュ読込 Token: {0}",
|
||||
"eaffd48cd2ea9f1a": "例: https://api.anthropic.com",
|
||||
"eb1be07f2ca6e506": "Claude Opus 4.7の価格に基づいて見積もられます。",
|
||||
"ec3b17a75db49e24": "{0} t/s | 初回 Token {1}",
|
||||
"ec99e5c45d648fd6": "アップデートに失敗しました",
|
||||
"ee95057c6b0335d2": "現在のアウトバウンドリクエストはシステムプロキシを使用しています:{0}",
|
||||
"f1e0fc261d42fe29": "モデル一覧にホバーしたときに表示されるメモです。",
|
||||
"f363622480699c52": "推論強度は reasoning_effort をサポートする一部のモデルでのみ有効です。すべてのモデルが対応しているわけではありません。値が高いほど安定しやすい反面、遅くなることがあります。",
|
||||
"f3a76d896853c1df": "ミス",
|
||||
"f3fae6cccb9004b1": "カスタムヘッダー名は空にできません",
|
||||
"f474a4108aba4c4c": "サービスを停止",
|
||||
"f4f0ead1116b5b62": "有効化",
|
||||
"f56c6c82203b33f6": "お知らせ",
|
||||
"f61e03f047b786d5": "{0} の最大出力 Token は正の整数である必要があります",
|
||||
"f6e1c8b1a6970db5": "現在のアウトバウンドリクエストはシステムプロキシを使用していません",
|
||||
"fac2a67ad87807c4": "OK",
|
||||
"fb7a4c81729ed0ca": "停止中...",
|
||||
"fec45092945f8790": "ユーザーガイド"
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"02216368edc68816": "Нет примечаний к выпуску",
|
||||
"02bc2e95bf49e587": "Пока нет настроенных",
|
||||
"03b11112dc970014": "Базовый URL",
|
||||
"047ec6b71d0cec08": "Определяет, будут ли запросы основного маршрута из белого списка проходить через локальный сервис или исходный сервер Cursor",
|
||||
"04f632dd4f034d5e": "Размер контекстного окна {0} должен быть положительным целым числом",
|
||||
"051836569928a9f9": "Изменить",
|
||||
"054d763265603305": "например, 65536 (оставьте пустым для значения по умолчанию)",
|
||||
"05c8a9238c702efa": "Прямое подключение к Cursor",
|
||||
"0647728439b5da2e": "Здесь можно настроить режим маршрутизации и каналы моделей. Журналы выполнения находятся в",
|
||||
"092b520558eff5f2": "Не проверено",
|
||||
"09ebc2643631ba25": "Оценка стоимости",
|
||||
"0b0e7478e41fe677": "Текст подсказки {0} не может быть пустым",
|
||||
"0c3b4cf7aa259edb": "Не удалось выполнить операцию",
|
||||
"0dde813d719dbd01": "Не удалось открыть домашнюю страницу",
|
||||
"1117a2f86030d03b": "Чтение и запись кеша включены в статистику Prompt.",
|
||||
"11afd2a534395b18": "Успешно",
|
||||
"124be3f86f197802": "Использование токенов",
|
||||
"13b61c5f697b6700": "Доля попаданий в кеш",
|
||||
"15d124b200ddabed": "Максимальное число токенов контекста, которое модель может принять за один запрос. Оставьте поле пустым для значения по умолчанию.",
|
||||
"185aebe19c77425d": "{0} должен быть объектом JSON",
|
||||
"18b7312022cd1840": "Запустить сервис",
|
||||
"1af38868896cf53d": "Режим маршрутизации поддерживает только local или upstream",
|
||||
"1baddde657dd2720": "Исходящие запросы используют системный прокси",
|
||||
"1bc77f5ab979f4c1": "Добавить настройки модели",
|
||||
"1e238093b79b3165": "Если оставить пустым, используется 65536",
|
||||
"21296ab18ad9af25": "Дополнительные параметры JSON",
|
||||
"24343a2096988d42": "Не удалось открыть",
|
||||
"253d4a3428c648fb": "Токены записи в кеш: {0}",
|
||||
"26a3855aed1d8d17": "Сервис не запущен",
|
||||
"281eb6d08c9960d0": "Бюджет токенов рассуждения {0} должен быть положительным целым числом",
|
||||
"28aeffc70ceb4267": "Измените язык интерфейса. Настройка применяется сразу и сохраняется на этом устройстве.",
|
||||
"2a24519398684ed5": "Перейти на домашнюю страницу",
|
||||
"2cd0f3be8738a86c": "Отмена",
|
||||
"2d706f7981b45a7b": "Локальные настройки сохранены",
|
||||
"2f9daa828907b93f": "Удалить",
|
||||
"30bb57a50caedc38": "например, для ежедневного дополнения кода и ответов на вопросы",
|
||||
"3275d7e70bbdc3d5": "{1} для {0}",
|
||||
"32b3c9a50003f77a": "Количество выходных токенов рассчитано приблизительно",
|
||||
"33d2d273e2bd5f88": "Не удалось открыть руководство пользователя",
|
||||
"3463c5585c246df9": "Итого: {0}",
|
||||
"3468b57e3edbc599": "Сводка составлена по данным ходов, найденным в истории.",
|
||||
"35076178fe79a210": "Конфигурация изменена. Выполните проверку снова.",
|
||||
"358f07b2c1445ab1": "Сообщение автора",
|
||||
"36c149a9b3e8dca0": "моделей.",
|
||||
"37d23612f78a2e63": "Перезапустить и обновить",
|
||||
"392d0dceb45998d3": "Очень высокая",
|
||||
"393df9bb13ea4900": "Попадание",
|
||||
"3af7e5489e61ea51": "Обновление",
|
||||
"3bf8512aa520ed21": "Режим локального сервиса",
|
||||
"3c2a9f9901109e75": "Тип {0} поддерживает только OpenAI или Anthropic",
|
||||
"3d13868593ae4eeb": "Язык интерфейса",
|
||||
"3ea83f9f55062582": "Дата выпуска: {0}",
|
||||
"3edda85621fd03b2": "адаптеров моделей",
|
||||
"3fd47edce45b3603": "Закрыть",
|
||||
"42aa8e01e98c0d8c": "Общая длительность",
|
||||
"438bb77c1ecdfab0": "Запись в кеш: {0} × ${1}/1M = {2}",
|
||||
"46056425a48ef05b": "Сейчас используется расчет по коэффициенту повторного использования",
|
||||
"468adaa418ee1475": "например, https://api.openai.com/v1",
|
||||
"472642d58d3d5a6d": "Формула: {0}",
|
||||
"4923eeb7bd75cccd": "Идентификатор модели {0} не может быть пустым",
|
||||
"497c85690c4cc0fc": "Нет данных",
|
||||
"4c0a929bb86ce912": "Сейчас: {0}",
|
||||
"4d2b6e53be6002e5": "Стратегия статистики кеша: {0} ({1})",
|
||||
"4d8c1c5b42830791": "Неизвестно",
|
||||
"4f0982ba1d37e51b": "Исходящие запросы используют прокси из переменных окружения",
|
||||
"51194c3ad014fb29": "Требуется повторная проверка",
|
||||
"5205125c0e91d346": "Максимальное число токенов, которое модель Anthropic может сгенерировать за один ответ. Оставьте поле пустым для значения по умолчанию.",
|
||||
"56627c94a9decee6": "Макс. выходных токенов",
|
||||
"593a972852ba0004": "Cursor Assistant | Всегда бесплатно | Пользовательский API",
|
||||
"59a2195a01a8b35b": "{0} должен быть допустимым объектом JSON",
|
||||
"5aa8f5590c940829": "Ввод без кеша: {0}",
|
||||
"5beb1206c532729f": "Максимальное число токенов в одном ответе. Оставьте поле пустым для значения по умолчанию.",
|
||||
"5d1687a4a41883fd": "Остановка...",
|
||||
"5e709712ce012f5d": "Исходящие запросы используют прокси из переменных окружения: {0}",
|
||||
"6106f0a12583a334": "Не удалось обновить",
|
||||
"62873083fcaed27d": "Статистика сеанса",
|
||||
"6309a3bb5ba4c714": "Не удалось сохранить",
|
||||
"636e3deffc1e960a": "Модель {0}",
|
||||
"63d90d977348ab1f": "Дублировать",
|
||||
"64857b4338678314": "Значение пользовательского заголовка {0} должно быть строкой",
|
||||
"64d2730f2ae37997": "Исходный ответ",
|
||||
"65cc5fd2e6ce6e75": "Бэкенд запущен, прокси не запущен",
|
||||
"66af574b8948fe83": "Ключ API {0} не может быть пустым",
|
||||
"6744b4c6a9aa0038": "Выключено",
|
||||
"675109292da4eb36": "Еще не проверено",
|
||||
"699fe7ade5407687": "Прямой режим",
|
||||
"6a7b96f399e58138": "например, sk-xxxxxx",
|
||||
"6aa8f49cc992dfd7": "Проверить",
|
||||
"6ae23d6d7cb18592": "Ошибка сервиса",
|
||||
"6d7872164df9b36e": "Обычный ввод: {0} × ${1}/1M = {2}",
|
||||
"6e584e3d5ce64aa0": "Сохранить настройки",
|
||||
"6ec87609a8769425": "По умолчанию {0} / С учетом создания {1}",
|
||||
"72f6c3525c0192a7": "Если включено, создание кеша учитывается в знаменателе",
|
||||
"737225e2904673fc": "Расчетные выходные токены: {0}",
|
||||
"7520bd50a5ee5471": "Остановить проверку {0}/{1}",
|
||||
"753d8bb0da9913ce": "Не удалось дублировать",
|
||||
"774d6e1b7cb89751": "Стандартный расчет",
|
||||
"77c9e582e85583af": "Проверка не пройдена",
|
||||
"7a26bf794e9fb6bf": "Используется только для отображения в интерфейсе и помогает различать модели.",
|
||||
"7b6187c41e88b70c": "Проверка...",
|
||||
"7bf8e2c07e084d09": "Редактор модели",
|
||||
"7df7641e5e741346": "Чтение кеша / (Чтение кеша + Создание кеша + Ввод без кеша)",
|
||||
"7e9e334aeb0bdc07": "Не удалось выполнить операцию с сервисом",
|
||||
"7f68ebad19ba6bcd": "Проверить обновления",
|
||||
"80296f4aa3f4543b": "Чтение/запись кеша",
|
||||
"81123c56d5d880d0": "Ключ API",
|
||||
"8139cb3dd11f5a67": "Если включено, объект JSON переопределит итоговые заголовки запроса. При совпадении имен используются значения отсюда; все значения должны быть строками.",
|
||||
"8672864e90417138": "Максимальная",
|
||||
"86df7ec743047234": "Сервис запущен",
|
||||
"87ed126f7bd1121e": "Режим маршрутизации",
|
||||
"899add6275682210": "Если оставить пустым, используется 200000",
|
||||
"8a4ef3e48e4e8a5a": "Включено",
|
||||
"8c0d84831a3c3d5b": "Сейчас используется режим локального сервиса",
|
||||
"8c1935935600e336": "Проверка модели",
|
||||
"8cbcf741e727dbf7": "Настройки модели",
|
||||
"8d1de152be6360ce": "Доля успешных: {0}",
|
||||
"8e2dc7b0d2e8f6f8": "например, OpenAI - GPT-4.1",
|
||||
"8f6f8d979c981ced": "Скопировано",
|
||||
"8f8baf5d18dd0492": "Если включено, объект JSON переопределит тело запроса OpenAI. При совпадении полей используются значения отсюда. OpenAI service_tier поддерживает auto, default, flex, scale и priority; priority можно использовать для сценариев с высоким приоритетом/Fast.",
|
||||
"8faa670b512b6b9b": "Открыть настройки модели",
|
||||
"907395551d0f3a93": "Конечная точка OpenAI для {0} поддерживает только /v1/responses, /v1/chat/completions или пользовательский путь, начинающийся с /",
|
||||
"917b1c1f18d0276b": "Сохранение...",
|
||||
"9196835e388d2550": "Проверить все",
|
||||
"91cba5c107a51892": "/ Ошибочные",
|
||||
"92059fe6cd713db4": "Имя модели, которое фактически отправляется серверу, например gpt-4.1 или claude-sonnet.",
|
||||
"93e08803675e378b": "Идентификатор модели",
|
||||
"93faf55cd25c8319": "Это программное обеспечение полностью бесплатно. Если с вас взяли плату, скорее всего, вас обманули.\\nПосетите страницу автора: https://space.bilibili.com/311706663/upload/video\\nТам публикуются обновления, руководства и другие материалы.",
|
||||
"942ff2d88baca0c6": "Проверка обновлений...",
|
||||
"970388573a3c88c9": "Чтение кеша: {0} × ${1}/1M = {2}",
|
||||
"9730c15f3c1963a1": "Макс.",
|
||||
"986678eccf56dc28": "Состояние сервиса обновляется. Повторите попытку позже.",
|
||||
"9970736b36ff2b68": "Корневой адрес API сервиса модели, обычно совместимый с OpenAI или Anthropic.",
|
||||
"9a6e7d6c17471711": "Сейчас используется стандартный расчет доли попаданий",
|
||||
"9b17fa889b307f7f": "Успешных ходов: {0}",
|
||||
"9c38b6e9bf94abec": "Включено прямое подключение к Cursor",
|
||||
"9c41b3a9e12ac994": "Интенсивность рассуждений",
|
||||
"9d2ca261281a158a": "Позже",
|
||||
"9d2fb46c0ba890b9": "Пользовательский путь",
|
||||
"9dc0825fba5422e4": "Загрузка...",
|
||||
"9e02529bcaef36c6": "Обнаружен повторяющийся канал модели. Проверьте сочетание url, modelID, apiKey, displayName и endpoint",
|
||||
"a026f37e613cf48b": "Выходные токены",
|
||||
"a0d36236c523667c": "Интенсивность рассуждений Anthropic для {0} поддерживает только low, medium, high, xhigh и max",
|
||||
"a1a038dfa16c3ede": "У вас уже установлена последняя версия (v{0}).",
|
||||
"a3030bf8f16dc63c": "Сохранить",
|
||||
"a325d25c69e7256d": "Настройки модели не найдены; дублирование невозможно",
|
||||
"a4dd8bb7e8b6eb31": "Показать ключ API",
|
||||
"a54d745d9a9249e2": "Если включено, объект JSON переопределит тело запроса Anthropic. При совпадении полей используются значения отсюда.",
|
||||
"a55a88237df85d98": "Сейчас используется прямой режим",
|
||||
"a567bdaa11367f26": "Средняя",
|
||||
"a5f1bd344c92e195": "Ключ API, необходимый для обращения к сервису модели.",
|
||||
"a693d69af48bfe48": "Сохранить и проверить",
|
||||
"a98585871c5313ff": "Отображаемое имя",
|
||||
"aa9e366f68d3d097": "Низкая",
|
||||
"ab607d54d86dc789": "Автор leookun",
|
||||
"ac217e4d1ca410f1": "Доступна новая версия",
|
||||
"ad79540418be700a": "Открыть папку настроек или отдельно управлять настройками моделей",
|
||||
"ae5a738238463a92": "Скрыть ключ API",
|
||||
"aed55419ce62f08e": "Переключение...",
|
||||
"b10041a13f5c55b1": "Вывод модели: {0} × ${1}/1M = {2}",
|
||||
"b1c27820fec23edb": "Высокая",
|
||||
"b42049dcf8a05ef7": "Включен режим локального сервиса",
|
||||
"b5409d4049286061": "Пользовательский путь (введите полный URL запроса)",
|
||||
"b571037dc396a00c": "Общее число токенов запроса включает Prompt и вывод модели.",
|
||||
"b765005f69fa971f": "например, gpt-4.1",
|
||||
"b76a22622020f849": "Выберите конечную точку протокола. Для варианта «Пользовательский путь» укажите полный URL запроса в поле адреса API, включая суффикс /chat/completions или /responses. Тип протокола будет определен автоматически по последнему сегменту.",
|
||||
"b870928f8f9a24c4": "Конечная точка API",
|
||||
"b90a8ac9c488ce46": "Выберите язык",
|
||||
"ba3c66f90fd11725": "Обнаружен системный прокси",
|
||||
"ba40014ff496f64e": "Тип",
|
||||
"bb074b86a98f6911": "Контекстное окно",
|
||||
"bc87a4121a0873b3": "Обновить статистику",
|
||||
"bd4464ea88d3f24a": "Всего ходов: {0}",
|
||||
"bddd504af0c92fd0": "Обнаружен системный PAC/автоматический прокси; в текущей версии используется прямое подключение",
|
||||
"bef280f9eb392495": "Ходы диалога",
|
||||
"c228558cf257fc49": "Не удалось удалить",
|
||||
"c3e9c3c60020b8b7": "Выберите режим",
|
||||
"c5af02060847d167": "Интенсивность для адаптивных рассуждений Anthropic. В запросах всегда используется новый режим thinking.type=adaptive.",
|
||||
"c69f5bce63b9f14c": "Папка настроек",
|
||||
"c8c14507b2d37395": "Интенсивность рассуждений",
|
||||
"c98e118e0a43f078": "Модель",
|
||||
"c9dd59beefd7144f": "Чтение кеша / (Чтение кеша + Ввод без кеша)",
|
||||
"ca00a39fcea70dc6": "Запуск...",
|
||||
"ca1d1059408b3837": "Ошибочных ходов: {0}",
|
||||
"cd7ca5fb221e1c53": "{0} не может быть пустым",
|
||||
"ce46f23cea3bf3c5": "Если включено, Cursor подключается напрямую к официальному сервису. Не включайте этот режим.",
|
||||
"d0325067fed88e5a": "Доля попаданий в кеш: {0}",
|
||||
"d08fd4224abcd69d": "Не удалось переключить",
|
||||
"d1bde4a4e057b2c7": "[MainLayout] Не удалось загрузить сведения об авторе",
|
||||
"d20ab96566d33f25": "Отображаемое имя {0} не может быть пустым",
|
||||
"d2243e1d44b2a94e": "Изменить настройки модели",
|
||||
"d3209b935ae86797": "Настройки модели не найдены; удаление невозможно",
|
||||
"d373809ab86ba93b": "Копировать",
|
||||
"d3b1da3088ddd334": "Проверка модели не пройдена",
|
||||
"d53d32f1a1211371": "Пользовательские заголовки JSON",
|
||||
"d7889896c5b7732a": "Дополнительные параметры Anthropic JSON",
|
||||
"d7da2aabd35772ec": "например, 200000 (оставьте пустым для значения по умолчанию)",
|
||||
"d95e5cb6bdcee553": "Учитывать создание кеша",
|
||||
"da590a8fe3ce4de0": "Выберите значение",
|
||||
"daede9881787abe7": "Примечания",
|
||||
"dbb4b5be9b5723dc": "Интенсивность рассуждений {0} поддерживает только low, medium, high, xhigh и max",
|
||||
"dbee6e7139243362": "Базовый URL {0} не может быть пустым",
|
||||
"dc82c5e8fb2ab777": "Версия: v{0}",
|
||||
"de8184da1ef88d03": "Настроено",
|
||||
"e01c5dae36cf8c35": "Если включено, объект JSON переопределит тело запроса OpenAI. При совпадении полей используются значения отсюда. OpenAI service_tier поддерживает auto, default, flex, scale и priority.",
|
||||
"e14c41ef2b7253c9": "Всего токенов запроса: {0}",
|
||||
"e406825e0a72d2c2": "Локальные настройки",
|
||||
"e552c2accdbf5178": "Добавить модель",
|
||||
"e6faccfddce722e8": "Токены чтения из кеша: {0}",
|
||||
"eaffd48cd2ea9f1a": "например, https://api.anthropic.com",
|
||||
"eb1be07f2ca6e506": "Расчет основан на тарифах Claude Opus 4.7.",
|
||||
"ec3b17a75db49e24": "{0} т/с | Первый токен {1}",
|
||||
"ec99e5c45d648fd6": "Не удалось обновить",
|
||||
"ee95057c6b0335d2": "Исходящие запросы используют системный прокси: {0}",
|
||||
"f1e0fc261d42fe29": "Примечание, отображаемое при наведении на модель в списке.",
|
||||
"f363622480699c52": "Интенсивность рассуждений применяется только к моделям с поддержкой reasoning_effort. Чем выше значение, тем обычно стабильнее результат, но ответ может формироваться медленнее.",
|
||||
"f3a76d896853c1df": "Промах",
|
||||
"f3fae6cccb9004b1": "Имя пользовательского заголовка не может быть пустым",
|
||||
"f474a4108aba4c4c": "Остановить сервис",
|
||||
"f4f0ead1116b5b62": "Включить",
|
||||
"f56c6c82203b33f6": "Уведомление",
|
||||
"f61e03f047b786d5": "Максимальное число выходных токенов {0} должно быть положительным целым числом",
|
||||
"f6e1c8b1a6970db5": "Исходящие запросы не используют системный прокси",
|
||||
"fac2a67ad87807c4": "ОК",
|
||||
"fb7a4c81729ed0ca": "Остановка...",
|
||||
"fec45092945f8790": "Руководство пользователя"
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
"02216368edc68816": "无更新说明",
|
||||
"02bc2e95bf49e587": "当前还没有配置任何",
|
||||
"03b11112dc970014": "接口地址",
|
||||
"045261cc748d4300": "Anthropic 思考阶段允许消耗的预算 Token 数。留空时使用默认值。",
|
||||
"047ec6b71d0cec08": "控制白名单主链路请求走本地服务,还是回到原始 Cursor 上游地址",
|
||||
"04f632dd4f034d5e": "{0} 的上下文窗口必须为正整数",
|
||||
"051836569928a9f9": "编辑",
|
||||
@@ -10,34 +9,39 @@
|
||||
"05c8a9238c702efa": "直连 Cursor 模式",
|
||||
"0647728439b5da2e": "可配置运行模式和模型渠道;运行日志位于",
|
||||
"092b520558eff5f2": "未测试",
|
||||
"09ebc2643631ba25": "价值估算",
|
||||
"0b0e7478e41fe677": "{0} 的悬停提示不能为空",
|
||||
"0c3b4cf7aa259edb": "操作失败",
|
||||
"0dde813d719dbd01": "打开主页失败",
|
||||
"0e40a09ab4e664ab": "模型渠道重复,请检查 url、modelID、apiKey、displayName 组合",
|
||||
"1117a2f86030d03b": "缓存读写已计入 Prompt 侧统计。",
|
||||
"11afd2a534395b18": "有效",
|
||||
"124be3f86f197802": "Token 消耗",
|
||||
"13b61c5f697b6700": "缓存命中率",
|
||||
"15d124b200ddabed": "模型单次可接受的最大上下文 Token 数。留空时使用默认值。",
|
||||
"185aebe19c77425d": "{0}必须是 JSON 对象",
|
||||
"18b7312022cd1840": "启动服务",
|
||||
"1af38868896cf53d": "运行模式仅支持 local 或 upstream",
|
||||
"1baddde657dd2720": "当前出站请求使用系统代理",
|
||||
"1bc77f5ab979f4c1": "新增模型配置",
|
||||
"1e238093b79b3165": "留空时默认 65536",
|
||||
"21296ab18ad9af25": "额外参数 JSON",
|
||||
"24343a2096988d42": "打开失败",
|
||||
"253d4a3428c648fb": "缓存写入:{0}",
|
||||
"258e4620e2108793": "留空时默认 4096",
|
||||
"26a3855aed1d8d17": "服务未启动",
|
||||
"281eb6d08c9960d0": "{0} 的思考预算 Token 必须为正整数",
|
||||
"28aeffc70ceb4267": "切换当前界面显示语言,设置会立即生效并保存在本机",
|
||||
"2caeaec539e78898": "思考预算 Token",
|
||||
"2a24519398684ed5": "访问主页",
|
||||
"2cd0f3be8738a86c": "取消",
|
||||
"2d706f7981b45a7b": "本地配置已保存",
|
||||
"2f9daa828907b93f": "删除",
|
||||
"30bb57a50caedc38": "例如:用于日常代码补全与问答",
|
||||
"3275d7e70bbdc3d5": "{0} 的 {1}",
|
||||
"32b3c9a50003f77a": "输出 Token 为估算值",
|
||||
"33d2d273e2bd5f88": "打开使用教程失败",
|
||||
"3463c5585c246df9": "合计:{0}",
|
||||
"3468b57e3edbc599": "按历史记录里扫描到的回合 summary 汇总。",
|
||||
"35076178fe79a210": "配置已变更,请重新测试",
|
||||
"358f07b2c1445ab1": "作者寄语",
|
||||
"36c149a9b3e8dca0": "模型。",
|
||||
"37d23612f78a2e63": "立即重启更新",
|
||||
"392d0dceb45998d3": "极高",
|
||||
@@ -48,100 +52,137 @@
|
||||
"3d13868593ae4eeb": "界面语言",
|
||||
"3ea83f9f55062582": "发布时间:{0}",
|
||||
"3edda85621fd03b2": "个模型适配器",
|
||||
"3fd47edce45b3603": "关闭",
|
||||
"42aa8e01e98c0d8c": "总耗时",
|
||||
"438bb77c1ecdfab0": "缓存写入:{0} × ${1}/1M = {2}",
|
||||
"46056425a48ef05b": "当前按复用率口径显示",
|
||||
"468adaa418ee1475": "例如:https://api.openai.com/v1",
|
||||
"472642d58d3d5a6d": "公式:{0}",
|
||||
"4923eeb7bd75cccd": "{0} 的模型标识不能为空",
|
||||
"497c85690c4cc0fc": "暂无数据",
|
||||
"4b8d11bf235e9213": "当前命中率:{0}",
|
||||
"4c0a929bb86ce912": "当前:{0}",
|
||||
"4d2b6e53be6002e5": "缓存统计策略:{0}({1})",
|
||||
"4d8c1c5b42830791": "未知",
|
||||
"4f0982ba1d37e51b": "当前出站请求使用环境变量代理",
|
||||
"51194c3ad014fb29": "需重测",
|
||||
"5205125c0e91d346": "Anthropic 模型单次回复允许生成的最大 Token 数。留空时使用默认值。",
|
||||
"56627c94a9decee6": "最大输出 Token",
|
||||
"593a972852ba0004": "Cursor助手|永久免费|自定义API",
|
||||
"59a2195a01a8b35b": "{0}必须是合法 JSON 对象",
|
||||
"5aa8f5590c940829": "非缓存输入:{0}",
|
||||
"5beb1206c532729f": "单次回复允许生成的最大 Token 数。留空时使用默认值。",
|
||||
"5d1687a4a41883fd": "停止中...",
|
||||
"5e709712ce012f5d": "当前出站请求使用环境变量代理:{0}",
|
||||
"6106f0a12583a334": "刷新失败",
|
||||
"62873083fcaed27d": "会话统计",
|
||||
"6309a3bb5ba4c714": "保存失败",
|
||||
"636e3deffc1e960a": "模型 {0}",
|
||||
"63d90d977348ab1f": "复制",
|
||||
"64857b4338678314": "自定义请求头 {0} 的值必须是字符串",
|
||||
"64d2730f2ae37997": "原始返回",
|
||||
"65cc5fd2e6ce6e75": "后端已启动,代理未启动",
|
||||
"66af574b8948fe83": "{0} 的访问密钥不能为空",
|
||||
"6744b4c6a9aa0038": "已关闭",
|
||||
"675109292da4eb36": "尚未测试",
|
||||
"699fe7ade5407687": "直连模式",
|
||||
"6a7b96f399e58138": "例如:sk-xxxxxx",
|
||||
"6aa8f49cc992dfd7": "测试",
|
||||
"6ae23d6d7cb18592": "服务错误",
|
||||
"6d7872164df9b36e": "普通输入:{0} × ${1}/1M = {2}",
|
||||
"6e584e3d5ce64aa0": "保存配置",
|
||||
"6ec87609a8769425": "默认 {0} / 计入创建 {1}",
|
||||
"72f6c3525c0192a7": "开启后把缓存创建纳入分母",
|
||||
"737225e2904673fc": "输出推算:{0}",
|
||||
"7520bd50a5ee5471": "停止测试 {0}/{1}",
|
||||
"753d8bb0da9913ce": "复制失败",
|
||||
"774d6e1b7cb89751": "默认口径",
|
||||
"77c9e582e85583af": "测试失败",
|
||||
"7a26bf794e9fb6bf": "仅用于界面展示,便于你区分不同模型。",
|
||||
"7b6187c41e88b70c": "测试中...",
|
||||
"7bf8e2c07e084d09": "模型编辑",
|
||||
"7df7641e5e741346": "缓存读取 /(缓存读取 + 缓存创建 + 非缓存输入)",
|
||||
"7e9e334aeb0bdc07": "服务操作失败",
|
||||
"7f68ebad19ba6bcd": "检查更新",
|
||||
"80296f4aa3f4543b": "缓存读写",
|
||||
"81123c56d5d880d0": "访问密钥",
|
||||
"8139cb3dd11f5a67": "开启后会把 JSON 对象覆盖到最终请求头。同名请求头以这里为准,值必须是字符串。",
|
||||
"8672864e90417138": "最高",
|
||||
"86df7ec743047234": "服务运行中",
|
||||
"87ed126f7bd1121e": "运行模式",
|
||||
"899add6275682210": "留空时默认 200000",
|
||||
"8a4ef3e48e4e8a5a": "已开启",
|
||||
"8c0d84831a3c3d5b": "当前为本地服务模式",
|
||||
"8c1935935600e336": "模型测试",
|
||||
"8cbcf741e727dbf7": "模型配置",
|
||||
"8d1de152be6360ce": "有效占比:{0}",
|
||||
"8e2dc7b0d2e8f6f8": "例如:OpenAI - GPT-4.1",
|
||||
"8f6f8d979c981ced": "已复制",
|
||||
"8f8baf5d18dd0492": "开启后会把 JSON 对象覆盖到 OpenAI 请求体。同名字段以这里为准。OpenAI service_tier 支持 auto、default、flex、scale、priority;priority 可用于高优先级/Fast 类场景。",
|
||||
"8faa670b512b6b9b": "打开模型配置",
|
||||
"907395551d0f3a93": "{0} 的 OpenAI 端点仅支持 /v1/responses、/v1/chat/completions 或以 / 开头的自定义路径",
|
||||
"917b1c1f18d0276b": "保存中...",
|
||||
"9196835e388d2550": "测试全部",
|
||||
"91cba5c107a51892": "/ 异常",
|
||||
"92059fe6cd713db4": "请求实际发送给服务端的模型名称,例如 gpt-4.1 或 claude-sonnet。",
|
||||
"93e08803675e378b": "模型标识",
|
||||
"93faf55cd25c8319": "本软件是纯免费软件,如果你被收费,那大概率就是被骗了。\n欢迎点击访问作者主页 https://space.bilibili.com/311706663/upload/video\n查看更多更新动态、使用分享和后续内容。",
|
||||
"942ff2d88baca0c6": "检查更新中...",
|
||||
"970388573a3c88c9": "缓存读取:{0} × ${1}/1M = {2}",
|
||||
"9730c15f3c1963a1": "最大",
|
||||
"986678eccf56dc28": "服务状态更新中,请稍后再试",
|
||||
"991e374fce0f4492": "Cursor助手",
|
||||
"9970736b36ff2b68": "模型服务的 API 根地址,通常为兼容 OpenAI 或 Anthropic 的接口入口。",
|
||||
"9a6e7d6c17471711": "当前按默认命中率口径显示",
|
||||
"9b17fa889b307f7f": "有效轮次:{0}",
|
||||
"9c38b6e9bf94abec": "已切换到直连 Cursor 模式",
|
||||
"9cd4ac17428b86e4": "缓存命中率 = 缓存读取 Token / Prompt Token",
|
||||
"9c41b3a9e12ac994": "思考强度",
|
||||
"9d2ca261281a158a": "稍后",
|
||||
"9d2fb46c0ba890b9": "自定义路径",
|
||||
"9dc0825fba5422e4": "加载中...",
|
||||
"9e02529bcaef36c6": "模型渠道重复,请检查 url、modelID、apiKey、displayName、endpoint 组合",
|
||||
"a026f37e613cf48b": "输出 Token",
|
||||
"a0d36236c523667c": "{0} 的 Anthropic 思考强度仅支持 low、medium、high、xhigh、max",
|
||||
"a1a038dfa16c3ede": "当前已是最新版本(v{0})。",
|
||||
"a3030bf8f16dc63c": "保存",
|
||||
"a325d25c69e7256d": "模型配置不存在,无法复制",
|
||||
"a4dd8bb7e8b6eb31": "显示访问密钥",
|
||||
"a54d745d9a9249e2": "开启后会把 JSON 对象覆盖到 Anthropic 请求体。同名字段以这里为准。",
|
||||
"a55a88237df85d98": "当前为直连模式",
|
||||
"a567bdaa11367f26": "中",
|
||||
"a5f1bd344c92e195": "调用该模型服务需要使用的访问密钥。",
|
||||
"a693d69af48bfe48": "保存并测试",
|
||||
"a98585871c5313ff": "显示名称",
|
||||
"aa9e366f68d3d097": "低",
|
||||
"ab607d54d86dc789": "作者 leookun",
|
||||
"ac217e4d1ca410f1": "发现新版本",
|
||||
"ad79540418be700a": "打开设置目录,或单独管理模型配置",
|
||||
"ae5a738238463a92": "隐藏访问密钥",
|
||||
"aed55419ce62f08e": "切换中...",
|
||||
"b10041a13f5c55b1": "模型输出:{0} × ${1}/1M = {2}",
|
||||
"b1c27820fec23edb": "高",
|
||||
"b42049dcf8a05ef7": "已切换到本地服务模式",
|
||||
"b5409d4049286061": "自定义路径(请输入完整请求地址)",
|
||||
"b571037dc396a00c": "总请求 Token 包含 Prompt 和模型输出。",
|
||||
"b765005f69fa971f": "例如:gpt-4.1",
|
||||
"b76a22622020f849": "选择接口协议端点。选“自定义路径”时,请在接口地址栏填写完整请求地址(含 /chat/completions 或 /responses 路径后缀),系统会根据末段自动判断协议形态。",
|
||||
"b870928f8f9a24c4": "接口端点",
|
||||
"b90a8ac9c488ce46": "选择语言",
|
||||
"ba3c66f90fd11725": "已识别系统代理",
|
||||
"ba40014ff496f64e": "类型",
|
||||
"bb074b86a98f6911": "上下文窗口",
|
||||
"bbacfde55a92869f": "命中率越高,说明重复上下文复用得越多。",
|
||||
"bc87a4121a0873b3": "刷新统计",
|
||||
"bd4464ea88d3f24a": "总轮次:{0}",
|
||||
"bddd504af0c92fd0": "检测到系统 PAC/自动代理,当前版本按直连处理",
|
||||
"bef280f9eb392495": "对话轮次",
|
||||
"c228558cf257fc49": "删除失败",
|
||||
"c3e9c3c60020b8b7": "选择模式",
|
||||
"c5af02060847d167": "Anthropic adaptive thinking 的思考强度。请求会固定使用新版 thinking.type=adaptive。",
|
||||
"c69f5bce63b9f14c": "设置文件夹",
|
||||
"c6f743953145f40b": "例如:4096(留空用默认值)",
|
||||
"c8c14507b2d37395": "推理强度",
|
||||
"c98e118e0a43f078": "模型",
|
||||
"c9dd59beefd7144f": "缓存读取 /(缓存读取 + 非缓存输入)",
|
||||
"ca00a39fcea70dc6": "启动中...",
|
||||
"ca1d1059408b3837": "异常轮次:{0}",
|
||||
"cd7ca5fb221e1c53": "{0}不能为空",
|
||||
"ce46f23cea3bf3c5": "开启后,Cursor将直接接通官方,请勿开启",
|
||||
"d0325067fed88e5a": "缓存命中率 {0}",
|
||||
"d08fd4224abcd69d": "切换失败",
|
||||
@@ -151,26 +192,35 @@
|
||||
"d3209b935ae86797": "模型配置不存在,无法删除",
|
||||
"d373809ab86ba93b": "拷贝",
|
||||
"d3b1da3088ddd334": "模型测试失败",
|
||||
"d53d32f1a1211371": "自定义请求头 JSON",
|
||||
"d7889896c5b7732a": "Anthropic 额外参数 JSON",
|
||||
"d7da2aabd35772ec": "例如:200000(留空用默认值)",
|
||||
"d95e5cb6bdcee553": "计入缓存创建",
|
||||
"da590a8fe3ce4de0": "请选择",
|
||||
"daede9881787abe7": "备注",
|
||||
"dbb4b5be9b5723dc": "{0} 的推理强度仅支持 low、medium、high、xhigh、max",
|
||||
"dbee6e7139243362": "{0} 的接口地址不能为空",
|
||||
"dc82c5e8fb2ab777": "版本:v{0}",
|
||||
"de8184da1ef88d03": "已配置",
|
||||
"e01c5dae36cf8c35": "开启后会把 JSON 对象覆盖到 OpenAI 请求体。同名字段以这里为准。OpenAI service_tier 支持 auto、default、flex、scale、priority。",
|
||||
"e14c41ef2b7253c9": "总请求:{0}",
|
||||
"e406825e0a72d2c2": "本地配置",
|
||||
"e552c2accdbf5178": "新增模型",
|
||||
"e6faccfddce722e8": "缓存读取:{0}",
|
||||
"eaffd48cd2ea9f1a": "例如:https://api.anthropic.com",
|
||||
"eb1be07f2ca6e506": "按 Claude Opus 4.7 价格估算。",
|
||||
"ec3b17a75db49e24": "{0} t/s | 首字 {1}",
|
||||
"ec99e5c45d648fd6": "更新失败",
|
||||
"ee95057c6b0335d2": "当前出站请求使用系统代理:{0}",
|
||||
"f1e0fc261d42fe29": "模型列表 hover 时显示的备注说明。",
|
||||
"f363622480699c52": "推理强度仅对部分支持 reasoning_effort 的模型生效,并不是所有模型都支持。越高通常越稳,但也可能更慢。",
|
||||
"f3a76d896853c1df": "未命中",
|
||||
"f3fae6cccb9004b1": "自定义请求头名称不能为空",
|
||||
"f474a4108aba4c4c": "关闭服务",
|
||||
"f4f0ead1116b5b62": "启用",
|
||||
"f56c6c82203b33f6": "提示",
|
||||
"f61e03f047b786d5": "{0} 的最大输出 Token 必须为正整数",
|
||||
"f6e1c8b1a6970db5": "当前出站请求未使用系统代理",
|
||||
"fac2a67ad87807c4": "确定",
|
||||
"fb7a4c81729ed0ca": "关闭中...",
|
||||
"fec45092945f8790": "使用教程"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { computed, ref } from "vue";
|
||||
import { Events } from "@wailsio/runtime";
|
||||
import {
|
||||
DEFAULT_LOCALE,
|
||||
LOCALE_OPTIONS,
|
||||
@@ -10,17 +11,20 @@ import {
|
||||
import zhCNMessages from "@/i18n/locales/zh-CN.json";
|
||||
import enUSMessages from "@/i18n/locales/en-US.json";
|
||||
import jaJPMessages from "@/i18n/locales/ja-JP.json";
|
||||
import ruRUMessages from "@/i18n/locales/ru-RU.json";
|
||||
|
||||
const localeMessages = {
|
||||
"zh-CN": zhCNMessages,
|
||||
"en-US": enUSMessages,
|
||||
"ja-JP": jaJPMessages,
|
||||
"ru-RU": ruRUMessages,
|
||||
};
|
||||
|
||||
const languageLocaleMap = {
|
||||
zh: "zh-CN",
|
||||
en: "en-US",
|
||||
ja: "ja-JP",
|
||||
ru: "ru-RU",
|
||||
};
|
||||
|
||||
function isSupportedLocale(locale) {
|
||||
@@ -143,6 +147,7 @@ class LocalizedText extends String {
|
||||
|
||||
const currentLocale = ref(resolveInitialLocale());
|
||||
applyLocaleToDocument(currentLocale.value);
|
||||
Events.Emit("locale:changed", currentLocale.value);
|
||||
|
||||
const localizedCache = new Map();
|
||||
|
||||
@@ -155,6 +160,7 @@ export function setLocale(locale) {
|
||||
currentLocale.value = nextLocale;
|
||||
persistManualLocale(nextLocale);
|
||||
applyLocaleToDocument(nextLocale);
|
||||
Events.Emit("locale:changed", nextLocale);
|
||||
return nextLocale;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,17 @@ const title = computed(() => route.meta.title ?? "Cursor助手|永久免费|
|
||||
const directlyClose = computed(() => route.meta.directlyClose === true);
|
||||
const showFooter = computed(() => route.path === "/");
|
||||
const footerAuthorInfo = ref(null);
|
||||
|
||||
const localizedAuthorInfo = computed(() => {
|
||||
if (!footerAuthorInfo.value) return null;
|
||||
return {
|
||||
buttonText: "作者 leookun",
|
||||
dialogTitle: "作者寄语",
|
||||
dialogContent: "本软件是纯免费软件,如果你被收费,那大概率就是被骗了。\n欢迎点击访问作者主页 https://space.bilibili.com/311706663/upload/video\n查看更多更新动态、使用分享和后续内容。",
|
||||
dialogConfirmText: "访问主页",
|
||||
dialogCancelText: "关闭",
|
||||
};
|
||||
});
|
||||
const usageDocsURL = "https://docs.leokun.cn";
|
||||
let proxyStateTimer = null;
|
||||
const proxyStatePollIntervalMs = 10000;
|
||||
@@ -106,14 +117,14 @@ async function showActionError(title, error) {
|
||||
}
|
||||
|
||||
async function handleOpenAuthorHome() {
|
||||
if (!footerAuthorInfo.value) {
|
||||
if (!localizedAuthorInfo.value) {
|
||||
return;
|
||||
}
|
||||
const confirmed = await showModal({
|
||||
title: footerAuthorInfo.value.dialogTitle,
|
||||
content: footerAuthorInfo.value.dialogContent,
|
||||
confirmText: footerAuthorInfo.value.dialogConfirmText,
|
||||
cancelText: footerAuthorInfo.value.dialogCancelText,
|
||||
title: localizedAuthorInfo.value.dialogTitle,
|
||||
content: localizedAuthorInfo.value.dialogContent,
|
||||
confirmText: localizedAuthorInfo.value.dialogConfirmText,
|
||||
cancelText: localizedAuthorInfo.value.dialogCancelText,
|
||||
showCancel: true,
|
||||
});
|
||||
if (!confirmed) {
|
||||
@@ -221,13 +232,13 @@ onUnmounted(() => {
|
||||
<span>使用教程</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="footerAuthorInfo"
|
||||
v-if="localizedAuthorInfo"
|
||||
type="button"
|
||||
class="center-row shrink-0 gap-[6px] cursor-pointer rounded-[6px] px-[6px] py-[3px] transition-colors duration-150 hover:bg-[#1f1f1f] hover:text-[#e5e5e5]"
|
||||
@click="handleOpenAuthorHome"
|
||||
>
|
||||
<span class="icon-[ant-design--bilibili-outlined] text-[14px]"></span>
|
||||
<span>{{ footerAuthorInfo.buttonText }}</span>
|
||||
<span>{{ localizedAuthorInfo.buttonText }}</span>
|
||||
</button>
|
||||
<div
|
||||
v-if="updateViewState.footerDownloading"
|
||||
|
||||
@@ -995,6 +995,29 @@ export const appViewState = reactive({
|
||||
}),
|
||||
});
|
||||
|
||||
function localizeUpdateMessage(msg) {
|
||||
if (!msg) return "";
|
||||
if (/当前已是最新版本/.test(msg)) {
|
||||
const match = msg.match(/v?([0-9]+\.[0-9]+\.[0-9]+)/);
|
||||
const version = match ? match[1] : appState.appVersion || "...";
|
||||
return `当前已是最新版本(v${version})。`;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
function localizeReadyContent() {
|
||||
const version = appState.updateVersion || appState.appVersion || "...";
|
||||
const date = formatReleaseDate(appState.updateReleaseDate);
|
||||
const notes = appState.updateReleaseNotes || "";
|
||||
|
||||
return [
|
||||
`版本:v${version}`,
|
||||
`发布时间:${date}`,
|
||||
"",
|
||||
notes || "无更新说明",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export const updateViewState = reactive({
|
||||
footerDownloading: computed(() => appState.updateState === "downloading"),
|
||||
footerBusy: computed(() => ["checking", "installing"].includes(appState.updateState)),
|
||||
@@ -1016,24 +1039,25 @@ export const updateViewState = reactive({
|
||||
promptContent: computed(() => {
|
||||
switch (appState.updatePromptKind) {
|
||||
case "ready":
|
||||
return [
|
||||
`版本:v${appState.updateVersion || appState.appVersion || "..."}`,
|
||||
`发布时间:${formatReleaseDate(appState.updateReleaseDate)}`,
|
||||
"",
|
||||
appState.updateReleaseNotes || "无更新说明",
|
||||
].join("\n");
|
||||
return localizeReadyContent();
|
||||
case "error":
|
||||
return appState.updateError || appState.updateMessage || GENERIC_SERVICE_ERROR;
|
||||
return appState.updateError || localizeUpdateMessage(appState.updateMessage) || GENERIC_SERVICE_ERROR;
|
||||
default:
|
||||
return appState.updateMessage || `当前已是最新版本(v${appState.appVersion || "..."})。`;
|
||||
return localizeUpdateMessage(appState.updateMessage) || localizeUpdateMessage(`当前已是最新版本(v${appState.appVersion || "..."})。`);
|
||||
}
|
||||
}),
|
||||
promptConfirmText: computed(() =>
|
||||
appState.updatePromptKind === "ready" ? "立即重启更新" : "确定",
|
||||
),
|
||||
promptCancelText: computed(() =>
|
||||
appState.updatePromptKind === "ready" ? "稍后" : "取消",
|
||||
),
|
||||
promptConfirmText: computed(() => {
|
||||
if (appState.updatePromptKind === "ready") {
|
||||
return "立即重启更新";
|
||||
}
|
||||
return "确定";
|
||||
}),
|
||||
promptCancelText: computed(() => {
|
||||
if (appState.updatePromptKind === "ready") {
|
||||
return "稍后";
|
||||
}
|
||||
return "取消";
|
||||
}),
|
||||
promptShowCancel: computed(() => appState.updatePromptKind === "ready"),
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ go 1.25.0
|
||||
require (
|
||||
codeberg.org/readeck/go-readability/v2 v2.1.1
|
||||
connectrpc.com/connect v1.19.1
|
||||
github.com/PuerkitoBio/goquery v1.9.2
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/elazarl/goproxy v1.7.2
|
||||
github.com/firecrawl/html-to-markdown v0.0.0-20260312013131-1af9901a5d61
|
||||
@@ -29,7 +30,6 @@ require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.9.2 // indirect
|
||||
github.com/adrg/xdg v0.5.3 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||
|
||||
+70
-7
@@ -217,7 +217,7 @@ func Run(resources EmbeddedResources) error {
|
||||
Hidden: false,
|
||||
HideOnEscape: false,
|
||||
MinimiseButtonState: application.ButtonEnabled,
|
||||
MaximiseButtonState: application.ButtonHidden,
|
||||
MaximiseButtonState: application.ButtonEnabled,
|
||||
CloseButtonState: application.ButtonEnabled,
|
||||
BackgroundColour: application.RGBA{Red: 25, Green: 25, Blue: 25, Alpha: 255},
|
||||
Mac: application.MacWindow{
|
||||
@@ -268,37 +268,100 @@ func Run(resources EmbeddedResources) error {
|
||||
menu.AddSeparator()
|
||||
startItem := menu.Add("启动服务")
|
||||
stopItem := menu.Add("停止服务")
|
||||
menu.Add("检查更新").OnClick(func(ctx *application.Context) {
|
||||
updateItem := menu.Add("检查更新").OnClick(func(ctx *application.Context) {
|
||||
updateManager.CheckNow(true)
|
||||
})
|
||||
menu.AddSeparator()
|
||||
menu.Add("显示窗口").OnClick(func(ctx *application.Context) {
|
||||
showItem := menu.Add("显示窗口").OnClick(func(ctx *application.Context) {
|
||||
showMainWindow()
|
||||
})
|
||||
menu.Add("隐藏窗口").OnClick(func(ctx *application.Context) {
|
||||
hideItem := menu.Add("隐藏窗口").OnClick(func(ctx *application.Context) {
|
||||
window.Hide()
|
||||
})
|
||||
menu.AddSeparator()
|
||||
menu.Add("退出").OnClick(func(ctx *application.Context) {
|
||||
quitItem := menu.Add("退出").OnClick(func(ctx *application.Context) {
|
||||
proxyService.ShutdownForQuit()
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
var currentLocale = "zh-CN"
|
||||
|
||||
updateTrayLabels := func(locale string) {
|
||||
currentLocale = locale
|
||||
state := proxyService.GetState()
|
||||
if state.Running {
|
||||
if locale == "en-US" {
|
||||
statusItem.SetLabel("Status: Running")
|
||||
} else if locale == "ja-JP" {
|
||||
statusItem.SetLabel("状態:実行中")
|
||||
} else if locale == "ru-RU" {
|
||||
statusItem.SetLabel("Статус: запущено")
|
||||
} else {
|
||||
statusItem.SetLabel("状态:运行中")
|
||||
}
|
||||
} else {
|
||||
if locale == "en-US" {
|
||||
statusItem.SetLabel("Status: Not Started")
|
||||
} else if locale == "ja-JP" {
|
||||
statusItem.SetLabel("状態:未起動")
|
||||
} else if locale == "ru-RU" {
|
||||
statusItem.SetLabel("Статус: не запущено")
|
||||
} else {
|
||||
statusItem.SetLabel("状态:未启动")
|
||||
}
|
||||
}
|
||||
|
||||
if locale == "en-US" {
|
||||
startItem.SetLabel("Start Service")
|
||||
stopItem.SetLabel("Stop Service")
|
||||
updateItem.SetLabel("Check for Updates")
|
||||
showItem.SetLabel("Show Window")
|
||||
hideItem.SetLabel("Hide Window")
|
||||
quitItem.SetLabel("Exit")
|
||||
} else if locale == "ja-JP" {
|
||||
startItem.SetLabel("サービス起動")
|
||||
stopItem.SetLabel("サービス停止")
|
||||
updateItem.SetLabel("アップデートを確認")
|
||||
showItem.SetLabel("ウィンドウを表示")
|
||||
hideItem.SetLabel("ウィンドウを非表示")
|
||||
quitItem.SetLabel("終了")
|
||||
} else if locale == "ru-RU" {
|
||||
startItem.SetLabel("Запустить сервис")
|
||||
stopItem.SetLabel("Остановить сервис")
|
||||
updateItem.SetLabel("Проверить обновления")
|
||||
showItem.SetLabel("Показать окно")
|
||||
hideItem.SetLabel("Скрыть окно")
|
||||
quitItem.SetLabel("Выход")
|
||||
} else {
|
||||
startItem.SetLabel("启动服务")
|
||||
stopItem.SetLabel("停止服务")
|
||||
updateItem.SetLabel("检查更新")
|
||||
showItem.SetLabel("显示窗口")
|
||||
hideItem.SetLabel("隐藏窗口")
|
||||
quitItem.SetLabel("退出")
|
||||
}
|
||||
}
|
||||
|
||||
refreshTray := func() {
|
||||
state := proxyService.GetState()
|
||||
if state.Running {
|
||||
statusItem.SetLabel("状态:运行中")
|
||||
startItem.SetEnabled(false)
|
||||
stopItem.SetEnabled(true)
|
||||
} else {
|
||||
statusItem.SetLabel("状态:未启动")
|
||||
startItem.SetEnabled(true)
|
||||
stopItem.SetEnabled(false)
|
||||
}
|
||||
updateTrayLabels(currentLocale)
|
||||
if refreshAdAssetBaseURL() {
|
||||
refreshAdRuntime()
|
||||
}
|
||||
}
|
||||
|
||||
app.Event.On("locale:changed", func(e *application.CustomEvent) {
|
||||
if locale, ok := e.Data.(string); ok {
|
||||
updateTrayLabels(locale)
|
||||
}
|
||||
})
|
||||
app.Event.On("proxy:state", func(event *application.CustomEvent) {
|
||||
refreshTray()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
neturl "net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
||||
"cursor/gen/agentv1"
|
||||
)
|
||||
|
||||
const (
|
||||
baiduWebSearchBaseURL = "https://www.baidu.com/s?ie=utf-8&tn=baidu&wd="
|
||||
baiduWebSearchHostURL = "https://www.baidu.com"
|
||||
baiduSearchAbstractLimit = 300
|
||||
baiduSearchReferenceLimit = 8
|
||||
)
|
||||
|
||||
// extractBaiduWebSearchReferences 从百度搜索结果页 HTML 中解析出搜索结果列表。
|
||||
func extractBaiduWebSearchReferences(body string) []*agentv1.WebSearchReference {
|
||||
document, err := goquery.NewDocumentFromReader(strings.NewReader(body))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
references := make([]*agentv1.WebSearchReference, 0, baiduSearchReferenceLimit)
|
||||
document.Find("#content_left > div").EachWithBreak(func(_ int, selection *goquery.Selection) bool {
|
||||
if len(references) >= baiduSearchReferenceLimit {
|
||||
return false
|
||||
}
|
||||
if !selection.HasClass("c-container") {
|
||||
return true
|
||||
}
|
||||
title, resultURL, abstract := extractBaiduSearchResult(selection)
|
||||
if title == "" || resultURL == "" {
|
||||
return true
|
||||
}
|
||||
references = append(references, &agentv1.WebSearchReference{
|
||||
Title: title,
|
||||
Url: normalizeBaiduSearchURL(resultURL),
|
||||
Chunk: truncateBaiduSearchAbstract(abstract),
|
||||
})
|
||||
return true
|
||||
})
|
||||
return references
|
||||
}
|
||||
|
||||
// extractBaiduSearchResult 从单条百度搜索结果节点中提取标题、链接和摘要。
|
||||
func extractBaiduSearchResult(selection *goquery.Selection) (string, string, string) {
|
||||
title := cleanBaiduSearchText(selection.Find("h3").First().Text())
|
||||
resultURL, _ := selection.Find("h3 a").First().Attr("href")
|
||||
if title == "" {
|
||||
title = firstBaiduSearchLine(selection.Text())
|
||||
}
|
||||
if resultURL == "" {
|
||||
resultURL, _ = selection.Find("a").First().Attr("href")
|
||||
}
|
||||
abstract := cleanBaiduSearchText(selection.Find(".c-abstract").First().Text())
|
||||
if abstract == "" {
|
||||
abstract = cleanBaiduSearchText(selection.ChildrenFiltered("div").First().Text())
|
||||
}
|
||||
if abstract == "" {
|
||||
abstract = baiduSearchTextAfterFirstLine(selection.Text())
|
||||
}
|
||||
return title, strings.TrimSpace(resultURL), abstract
|
||||
}
|
||||
|
||||
// normalizeBaiduSearchURL 把百度返回的相对或协议省略链接归一化为绝对 URL。
|
||||
func normalizeBaiduSearchURL(rawURL string) string {
|
||||
rawURL = strings.TrimSpace(rawURL)
|
||||
if strings.HasPrefix(rawURL, "//") {
|
||||
return "https:" + rawURL
|
||||
}
|
||||
if strings.HasPrefix(rawURL, "/") {
|
||||
return baiduWebSearchHostURL + rawURL
|
||||
}
|
||||
return rawURL
|
||||
}
|
||||
|
||||
// resolveBaiduWebSearchRedirects 把百度跳转链接解析为最终目标地址,就地更新引用列表。
|
||||
func resolveBaiduWebSearchRedirects(client *http.Client, references []*agentv1.WebSearchReference) {
|
||||
for _, reference := range references {
|
||||
if reference == nil {
|
||||
continue
|
||||
}
|
||||
reference.Url = resolveBaiduRedirectURL(client, reference.GetUrl())
|
||||
}
|
||||
}
|
||||
|
||||
// resolveBaiduRedirectURL 判断链接是否是百度跳转链接,并尝试解析出真实目标。
|
||||
func resolveBaiduRedirectURL(client *http.Client, rawURL string) string {
|
||||
resultURL := normalizeBaiduSearchURL(rawURL)
|
||||
if !isBaiduRedirectURL(resultURL) {
|
||||
return resultURL
|
||||
}
|
||||
redirectClient := baiduRedirectHTTPClient(client)
|
||||
if location := requestBaiduRedirectLocation(redirectClient, http.MethodHead, resultURL); location != "" {
|
||||
return location
|
||||
}
|
||||
if location := requestBaiduRedirectLocation(redirectClient, http.MethodGet, resultURL); location != "" {
|
||||
return location
|
||||
}
|
||||
return resultURL
|
||||
}
|
||||
|
||||
// baiduRedirectHTTPClient 基于基础 client 构造一个不自动跟随重定向的短超时客户端。
|
||||
func baiduRedirectHTTPClient(base *http.Client) *http.Client {
|
||||
if base == nil {
|
||||
base = http.DefaultClient
|
||||
}
|
||||
client := *base
|
||||
if client.Timeout == 0 || client.Timeout > 6*time.Second {
|
||||
client.Timeout = 6 * time.Second
|
||||
}
|
||||
client.CheckRedirect = func(_ *http.Request, _ []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
return &client
|
||||
}
|
||||
|
||||
// requestBaiduRedirectLocation 发起一次请求并读取响应头中的重定向目标地址。
|
||||
func requestBaiduRedirectLocation(client *http.Client, method string, rawURL string) string {
|
||||
request, err := http.NewRequest(method, rawURL, nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36")
|
||||
request.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
|
||||
request.Header.Set("Referer", baiduWebSearchHostURL+"/")
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer response.Body.Close()
|
||||
location := strings.TrimSpace(response.Header.Get("Location"))
|
||||
if location == "" {
|
||||
return ""
|
||||
}
|
||||
return resolveBaiduLocationURL(rawURL, location)
|
||||
}
|
||||
|
||||
// resolveBaiduLocationURL 把响应头里的相对重定向地址解析为绝对地址。
|
||||
func resolveBaiduLocationURL(baseURL string, location string) string {
|
||||
parsedLocation, err := neturl.Parse(location)
|
||||
if err != nil {
|
||||
return location
|
||||
}
|
||||
if parsedLocation.IsAbs() {
|
||||
return parsedLocation.String()
|
||||
}
|
||||
parsedBase, err := neturl.Parse(baseURL)
|
||||
if err != nil {
|
||||
return location
|
||||
}
|
||||
return parsedBase.ResolveReference(parsedLocation).String()
|
||||
}
|
||||
|
||||
// isBaiduRedirectURL 判断给定地址是否是百度域名下的跳转链接。
|
||||
func isBaiduRedirectURL(rawURL string) bool {
|
||||
parsedURL, err := neturl.Parse(strings.TrimSpace(rawURL))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
host := strings.ToLower(parsedURL.Hostname())
|
||||
path := strings.ToLower(parsedURL.EscapedPath())
|
||||
return (host == "baidu.com" || strings.HasSuffix(host, ".baidu.com")) && strings.HasPrefix(path, "/link")
|
||||
}
|
||||
|
||||
// truncateBaiduSearchAbstract 按字符数截断摘要文本,避免结果过长。
|
||||
func truncateBaiduSearchAbstract(value string) string {
|
||||
value = cleanBaiduSearchText(value)
|
||||
if baiduSearchAbstractLimit <= 0 {
|
||||
return value
|
||||
}
|
||||
runes := []rune(value)
|
||||
if len(runes) <= baiduSearchAbstractLimit {
|
||||
return value
|
||||
}
|
||||
return string(runes[:baiduSearchAbstractLimit])
|
||||
}
|
||||
|
||||
// firstBaiduSearchLine 返回文本中第一个非空行。
|
||||
func firstBaiduSearchLine(value string) string {
|
||||
for _, line := range strings.Split(strings.ReplaceAll(value, "\r", "\n"), "\n") {
|
||||
line = cleanBaiduSearchText(line)
|
||||
if line != "" {
|
||||
return line
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// baiduSearchTextAfterFirstLine 返回除第一个非空行外剩余文本的拼接结果。
|
||||
func baiduSearchTextAfterFirstLine(value string) string {
|
||||
nonEmpty := make([]string, 0, 8)
|
||||
for _, line := range strings.Split(strings.ReplaceAll(value, "\r", "\n"), "\n") {
|
||||
line = cleanBaiduSearchText(line)
|
||||
if line != "" {
|
||||
nonEmpty = append(nonEmpty, line)
|
||||
}
|
||||
}
|
||||
if len(nonEmpty) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return cleanBaiduSearchText(strings.Join(nonEmpty[1:], " "))
|
||||
}
|
||||
|
||||
// cleanBaiduSearchText 折叠多余空白并去除首尾空格。
|
||||
func cleanBaiduSearchText(value string) string {
|
||||
return strings.Join(strings.Fields(strings.TrimSpace(value)), " ")
|
||||
}
|
||||
@@ -577,13 +577,68 @@ const (
|
||||
)
|
||||
|
||||
func (bridge *Bridge) executeWebSearch(searchTerm string) ([]*agentv1.WebSearchReference, string, error) {
|
||||
if strings.TrimSpace(searchTerm) == "" {
|
||||
searchTerm = strings.TrimSpace(searchTerm)
|
||||
if searchTerm == "" {
|
||||
return nil, "", fmt.Errorf("web search search_term is required")
|
||||
}
|
||||
client := bridge.httpClient
|
||||
if client == nil {
|
||||
client = netproxy.NewHTTPClient(15 * time.Second)
|
||||
}
|
||||
|
||||
// 先尝试百度搜索
|
||||
baiduReferences, baiduPayload, baiduErr := bridge.tryBaiduWebSearch(client, searchTerm)
|
||||
if baiduErr == nil && len(baiduReferences) > 0 {
|
||||
return baiduReferences, baiduPayload, nil
|
||||
}
|
||||
|
||||
// 百度失败,回退到 DuckDuckGo
|
||||
duckReferences, duckPayload, duckErr := bridge.tryDuckDuckGoWebSearch(client, searchTerm)
|
||||
if duckErr == nil && len(duckReferences) > 0 {
|
||||
return duckReferences, duckPayload, nil
|
||||
}
|
||||
|
||||
// 两者都失败,返回综合错误
|
||||
if baiduErr != nil && duckErr != nil {
|
||||
return nil, "", fmt.Errorf("web search failed: baidu=%v, duckduckgo=%v", baiduErr, duckErr)
|
||||
}
|
||||
return nil, "", fmt.Errorf("web search returned no parseable results")
|
||||
}
|
||||
|
||||
func (bridge *Bridge) tryBaiduWebSearch(client *http.Client, searchTerm string) ([]*agentv1.WebSearchReference, string, error) {
|
||||
requestURL := baiduWebSearchBaseURL + neturl.QueryEscape(searchTerm)
|
||||
request, err := http.NewRequest(http.MethodGet, requestURL, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36")
|
||||
request.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
|
||||
request.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
||||
request.Header.Set("Referer", baiduWebSearchHostURL+"/")
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
return nil, "", fmt.Errorf("baidu http status %d", response.StatusCode)
|
||||
}
|
||||
body, err := io.ReadAll(io.LimitReader(response.Body, 2*1024*1024))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
references := extractBaiduWebSearchReferences(string(body))
|
||||
if len(references) == 0 {
|
||||
return nil, "", fmt.Errorf("baidu returned no parseable results")
|
||||
}
|
||||
if len(references) > 5 {
|
||||
references = references[:5]
|
||||
}
|
||||
resolveBaiduWebSearchRedirects(client, references)
|
||||
return references, formatWebSearchPayload(searchTerm, references), nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) tryDuckDuckGoWebSearch(client *http.Client, searchTerm string) ([]*agentv1.WebSearchReference, string, error) {
|
||||
requestURL := webSearchURLOverride + neturl.QueryEscape(searchTerm)
|
||||
request, err := http.NewRequest(http.MethodGet, requestURL, nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -156,6 +156,18 @@ func anthropicEndpointURL(baseURL string) string {
|
||||
return base + "/v1/messages"
|
||||
}
|
||||
|
||||
// shouldRelocateAnthropicImages 判断是否需要把图片块搬运到末条 user 消息。
|
||||
//
|
||||
// 官方 Anthropic 端点(api.anthropic.com)可正确处理任意位置的图片,保持原样;
|
||||
// 其余第三方中转站默认启用搬运,规避「非末尾图片被丢弃」的转换问题。
|
||||
func shouldRelocateAnthropicImages(baseURL string) bool {
|
||||
base := strings.ToLower(strings.TrimSpace(baseURL))
|
||||
if base == "" {
|
||||
return false
|
||||
}
|
||||
return !strings.Contains(base, "api.anthropic.com")
|
||||
}
|
||||
|
||||
// ApplyAnthropicCompatibleAuthHeaders 同时兼容 Anthropic 原生 x-api-key 和 Bearer token 代理。
|
||||
func ApplyAnthropicCompatibleAuthHeaders(httpReq *http.Request, apiKey string) {
|
||||
if httpReq == nil {
|
||||
@@ -215,8 +227,9 @@ func (adapter *AnthropicAdapter) Stream(ctx context.Context, req StreamRequest,
|
||||
body := cloneRequestBodyOverride(req.RequestBodyOverride)
|
||||
if len(body) == 0 {
|
||||
thinkingConfig := buildAnthropicThinkingConfig(req)
|
||||
relocateImages := shouldRelocateAnthropicImages(baseURL)
|
||||
stableMessageCount := anthropicStableProviderMessageCount(req.Messages, req.StableMessageCount, thinkingConfig != nil)
|
||||
systemParts, messages, err := normalizeAnthropicProviderMessages(req.Messages, thinkingConfig != nil)
|
||||
systemParts, messages, err := normalizeAnthropicProviderMessages(req.Messages, thinkingConfig != nil, relocateImages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -261,18 +274,17 @@ func (adapter *AnthropicAdapter) Stream(ctx context.Context, req StreamRequest,
|
||||
body["tools"] = tools
|
||||
}
|
||||
body["system"] = anthropicProviderSystemBlocks(systemParts)
|
||||
if thinkingConfig != nil {
|
||||
body["thinking"] = thinkingConfig
|
||||
if normalizeRuntimeThinkingEffort(req.ThinkingEffort) != "disabled" {
|
||||
body["output_config"] = buildAnthropicOutputConfig(req)
|
||||
}
|
||||
}
|
||||
frontier := buildAnthropicCacheFrontier(body, stableMessageCount)
|
||||
req.RequestKnobs = annotateAnthropicRequestKnobs(req.RequestKnobs, body, frontier)
|
||||
body = cloneRequestBodyOverride(body)
|
||||
applyAnthropicCacheBreakpoints(body, frontier.BreakpointPositions)
|
||||
frontier.BreakpointCount = len(frontier.BreakpointPositions)
|
||||
}
|
||||
// applyAnthropicThinkingConfig 在 override 块之外无条件调用,确保 RequestBodyOverride
|
||||
// 路径与正常构造路径行为一致:disabled 时强制 thinking:{type:disabled} 并清理冲突字段,
|
||||
// 非 disabled 时按 AnthropicThinkingEffort 写 adaptive 配置。与 openai.go 的
|
||||
// applyOpenAIThinkingDisable 对称——后者也是无条件在两条路径之后调用。
|
||||
applyAnthropicThinkingConfig(body, req)
|
||||
if err := ApplyAnthropicExtraParams(body, req.AnthropicExtraParamsEnabled, req.AnthropicExtraParamsJSON); err != nil {
|
||||
finishedAt = time.Now().UTC()
|
||||
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "anthropic", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
|
||||
@@ -1066,7 +1078,7 @@ func anthropicStableProviderMessageCount(input []Message, stableReplayMessageCou
|
||||
if len(stableReplayMessages) == 0 {
|
||||
return 0
|
||||
}
|
||||
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled)
|
||||
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled, false)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
@@ -1122,7 +1134,7 @@ func isAnthropicCacheableBlock(block map[string]any) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool) ([]string, []anthropicMessage, error) {
|
||||
func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool, relocateImages bool) ([]string, []anthropicMessage, error) {
|
||||
systemParts := make([]string, 0, len(input))
|
||||
messages := make([]anthropicMessage, 0, len(input))
|
||||
pendingToolResults := make([]map[string]any, 0, 2)
|
||||
@@ -1208,9 +1220,70 @@ func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool) (
|
||||
}
|
||||
}
|
||||
flushToolResults()
|
||||
if relocateImages {
|
||||
messages = relocateAnthropicImagesToLastUserMessage(messages)
|
||||
}
|
||||
return systemParts, messages, nil
|
||||
}
|
||||
|
||||
// relocateAnthropicImagesToLastUserMessage 把所有 user 消息里的 image 块搬运到最后一条 user 消息的末尾。
|
||||
//
|
||||
// 背景:部分第三方中转站(如 Bedrock 代理)在 Anthropic→上游 的消息转换中,
|
||||
// 会丢弃「后面还跟着大量文本/消息」的非末尾图片块。将图片统一移动到末条 user 消息
|
||||
// 可规避该问题,同时保留图片信息本身。
|
||||
//
|
||||
// 数据流演变:
|
||||
//
|
||||
// [user_info] [query + IMG] [reminder] [reminder] [current_request]
|
||||
// → [user_info] [query] [reminder] [reminder] [current_request + IMG]
|
||||
//
|
||||
// 搬运后若某条 user 消息 content 变空,则丢弃该消息,避免 Anthropic 拒绝空内容消息。
|
||||
func relocateAnthropicImagesToLastUserMessage(messages []anthropicMessage) []anthropicMessage {
|
||||
lastUserIndex := -1
|
||||
for index := len(messages) - 1; index >= 0; index-- {
|
||||
if strings.TrimSpace(messages[index].Role) == "user" {
|
||||
lastUserIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
if lastUserIndex < 0 {
|
||||
return messages
|
||||
}
|
||||
|
||||
relocated := make([]map[string]any, 0, 2)
|
||||
for index := 0; index < len(messages); index++ {
|
||||
if index == lastUserIndex || strings.TrimSpace(messages[index].Role) != "user" {
|
||||
continue
|
||||
}
|
||||
kept := make([]map[string]any, 0, len(messages[index].Content))
|
||||
for _, block := range messages[index].Content {
|
||||
if isAnthropicImageBlock(block) {
|
||||
relocated = append(relocated, block)
|
||||
continue
|
||||
}
|
||||
kept = append(kept, block)
|
||||
}
|
||||
messages[index].Content = kept
|
||||
}
|
||||
if len(relocated) == 0 {
|
||||
return messages
|
||||
}
|
||||
messages[lastUserIndex].Content = append(messages[lastUserIndex].Content, relocated...)
|
||||
|
||||
compacted := make([]anthropicMessage, 0, len(messages))
|
||||
for index, message := range messages {
|
||||
if index != lastUserIndex && strings.TrimSpace(message.Role) == "user" && len(message.Content) == 0 {
|
||||
continue
|
||||
}
|
||||
compacted = append(compacted, message)
|
||||
}
|
||||
return compacted
|
||||
}
|
||||
|
||||
func isAnthropicImageBlock(block map[string]any) bool {
|
||||
return strings.TrimSpace(anthropicStringField(block, "type")) == "image"
|
||||
}
|
||||
|
||||
func anthropicProviderContentBlocks(message Message, thinkingEnabled bool) ([]map[string]any, error) {
|
||||
blocks, err := anthropicContentBlocks(message)
|
||||
if err != nil {
|
||||
@@ -1367,6 +1440,34 @@ func buildAnthropicThinkingConfig(req StreamRequest) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
// applyAnthropicThinkingConfig 在请求体构造完成后(含 RequestBodyOverride 路径)无条件调用,
|
||||
// 与 openai.go 的 applyOpenAIThinkingDisable 对称。它把 thinking 配置写入 body 并在 disabled
|
||||
// 时清理与之冲突的字段,确保两条构造路径行为一致:
|
||||
// - disabled: 强制 thinking:{type:"disabled"},删除 output_config / 残留 thinking adaptive 配置,
|
||||
// 记录 thinking_disabled_provider_param=thinking.type knob
|
||||
// - adaptive: 按 AnthropicThinkingEffort 写 thinking:{type:adaptive,display:summarized} + output_config
|
||||
//
|
||||
// 在 override 路径下,上层若已在 override body 里塞了 thinking/output_config,disabled 时会被正确覆盖。
|
||||
func applyAnthropicThinkingConfig(body map[string]any, req StreamRequest) {
|
||||
if len(body) == 0 {
|
||||
return
|
||||
}
|
||||
if normalizeRuntimeThinkingEffort(req.ThinkingEffort) != "disabled" {
|
||||
if strings.TrimSpace(req.AnthropicThinkingEffort) == "" {
|
||||
return
|
||||
}
|
||||
body["thinking"] = map[string]any{
|
||||
"type": "adaptive",
|
||||
"display": "summarized",
|
||||
}
|
||||
body["output_config"] = buildAnthropicOutputConfig(req)
|
||||
return
|
||||
}
|
||||
body["thinking"] = map[string]any{"type": "disabled"}
|
||||
delete(body, "output_config")
|
||||
setRequestKnob(req, "thinking_disabled_provider_param", "thinking.type")
|
||||
}
|
||||
|
||||
func buildAnthropicOutputConfig(req StreamRequest) map[string]any {
|
||||
return map[string]any{
|
||||
"effort": anthropicThinkingEffort(req),
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package modeladapter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestApplyAnthropicThinkingConfig covers the three effort branches on a body
|
||||
// built by the normal construction path, plus the override path symmetry that
|
||||
// was the original bug (thinking config was only applied inside the
|
||||
// `if len(body)==0` block, so RequestBodyOverride skipped it entirely).
|
||||
func TestApplyAnthropicThinkingConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
body map[string]any
|
||||
thinkingEffort string
|
||||
adaptiveEffort string
|
||||
wantThinking any
|
||||
wantOutputCfg bool // expect output_config key present
|
||||
}{
|
||||
{
|
||||
name: "disabled_writes_disabled_and_drops_output_config",
|
||||
body: map[string]any{"model": "m", "output_config": map[string]any{"effort": "high"}},
|
||||
thinkingEffort: "disabled",
|
||||
wantThinking: map[string]any{"type": "disabled"},
|
||||
wantOutputCfg: false,
|
||||
},
|
||||
{
|
||||
name: "adaptive_writes_adaptive_and_output_config",
|
||||
body: map[string]any{"model": "m"},
|
||||
adaptiveEffort: "high",
|
||||
wantThinking: map[string]any{"type": "adaptive", "display": "summarized"},
|
||||
wantOutputCfg: true,
|
||||
},
|
||||
{
|
||||
name: "empty_effort_no_op",
|
||||
body: map[string]any{"model": "m"},
|
||||
wantThinking: nil,
|
||||
wantOutputCfg: false,
|
||||
},
|
||||
{
|
||||
name: "disabled_overrides_existing_adaptive_thinking",
|
||||
body: map[string]any{"thinking": map[string]any{"type": "adaptive", "display": "summarized"}, "output_config": map[string]any{"effort": "high"}},
|
||||
thinkingEffort: "disabled",
|
||||
wantThinking: map[string]any{"type": "disabled"},
|
||||
wantOutputCfg: false,
|
||||
},
|
||||
{
|
||||
name: "disabled_normalizes_aliases",
|
||||
body: map[string]any{"model": "m"},
|
||||
thinkingEffort: "off",
|
||||
wantThinking: map[string]any{"type": "disabled"},
|
||||
wantOutputCfg: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := StreamRequest{
|
||||
ThinkingEffort: tc.thinkingEffort,
|
||||
AnthropicThinkingEffort: tc.adaptiveEffort,
|
||||
RequestKnobs: map[string]any{},
|
||||
}
|
||||
applyAnthropicThinkingConfig(tc.body, req)
|
||||
|
||||
gotThinking, hasThinking := tc.body["thinking"]
|
||||
if tc.wantThinking == nil {
|
||||
if hasThinking {
|
||||
t.Fatalf("expected no thinking key, got %v", gotThinking)
|
||||
}
|
||||
} else {
|
||||
if !hasThinking {
|
||||
t.Fatalf("expected thinking=%v, key absent", tc.wantThinking)
|
||||
}
|
||||
if !mapEqual(gotThinking, tc.wantThinking) {
|
||||
t.Fatalf("thinking mismatch\nwant: %v\ngot: %v", tc.wantThinking, gotThinking)
|
||||
}
|
||||
}
|
||||
|
||||
_, hasOutputCfg := tc.body["output_config"]
|
||||
if hasOutputCfg != tc.wantOutputCfg {
|
||||
t.Fatalf("output_config presence=%v, want %v", hasOutputCfg, tc.wantOutputCfg)
|
||||
}
|
||||
|
||||
if tc.thinkingEffort == "disabled" || tc.thinkingEffort == "off" {
|
||||
gotKnob := req.RequestKnobs["thinking_disabled_provider_param"]
|
||||
if gotKnob != "thinking.type" {
|
||||
t.Fatalf("knob thinking_disabled_provider_param=%v, want thinking.type", gotKnob)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyAnthropicThinkingConfigOverridePath simulates the RequestBodyOverride
|
||||
// branch: the adapter receives a body that did NOT go through the normal
|
||||
// construction (no tools/messages/system written by the adapter). Before the
|
||||
// fix, thinking config was skipped entirely on this path. Now
|
||||
// applyAnthropicThinkingConfig runs unconditionally and disables thinking.
|
||||
func TestApplyAnthropicThinkingConfigOverridePath(t *testing.T) {
|
||||
overrideBody := map[string]any{
|
||||
"model": "claude-x",
|
||||
"messages": []any{map[string]any{"role": "user", "content": "hi"}},
|
||||
"thinking": map[string]any{"type": "adaptive", "display": "summarized"},
|
||||
"output_config": map[string]any{"effort": "high"},
|
||||
"stream": true,
|
||||
}
|
||||
req := StreamRequest{
|
||||
ThinkingEffort: "disabled",
|
||||
RequestKnobs: map[string]any{},
|
||||
}
|
||||
|
||||
applyAnthropicThinkingConfig(overrideBody, req)
|
||||
|
||||
gotThinking := overrideBody["thinking"]
|
||||
if !mapEqual(gotThinking, map[string]any{"type": "disabled"}) {
|
||||
t.Fatalf("override path: thinking not forced to disabled, got %v", gotThinking)
|
||||
}
|
||||
if _, hasOutputCfg := overrideBody["output_config"]; hasOutputCfg {
|
||||
t.Fatalf("override path: output_config should be dropped on disabled, still present")
|
||||
}
|
||||
if gotKnob := req.RequestKnobs["thinking_disabled_provider_param"]; gotKnob != "thinking.type" {
|
||||
t.Fatalf("override path: knob=%v, want thinking.type", gotKnob)
|
||||
}
|
||||
}
|
||||
|
||||
func mapEqual(a, b any) bool {
|
||||
ma, okA := a.(map[string]any)
|
||||
mb, okB := b.(map[string]any)
|
||||
if !okA || !okB {
|
||||
return a == b
|
||||
}
|
||||
if len(ma) != len(mb) {
|
||||
return false
|
||||
}
|
||||
for k, va := range ma {
|
||||
vb, ok := mb[k]
|
||||
if !ok || !mapEqual(va, vb) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1901,10 +1901,13 @@ func openAIThinkingDisableKind(baseURL string, modelID string, endpoint string)
|
||||
strings.Contains(base, "bigmodel") ||
|
||||
strings.Contains(base, "z.ai") ||
|
||||
strings.Contains(base, "zhipu") ||
|
||||
strings.Contains(base, "xiaomimimo") ||
|
||||
strings.Contains(base, "mimo") ||
|
||||
strings.Contains(model, "deepseek") ||
|
||||
strings.Contains(model, "glm") ||
|
||||
strings.Contains(model, "zai") ||
|
||||
strings.Contains(model, "zhipu"):
|
||||
strings.Contains(model, "zhipu") ||
|
||||
strings.Contains(model, "mimo"):
|
||||
return "thinking_type"
|
||||
case openAIModelSupportsReasoningNone(model):
|
||||
return "reasoning_none"
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package modeladapter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestOpenAIThinkingDisableKindMiMo 验证小米 MiMo(base_url 含 xiaomimimo/mimo,
|
||||
// model 含 mimo)被识别为 thinking_type 分支,从而在用户禁用思考时正确写入
|
||||
// thinking:{type:"disabled"}。回归覆盖 deepseek/glm/qwen/gpt-5 等既有分支。
|
||||
func TestOpenAIThinkingDisableKindMiMo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
baseURL string
|
||||
modelID string
|
||||
endpoint string
|
||||
want string
|
||||
}{
|
||||
// MiMo —— 修复目标
|
||||
{name: "mimo official base + pro model", baseURL: "https://api.xiaomimimo.com/v1", modelID: "mimo-v2.5-pro", endpoint: "/chat/completions", want: "thinking_type"},
|
||||
{name: "mimo base by host keyword", baseURL: "https://api.xiaomimimo.com/v1", modelID: "mimo-v2.5", endpoint: "/chat/completions", want: "thinking_type"},
|
||||
{name: "mimo model only (custom base)", baseURL: "https://custom.proxy.example.com/v1", modelID: "mimo-v2.5-pro", endpoint: "/chat/completions", want: "thinking_type"},
|
||||
{name: "mimo ultraspeed variant", baseURL: "https://api.xiaomimimo.com/v1", modelID: "mimo-v2.5-pro-ultraspeed", endpoint: "/chat/completions", want: "thinking_type"},
|
||||
// 回归:既有 thinking_type 分支不受影响
|
||||
{name: "deepseek base", baseURL: "https://api.deepseek.com/v1", modelID: "deepseek-chat", endpoint: "/chat/completions", want: "thinking_type"},
|
||||
{name: "glm model via zhipu base", baseURL: "https://open.bigmodel.cn/api/paas/v4", modelID: "glm-4.6", endpoint: "/chat/completions", want: "thinking_type"},
|
||||
{name: "z.ai base", baseURL: "https://api.z.ai/api/paas/v4", modelID: "glm-4.5", endpoint: "/chat/completions", want: "thinking_type"},
|
||||
// 回归:enable_thinking 分支(qwen 系)
|
||||
{name: "qwen via dashscope", baseURL: "https://dashscope.aliyuncs.com/v1", modelID: "qwen-max", endpoint: "/chat/completions", want: "enable_thinking"},
|
||||
{name: "qwen model keyword", baseURL: "https://custom.example.com/v1", modelID: "qwen3-coder", endpoint: "/chat/completions", want: "enable_thinking"},
|
||||
// 回归:reasoning_none 分支(gpt-5.1+/gpt-6)
|
||||
{name: "gpt-5.1", baseURL: "https://api.openai.com/v1", modelID: "gpt-5.1", endpoint: "/chat/completions", want: "reasoning_none"},
|
||||
{name: "gpt-6", baseURL: "https://api.openai.com/v1", modelID: "gpt-6", endpoint: "/chat/completions", want: "reasoning_none"},
|
||||
// 回归:未知 provider 不做 disable
|
||||
{name: "unknown provider", baseURL: "https://api.unknown-llm.com/v1", modelID: "some-model", endpoint: "/chat/completions", want: ""},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := openAIThinkingDisableKind(tc.baseURL, tc.modelID, tc.endpoint)
|
||||
if got != tc.want {
|
||||
t.Fatalf("openAIThinkingDisableKind(%q, %q, %q) = %q, want %q", tc.baseURL, tc.modelID, tc.endpoint, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyOpenAIThinkingDisableMiMo 验证当 ThinkingEffort=disabled 且 provider
|
||||
// 为 MiMo 时,applyOpenAIThinkingDisable 会写入 thinking:{type:"disabled"} 并删除
|
||||
// reasoning_effort,与 deepseek/glm 行为一致。
|
||||
func TestApplyOpenAIThinkingDisableMiMo(t *testing.T) {
|
||||
req := StreamRequest{ThinkingEffort: "disabled", RequestKnobs: map[string]any{}}
|
||||
body := map[string]any{
|
||||
"model": "mimo-v2.5-pro",
|
||||
"messages": []map[string]any{{"role": "user", "content": "hi"}},
|
||||
"reasoning_effort": "high",
|
||||
}
|
||||
applyOpenAIThinkingDisable(body, req, "https://api.xiaomimimo.com/v1", "mimo-v2.5-pro", "/chat/completions")
|
||||
|
||||
thinking, ok := body["thinking"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected body[thinking] to be map[string]any, got %T (%v)", body["thinking"], body["thinking"])
|
||||
}
|
||||
if thinking["type"] != "disabled" {
|
||||
t.Fatalf("expected thinking.type=disabled, got %v", thinking["type"])
|
||||
}
|
||||
if _, stillPresent := body["reasoning_effort"]; stillPresent {
|
||||
t.Fatalf("reasoning_effort should be deleted when thinking disabled for mimo, got %v", body["reasoning_effort"])
|
||||
}
|
||||
if got := req.RequestKnobs["thinking_disabled_provider_param"]; got != "thinking.type" {
|
||||
t.Fatalf("expected request knob thinking_disabled_provider_param=thinking.type, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyOpenAIThinkingDisableMiMoNotTriggered 验证非 disabled 时不会误写 disable 字段。
|
||||
func TestApplyOpenAIThinkingDisableMiMoNotTriggered(t *testing.T) {
|
||||
req := StreamRequest{ThinkingEffort: "high", RequestKnobs: map[string]any{}}
|
||||
body := map[string]any{"model": "mimo-v2.5-pro", "reasoning_effort": "high"}
|
||||
applyOpenAIThinkingDisable(body, req, "https://api.xiaomimimo.com/v1", "mimo-v2.5-pro", "/chat/completions")
|
||||
if _, present := body["thinking"]; present {
|
||||
t.Fatalf("thinking should not be injected when ThinkingEffort != disabled, got %v", body["thinking"])
|
||||
}
|
||||
if body["reasoning_effort"] != "high" {
|
||||
t.Fatalf("reasoning_effort should be preserved when not disabled, got %v", body["reasoning_effort"])
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ func canMergeProviderAssistantToolCalls(last Message, current Message) bool {
|
||||
if strings.TrimSpace(last.Role) != "assistant" || strings.TrimSpace(current.Role) != "assistant" {
|
||||
return false
|
||||
}
|
||||
if len(last.ToolCalls) == 0 || len(current.ToolCalls) == 0 {
|
||||
if len(current.ToolCalls) == 0 {
|
||||
return false
|
||||
}
|
||||
if strings.TrimSpace(last.ToolCallID) != "" || strings.TrimSpace(last.Name) != "" {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package modeladapter
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSanitizeProviderMessagesMergesLegacyAssistantTextAndToolCallTurnsIdempotently(t *testing.T) {
|
||||
input := []Message{
|
||||
{
|
||||
Role: "assistant",
|
||||
Content: "Now let me pass stream.Mode in service.go.",
|
||||
ReasoningContent: "I need to update service.go.",
|
||||
},
|
||||
{
|
||||
Role: "assistant",
|
||||
Content: "",
|
||||
ReasoningContent: "I need to update service.go.",
|
||||
ToolCalls: []ToolCallDescriptor{
|
||||
{
|
||||
ID: "call_1",
|
||||
Type: "function",
|
||||
Function: ToolCallFunctionShape{
|
||||
Name: "PatchEdit",
|
||||
Arguments: `{"path":"/workspace/service.go"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Role: "tool",
|
||||
Content: `{"success":{"path":"/workspace/service.go"}}`,
|
||||
ToolCallID: "call_1",
|
||||
Name: "PatchEdit",
|
||||
},
|
||||
}
|
||||
|
||||
first := sanitizeProviderMessages(input)
|
||||
if len(first) != 2 {
|
||||
t.Fatalf("message count = %d, want 2: %#v", len(first), first)
|
||||
}
|
||||
|
||||
assistant := first[0]
|
||||
if assistant.Content != input[0].Content {
|
||||
t.Fatalf("assistant content = %q", assistant.Content)
|
||||
}
|
||||
if assistant.ReasoningContent != input[0].ReasoningContent {
|
||||
t.Fatalf("assistant reasoning = %q, want one copy of %q", assistant.ReasoningContent, input[0].ReasoningContent)
|
||||
}
|
||||
if len(assistant.ToolCalls) != 1 || assistant.ToolCalls[0].ID != "call_1" {
|
||||
t.Fatalf("assistant tool calls = %#v", assistant.ToolCalls)
|
||||
}
|
||||
|
||||
second := sanitizeProviderMessages(first)
|
||||
if !reflect.DeepEqual(second, first) {
|
||||
t.Fatalf("sanitizing normalized messages changed them:\nfirst: %#v\nsecond: %#v", first, second)
|
||||
}
|
||||
}
|
||||
@@ -897,8 +897,8 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
|
||||
reasoningSignature := ""
|
||||
pendingToolCalls := make([]ToolCallDescriptor, 0, len(payload.Content))
|
||||
pendingToolResults := make([]Message, 0, len(payload.Content))
|
||||
flushAssistantText := func(forceReasoningOnly bool) {
|
||||
if len(textParts) == 0 && (!forceReasoningOnly || len(reasoningParts) == 0) {
|
||||
flushAssistantTurn := func() {
|
||||
if len(textParts) == 0 && len(reasoningParts) == 0 && len(pendingToolCalls) == 0 {
|
||||
return
|
||||
}
|
||||
messages = append(messages, Message{
|
||||
@@ -906,23 +906,10 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
|
||||
Content: formatMessageText(strings.Join(textParts, "")),
|
||||
ReasoningContent: strings.Join(reasoningParts, ""),
|
||||
ReasoningSignature: reasoningSignature,
|
||||
})
|
||||
textParts = textParts[:0]
|
||||
reasoningParts = reasoningParts[:0]
|
||||
reasoningSignature = ""
|
||||
}
|
||||
flushToolCalls := func() {
|
||||
if len(pendingToolCalls) == 0 {
|
||||
return
|
||||
}
|
||||
messages = append(messages, Message{
|
||||
Role: "assistant",
|
||||
Content: "",
|
||||
ReasoningContent: strings.Join(reasoningParts, ""),
|
||||
ReasoningSignature: reasoningSignature,
|
||||
ToolCalls: append([]ToolCallDescriptor(nil), pendingToolCalls...),
|
||||
})
|
||||
messages = append(messages, pendingToolResults...)
|
||||
textParts = textParts[:0]
|
||||
reasoningParts = reasoningParts[:0]
|
||||
reasoningSignature = ""
|
||||
pendingToolCalls = pendingToolCalls[:0]
|
||||
@@ -932,8 +919,6 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
|
||||
for _, item := range payload.Content {
|
||||
switch strings.TrimSpace(item.Type) {
|
||||
case "reasoning":
|
||||
flushToolCalls()
|
||||
flushAssistantText(false)
|
||||
if item.Text != "" {
|
||||
reasoningParts = append(reasoningParts, item.Text)
|
||||
}
|
||||
@@ -941,12 +926,10 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
|
||||
reasoningSignature = strings.TrimSpace(item.Signature)
|
||||
}
|
||||
case "text":
|
||||
flushToolCalls()
|
||||
if item.Text != "" {
|
||||
textParts = append(textParts, item.Text)
|
||||
}
|
||||
case "tool-call":
|
||||
flushAssistantText(false)
|
||||
pendingToolCalls = append(pendingToolCalls, ToolCallDescriptor{
|
||||
ID: strings.TrimSpace(item.ToolCallID),
|
||||
Index: len(pendingToolCalls),
|
||||
@@ -966,9 +949,7 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
|
||||
}
|
||||
}
|
||||
}
|
||||
flushAssistantText(false)
|
||||
flushToolCalls()
|
||||
flushAssistantText(true)
|
||||
flushAssistantTurn()
|
||||
return messages
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package promptengine
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildReplayMessagesFromPendingAssistantOutputsKeepsTextAndToolCallInOneAssistantTurn(t *testing.T) {
|
||||
raw := `{
|
||||
"id":"1",
|
||||
"role":"assistant",
|
||||
"content":[
|
||||
{"type":"reasoning","text":"I need to update service.go.","signature":"reasoning-signature"},
|
||||
{"type":"text","text":"Now let me pass stream.Mode in service.go."},
|
||||
{"type":"tool-call","toolCallId":"call_1","toolName":"PatchEdit","args":{"path":"/workspace/service.go","old_string":"old","new_string":"new"},"result":{"success":{"path":"/workspace/service.go"}}}
|
||||
]
|
||||
}`
|
||||
|
||||
first := BuildReplayMessagesFromPendingAssistantOutputs([]string{raw})
|
||||
if len(first) != 2 {
|
||||
t.Fatalf("message count = %d, want 2 (assistant tool-call turn plus tool result): %#v", len(first), first)
|
||||
}
|
||||
|
||||
assistant := first[0]
|
||||
if assistant.Role != "assistant" {
|
||||
t.Fatalf("assistant role = %q, want assistant", assistant.Role)
|
||||
}
|
||||
if assistant.Content != "Now let me pass stream.Mode in service.go." {
|
||||
t.Fatalf("assistant content = %q", assistant.Content)
|
||||
}
|
||||
if assistant.ReasoningContent != "I need to update service.go." {
|
||||
t.Fatalf("assistant reasoning = %q", assistant.ReasoningContent)
|
||||
}
|
||||
if assistant.ReasoningSignature != "reasoning-signature" {
|
||||
t.Fatalf("assistant reasoning signature = %q", assistant.ReasoningSignature)
|
||||
}
|
||||
if len(assistant.ToolCalls) != 1 {
|
||||
t.Fatalf("assistant tool calls = %d, want 1", len(assistant.ToolCalls))
|
||||
}
|
||||
if assistant.ToolCalls[0].ID != "call_1" || assistant.ToolCalls[0].Function.Name != "PatchEdit" {
|
||||
t.Fatalf("assistant tool call = %#v", assistant.ToolCalls[0])
|
||||
}
|
||||
|
||||
toolResult := first[1]
|
||||
if toolResult.Role != "tool" || toolResult.ToolCallID != "call_1" || toolResult.Name != "PatchEdit" {
|
||||
t.Fatalf("tool result = %#v", toolResult)
|
||||
}
|
||||
|
||||
second := BuildReplayMessagesFromPendingAssistantOutputs([]string{raw})
|
||||
if !reflect.DeepEqual(second, first) {
|
||||
t.Fatalf("rebuilding the same pending output changed messages:\nfirst: %#v\nsecond: %#v", first, second)
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,10 @@ type artifactRecorder struct {
|
||||
}
|
||||
|
||||
type artifactSession struct {
|
||||
conversationID string
|
||||
turnSeq int64
|
||||
requestPayload map[string]any
|
||||
summaryPayload map[string]any
|
||||
conversationID string
|
||||
turnSeq int64
|
||||
requestPrefix requestArtifactPrefix
|
||||
hasRequestPrefix bool
|
||||
}
|
||||
|
||||
type requestArtifactPrefix struct {
|
||||
@@ -57,14 +57,22 @@ func (recorder *artifactRecorder) RecordLLMRequest(requestID string, _ string, m
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
session.requestPayload = cloneStringAnyMap(payload)
|
||||
prefix, hasPrefix, prefixErr := decodeRequestPrefixPayload(payload)
|
||||
session.requestPrefix = requestArtifactPrefix{}
|
||||
session.hasRequestPrefix = false
|
||||
if prefixErr == nil && hasPrefix && prefix != nil {
|
||||
session.requestPrefix = *prefix
|
||||
session.hasRequestPrefix = true
|
||||
}
|
||||
recorder.mu.Lock()
|
||||
recorder.sessions[artifactSessionKey(requestID, modelCallID)] = session
|
||||
recorder.mu.Unlock()
|
||||
if prefix, ok, err := decodeRequestPrefixPayload(session.requestPayload); err == nil && ok && prefix != nil {
|
||||
if prefixErr == nil && hasPrefix && prefix != nil {
|
||||
recorder.persistLatestRequestPrefix(session.conversationID, requestID, modelCallID, prefix)
|
||||
}
|
||||
recorder.debug.LogProviderArtifact(context.Background(), requestID, session.conversationID, modelCallID, "llm_request", session.requestPayload)
|
||||
// The payload is serialized synchronously for debug output and is deliberately
|
||||
// not retained in sessions: it can contain the entire replayed conversation.
|
||||
recorder.debug.LogProviderArtifact(context.Background(), requestID, session.conversationID, modelCallID, "llm_request", payload)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -86,17 +94,16 @@ func (recorder *artifactRecorder) RecordLLMSummary(requestID string, _ string, m
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
session.summaryPayload = cloneStringAnyMap(payload)
|
||||
recorder.mu.Lock()
|
||||
recorder.sessions[artifactSessionKey(requestID, modelCallID)] = session
|
||||
recorder.mu.Unlock()
|
||||
if prefix, ok, err := decodeRequestPrefixPayload(session.requestPayload); err == nil && ok && prefix != nil {
|
||||
if tokens := readInt64Value(session.summaryPayload["prompt_tokens_total"]); tokens > 0 {
|
||||
prefix.PromptTokensTotal = tokens
|
||||
if session.hasRequestPrefix {
|
||||
if tokens := readInt64Value(payload["prompt_tokens_total"]); tokens > 0 {
|
||||
session.requestPrefix.PromptTokensTotal = tokens
|
||||
}
|
||||
recorder.persistLatestRequestPrefix(session.conversationID, requestID, modelCallID, prefix)
|
||||
recorder.mu.Lock()
|
||||
recorder.sessions[artifactSessionKey(requestID, modelCallID)] = session
|
||||
recorder.mu.Unlock()
|
||||
recorder.persistLatestRequestPrefix(session.conversationID, requestID, modelCallID, &session.requestPrefix)
|
||||
}
|
||||
recorder.debug.LogProviderArtifact(context.Background(), requestID, session.conversationID, modelCallID, "llm_summary", session.summaryPayload)
|
||||
recorder.debug.LogProviderArtifact(context.Background(), requestID, session.conversationID, modelCallID, "llm_summary", payload)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,21 +10,6 @@ import (
|
||||
modeladapter "cursor/internal/backend/agent/model"
|
||||
)
|
||||
|
||||
func cloneStringAnyMap(value map[string]any) map[string]any {
|
||||
if len(value) == 0 {
|
||||
return nil
|
||||
}
|
||||
payload, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var decoded map[string]any
|
||||
if err := json.Unmarshal(payload, &decoded); err != nil {
|
||||
return nil
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
func parseRFC3339Time(value string) time.Time {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
|
||||
@@ -25,6 +25,7 @@ func (gateway *DefaultProviderGateway) StartStream(ctx context.Context, req Prov
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
defer releaseArtifactSession(req.Observer, req.RequestID, req.ModelCallID)
|
||||
requestKnobs := make(map[string]any, len(req.RequestKnobs)+2)
|
||||
for key, value := range req.RequestKnobs {
|
||||
requestKnobs[key] = value
|
||||
@@ -60,3 +61,15 @@ func (gateway *DefaultProviderGateway) StartStream(ctx context.Context, req Prov
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type artifactSessionCleaner interface {
|
||||
ClearActiveArtifacts(requestID string, modelCallID string)
|
||||
}
|
||||
|
||||
func releaseArtifactSession(observer modeladapter.LLMArtifactObserver, requestID string, modelCallID string) {
|
||||
cleaner, ok := observer.(artifactSessionCleaner)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
cleaner.ClearActiveArtifacts(requestID, modelCallID)
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ func (s *WindowService) OpenModelEditorWindow(index int, adapterJSON string) {
|
||||
Hidden: false,
|
||||
HideOnEscape: false,
|
||||
MinimiseButtonState: application.ButtonEnabled,
|
||||
MaximiseButtonState: application.ButtonHidden,
|
||||
MaximiseButtonState: application.ButtonEnabled,
|
||||
CloseButtonState: application.ButtonEnabled,
|
||||
BackgroundColour: application.RGBA{Red: 25, Green: 25, Blue: 25, Alpha: 255},
|
||||
Mac: application.MacWindow{
|
||||
|
||||
+10
-5
@@ -1,6 +1,11 @@
|
||||
QQ10群:1049354347
|
||||
----
|
||||
- 支持 GPT 5.6
|
||||
- 修复一些bug
|
||||
|
||||
- 修复内存泄漏问题,该可能导致内存异常占用
|
||||
- 支持俄语增加翻译范围
|
||||
- 修复qwen-3.8-max中断问题(mimo也应该属于同一类问题)
|
||||
- 修复claude模型可能无法识别图片问题 @GGHansome
|
||||
- 支持自定义Openai端点 @Sxuan-Coder
|
||||
- WebSearch 接入百度搜索,DuckDuckGo 作为兜底 @杨超
|
||||
- 修复一些兼容性问题 @kael-odin
|
||||
- 修复window上一些表现问题 @philau2512
|
||||
|
||||
🔔 如何让AI自动拉模型配置? (以下为提示词,把地址和密钥换为你的)
|
||||
我的模型配置在 ~/.cursor-local-assistant-v2/config.yaml,我的API地址是:https://xxx 密钥是xxx,帮我拉所有模型配置进去,不要影响已有模型,根据models标准接口拉取。
|
||||
|
||||
Reference in New Issue
Block a user