Compare commits

...
16 Commits
Author SHA1 Message Date
leookun 0f6b0f9484 release: 0.0.41
- 修复内存泄漏问题,该可能导致内存异常占用
- 支持俄语增加翻译范围
- 修复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标准接口拉取。
2026-07-27 00:55:02 +08:00
leookun e401303f64 fix: 修复502导致的内存泄漏问题 2026-07-27 00:15:23 +08:00
leokunandGitHub 799dbda7e0 Merge pull request #178 from kael-odin/fix/mimo-thinking-disable
fix(openai): recognize MiMo in thinking disable
2026-07-16 10:39:50 +08:00
leokunandGitHub f3cbf1e6c3 Merge pull request #179 from kael-odin/fix/anthropic-thinking-disable
fix(anthropic): apply thinking config on override path, symmetric with openai
2026-07-16 10:39:19 +08:00
leokunandGitHub 2380a0d692 Merge pull request #187 from ProtectCookies/main
feat: WebSearch 接入百度搜索,DuckDuckGo 作为兜底
2026-07-16 10:36:01 +08:00
leokunandGitHub 1da1faa4d4 Merge pull request #186 from GGHansome/main
尝试修复使用claude模型无法识别图片问题
2026-07-16 10:35:43 +08:00
leokunandGitHub ce05b0bf7a Merge pull request #205 from philau2512/main
fix(ui): support window resizing and fully localize remaining Chinese texts to EN/JA
2026-07-16 10:35:14 +08:00
philau2512 44a47992d9 feat(build): add PowerShell and batch build scripts for convenient compilation 2026-07-15 17:53:45 +07:00
philau2512 319bd3d7d8 fix(i18n): extract new Chinese keys and implement dynamic localization for footer/updates/tray menu 2026-07-15 17:53:42 +07:00
philau2512 12c7973a06 fix(window): enable maximize button state to support native window resizing on Windows 2026-07-15 17:53:38 +07:00
leokunandGitHub f93b18740d Merge pull request #189 from leookun/release/0.0.40
release: 0.0.40
2026-07-10 11:52:27 +08:00
leokun c6ee26b12d release: 0.0.40 2026-07-10 11:50:02 +08:00
杨超 556ea21f38 feat: WebSearch 接入百度搜索,DuckDuckGo 作为兜底
百度搜索失败或无结果时自动回退到 DuckDuckGo,不引入 Bing。
2026-07-10 10:36:59 +08:00
GGHansome 315aba49c2 fix: claude model can not access image 2026-07-10 03:17:02 +08:00
kael-odin 50ab63de3d fix(anthropic): apply thinking config on override path, symmetric with openai
AnthropicAdapter.Stream built thinking config (buildAnthropicThinkingConfig)
and wrote it into body only inside the `if len(body)==0` normal-construction
block. The RequestBodyOverride branch skipped it entirely — a disabled effort
on the override path left whatever thinking config the override body carried,
violating user intent and diverging from openai.go, where
applyOpenAIThinkingDisable runs unconditionally after both branches.

Also, on the normal path, disabled only wrote thinking:{type:disabled} but
left a stale output_config (set by a prior adaptive turn or by
AnthropicExtraParams) in place — an explicitly disabled request could still
carry output_config.effort=high, a contradictory payload.

Fix:
- Extract applyAnthropicThinkingConfig(body, req), called unconditionally
  after the override/normal block, mirroring openai.go:1860.
- disabled: force thinking:{type:"disabled"}, delete output_config, set
  thinking_disabled_provider_param=thinking.type knob.
- adaptive (AnthropicThinkingEffort non-empty): write
  thinking:{type:adaptive,display:summarized} + output_config.
- empty effort: no-op.
- buildAnthropicThinkingConfig retained for stable-message-count / message
  normalization signals inside the normal block.

Tests: anthropic_thinking_disable_test.go covers all three branches, alias
normalization (off→disabled), disabled overriding existing adaptive config,
and the override-path symmetry scenario.
2026-07-08 09:12:03 +08:00
kael-odin 802daa8fcd fix(openai): recognize MiMo in thinking disable
openAIThinkingDisableKind only matched dashscope/qwen/aliyun,
deepseek/bigmodel/z.ai/zhipu, and gpt-5.1+/gpt-6. Xiaomi MiMo
(api.xiaomimimo.com, model mimo-*) fell through to the default
branch, so when a user set reasoning=disabled the adapter wrote
no disable field and MiMo kept thinking enabled (extra latency +
reasoning token cost contrary to user intent).

Add xiaomimimo/mimo to the thinking_type case — MiMo uses the same
thinking:{type:"disabled"} shape as deepseek/glm, confirmed in
MiMo docs.

- openai.go: +2 conditions in openAIThinkingDisableKind
- openai_thinking_disable_test.go: cover MiMo (base/model/ultraspeed),
  regress deepseek/glm/z.ai/qwen/gpt-5.1/gpt-6/unknown, and verify
  applyOpenAIThinkingDisable writes thinking:{type:disabled} +
  deletes reasoning_effort for MiMo
2026-07-07 18:13:21 +08:00
40 changed files with 2506 additions and 610 deletions
+42
View File
@@ -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."
+3
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + ";$HOME\go\bin"
task build
+1 -1
View File
@@ -8,7 +8,7 @@ info:
description: "Cursor助手" description: "Cursor助手"
copyright: "© 2026, Cursor助手" copyright: "© 2026, Cursor助手"
comments: "Cursor助手" comments: "Cursor助手"
version: "0.0.40" version: "0.0.41"
dev_mode: dev_mode:
root_path: . root_path: .
+2 -2
View File
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.0.39</string> <string>0.0.41</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>0.0.39</string> <string>0.0.41</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>12.0.0</string> <string>12.0.0</string>
<key>LSUIElement</key> <key>LSUIElement</key>
+2 -2
View File
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.0.39</string> <string>0.0.41</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>0.0.39</string> <string>0.0.41</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>12.0.0</string> <string>12.0.0</string>
<key>LSUIElement</key> <key>LSUIElement</key>
+15 -28
View File
@@ -6,7 +6,7 @@
name: "Cursor助手" name: "Cursor助手"
arch: ${GOARCH} arch: ${GOARCH}
platform: "linux" platform: "linux"
version: "0.0.39" version: "0.0.41"
section: "default" section: "default"
priority: "extra" priority: "extra"
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
@@ -24,24 +24,24 @@ contents:
- src: "./build/linux/Cursor助手.desktop" - src: "./build/linux/Cursor助手.desktop"
dst: "/usr/share/applications/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: depends:
- libgtk-4-1 - libgtk-3-0
- libwebkitgtk-6.0-4 - libwebkit2gtk-4.1-0
# Distribution-specific overrides for different package formats # Distribution-specific overrides for different package formats and WebKit versions
overrides: overrides:
# RPM packages for Fedora / RHEL / AlmaLinux / Rocky Linux # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0)
rpm: rpm:
depends: depends:
- gtk4 - gtk3
- webkitgtk6.0 - webkit2gtk4.1
# Arch Linux packages # Arch Linux packages (WebKit 4.1)
archlinux: archlinux:
depends: depends:
- gtk4 - gtk3
- webkitgtk-6.0 - webkit2gtk-4.1
# scripts section to ensure desktop database is updated after install # scripts section to ensure desktop database is updated after install
scripts: scripts:
@@ -50,26 +50,13 @@ scripts:
# preremove: "./build/linux/nfpm/scripts/preremove.sh" # preremove: "./build/linux/nfpm/scripts/preremove.sh"
# postremove: "./build/linux/nfpm/scripts/postremove.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: # replaces:
# - foobar # - foobar
# provides: # provides:
# - bar # - bar
# depends:
# - gtk3
# - libwebkit2gtk
# recommends: # recommends:
# - whatever # - whatever
# suggests: # suggests:
+2 -2
View File
@@ -1,10 +1,10 @@
{ {
"fixed": { "fixed": {
"file_version": "0.0.39" "file_version": "0.0.41"
}, },
"info": { "info": {
"0000": { "0000": {
"ProductVersion": "0.0.39", "ProductVersion": "0.0.41",
"CompanyName": "Cursor助手", "CompanyName": "Cursor助手",
"FileDescription": "Cursor助手", "FileDescription": "Cursor助手",
"LegalCopyright": "© 2026, Cursor助手", "LegalCopyright": "© 2026, Cursor助手",
+12 -37
View File
@@ -14,7 +14,7 @@
!define INFO_PRODUCTNAME "Cursor助手" !define INFO_PRODUCTNAME "Cursor助手"
!endif !endif
!ifndef INFO_PRODUCTVERSION !ifndef INFO_PRODUCTVERSION
!define INFO_PRODUCTVERSION "0.0.39" !define INFO_PRODUCTVERSION "0.0.41"
!endif !endif
!ifndef INFO_COPYRIGHT !ifndef INFO_COPYRIGHT
!define INFO_COPYRIGHT "© 2026, Cursor助手" !define INFO_COPYRIGHT "© 2026, Cursor助手"
@@ -27,16 +27,8 @@
!endif !endif
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" !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 !ifndef REQUEST_EXECUTION_LEVEL
!if "${WAILS_INSTALL_SCOPE}" == "user" !define REQUEST_EXECUTION_LEVEL "admin"
!define REQUEST_EXECUTION_LEVEL "user"
!else
!define REQUEST_EXECUTION_LEVEL "admin"
!endif
!endif !endif
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
@@ -123,40 +115,23 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
WriteUninstaller "$INSTDIR\uninstall.exe" WriteUninstaller "$INSTDIR\uninstall.exe"
SetRegView 64 SetRegView 64
!if "${WAILS_INSTALL_SCOPE}" == "user" WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0 IntFmt $0 "0x%08X" $0
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" WriteRegDWORD HKLM "${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
!macroend !macroend
!macro wails.deleteUninstaller !macro wails.deleteUninstaller
Delete "$INSTDIR\uninstall.exe" Delete "$INSTDIR\uninstall.exe"
SetRegView 64 SetRegView 64
!if "${WAILS_INSTALL_SCOPE}" == "user" DeleteRegKey HKLM "${UNINST_KEY}"
DeleteRegKey HKCU "${UNINST_KEY}"
!else
DeleteRegKey HKLM "${UNINST_KEY}"
!endif
!macroend !macroend
!macro wails.setShellContext !macro wails.setShellContext
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?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"> <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> <dependency>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
+2 -2
View File
@@ -5,8 +5,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build:dev": "node ./scripts/run-vite-build.mjs --minify false --mode development", "build:dev": "node ./scripts/run-vite-build.mjs --scan --minify false --mode development",
"build": "node ./scripts/run-vite-build.mjs --mode production", "build": "node ./scripts/run-vite-build.mjs --scan --mode production",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
+26 -17
View File
@@ -11,7 +11,7 @@ import { normalizePath } from "vite";
const traverse = traverseModule.default ?? traverseModule; const traverse = traverseModule.default ?? traverseModule;
const SOURCE_LANGUAGE = "zh-CN"; 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 HAN_REGEX = /\p{Script=Han}/u;
const JS_HELPERS = { const JS_HELPERS = {
localized: "__i18nLocalized", 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 ast = parseTemplate(templateCode, { comments: true });
const replacements = []; const replacements = [];
const records = []; 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) => { walkTemplateNode(ast, (node, parent) => {
if (node.type === 2 && containsHan(node.content)) { if (node.type === 2 && containsHan(node.content)) {
const canonical = node.content.trim();
const record = buildMessageRecord( const record = buildMessageRecord(
filePath, filePath,
rootDir, rootDir,
node.content.trim(), canonical,
0, 0,
{ resolveLoc(node, node.content.indexOf(canonical)),
line: node.loc.start.line,
column: node.loc.start.column + 1,
},
); );
const replacement = createTextNodeReplacement(node.loc.source, record); const replacement = createTextNodeReplacement(node.loc.source, record);
if (!replacement) { if (!replacement) {
@@ -527,10 +539,7 @@ function transformVueTemplate(templateCode, filePath, rootDir) {
rootDir, rootDir,
node.value.content, node.value.content,
0, 0,
{ resolveLoc(node),
line: node.loc.start.line,
column: node.loc.start.column + 1,
},
); );
records.push(record); records.push(record);
replacements.push({ replacements.push({
@@ -552,10 +561,7 @@ function transformVueTemplate(templateCode, filePath, rootDir) {
node.content, node.content,
filePath, filePath,
rootDir, rootDir,
{ resolveLoc(node),
line: node.loc.start.line,
column: node.loc.start.column + 1,
},
); );
if (!transformed.changed) { if (!transformed.changed) {
return; return;
@@ -585,7 +591,10 @@ function transformVueSFC(code, filePath, rootDir) {
let changed = false; let changed = false;
if (descriptor.template) { 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); records.push(...templateResult.records);
if (templateResult.changed) { if (templateResult.changed) {
changed = true; changed = true;
@@ -651,7 +660,7 @@ function collectCatalogRecords(rootDir) {
return records; return records;
} }
function syncCatalogFiles(rootDir) { export function syncCatalogFiles(rootDir) {
const records = collectCatalogRecords(rootDir); const records = collectCatalogRecords(rootDir);
const catalog = mergeMessageRecords(records); const catalog = mergeMessageRecords(records);
const generatedDir = path.join(rootDir, "src/i18n/generated"); const generatedDir = path.join(rootDir, "src/i18n/generated");
@@ -273,7 +273,6 @@ const hasHomeAd = computed(() => normalizedHomeAds.value.length > 0);
<div <div
class="flex-1 center-row justify-end shrink-0 gap-2 text-xs text-[#6f6f6f] pr-4 w-[200px]" class="flex-1 center-row justify-end shrink-0 gap-2 text-xs text-[#6f6f6f] pr-4 w-[200px]"
> >
<span>刷新统计</span>
<button <button
type="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" 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 -1
View File
@@ -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 LOCALE_STORAGE_SOURCE_KEY = "cursor-client:locale-source:v1";
export const SOURCE_LOCALE = "zh-CN"; export const SOURCE_LOCALE = "zh-CN";
export const DEFAULT_LOCALE = "en-US"; 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 = [ export const LOCALE_OPTIONS = [
{ label: "简体中文", value: "zh-CN" }, { label: "简体中文", value: "zh-CN" },
{ label: "English", value: "en-US" }, { label: "English", value: "en-US" },
{ label: "日本語", value: "ja-JP" }, { label: "日本語", value: "ja-JP" },
{ label: "Русский", value: "ru-RU" },
]; ];
File diff suppressed because it is too large Load Diff
+59 -9
View File
@@ -2,7 +2,6 @@
"02216368edc68816": "No release notes", "02216368edc68816": "No release notes",
"02bc2e95bf49e587": "No", "02bc2e95bf49e587": "No",
"03b11112dc970014": "Base URL", "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", "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", "04f632dd4f034d5e": "{0} context window must be a positive integer",
"051836569928a9f9": "Edit", "051836569928a9f9": "Edit",
@@ -10,34 +9,39 @@
"05c8a9238c702efa": "Direct Cursor Mode", "05c8a9238c702efa": "Direct Cursor Mode",
"0647728439b5da2e": "You can configure the routing mode and model channels. Runtime logs are stored in", "0647728439b5da2e": "You can configure the routing mode and model channels. Runtime logs are stored in",
"092b520558eff5f2": "Not tested", "092b520558eff5f2": "Not tested",
"09ebc2643631ba25": "Cost Estimate",
"0b0e7478e41fe677": "{0} tooltip text cannot be empty", "0b0e7478e41fe677": "{0} tooltip text cannot be empty",
"0c3b4cf7aa259edb": "Operation failed", "0c3b4cf7aa259edb": "Operation failed",
"0dde813d719dbd01": "Failed to open homepage", "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.", "1117a2f86030d03b": "Cache reads and writes are included in Prompt-side statistics.",
"11afd2a534395b18": "Valid", "11afd2a534395b18": "Valid",
"124be3f86f197802": "Token Usage", "124be3f86f197802": "Token Usage",
"13b61c5f697b6700": "Cache Hit Rate", "13b61c5f697b6700": "Cache Hit Rate",
"15d124b200ddabed": "Maximum number of context tokens the model can accept in a single request. Leave blank to use the default.", "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", "18b7312022cd1840": "Start Service",
"1af38868896cf53d": "Routing mode only supports local or upstream", "1af38868896cf53d": "Routing mode only supports local or upstream",
"1baddde657dd2720": "Current outbound requests use system proxy",
"1bc77f5ab979f4c1": "Add Model Settings", "1bc77f5ab979f4c1": "Add Model Settings",
"1e238093b79b3165": "Uses 65536 by default when left blank", "1e238093b79b3165": "Uses 65536 by default when left blank",
"21296ab18ad9af25": "Extra Params JSON",
"24343a2096988d42": "Failed to open", "24343a2096988d42": "Failed to open",
"253d4a3428c648fb": "Cache write tokens: {0}", "253d4a3428c648fb": "Cache write tokens: {0}",
"258e4620e2108793": "Uses 4096 by default when left blank",
"26a3855aed1d8d17": "Service not running", "26a3855aed1d8d17": "Service not running",
"281eb6d08c9960d0": "{0} thinking budget token must be a positive integer", "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.", "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", "2cd0f3be8738a86c": "Cancel",
"2d706f7981b45a7b": "Local settings saved", "2d706f7981b45a7b": "Local settings saved",
"2f9daa828907b93f": "Delete", "2f9daa828907b93f": "Delete",
"30bb57a50caedc38": "e.g. For everyday code completion and Q&A", "30bb57a50caedc38": "e.g. For everyday code completion and Q&A",
"3275d7e70bbdc3d5": "{0}'s {1}",
"32b3c9a50003f77a": "Output tokens are estimated", "32b3c9a50003f77a": "Output tokens are estimated",
"33d2d273e2bd5f88": "Failed to open user guide", "33d2d273e2bd5f88": "Failed to open user guide",
"3463c5585c246df9": "Total: {0}",
"3468b57e3edbc599": "Aggregated from turn summaries scanned from the history.", "3468b57e3edbc599": "Aggregated from turn summaries scanned from the history.",
"35076178fe79a210": "Configuration changed. Please test again.", "35076178fe79a210": "Configuration changed. Please test again.",
"358f07b2c1445ab1": "Author's Message",
"36c149a9b3e8dca0": "models configured yet.", "36c149a9b3e8dca0": "models configured yet.",
"37d23612f78a2e63": "Restart Now to Update", "37d23612f78a2e63": "Restart Now to Update",
"392d0dceb45998d3": "Extreme", "392d0dceb45998d3": "Extreme",
@@ -48,100 +52,137 @@
"3d13868593ae4eeb": "Interface Language", "3d13868593ae4eeb": "Interface Language",
"3ea83f9f55062582": "Release date: {0}", "3ea83f9f55062582": "Release date: {0}",
"3edda85621fd03b2": "model adapters", "3edda85621fd03b2": "model adapters",
"3fd47edce45b3603": "Close",
"42aa8e01e98c0d8c": "Total Duration", "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", "468adaa418ee1475": "e.g. https://api.openai.com/v1",
"472642d58d3d5a6d": "Formula: {0}",
"4923eeb7bd75cccd": "{0} model ID cannot be empty", "4923eeb7bd75cccd": "{0} model ID cannot be empty",
"497c85690c4cc0fc": "No data", "497c85690c4cc0fc": "No data",
"4b8d11bf235e9213": "Current hit rate: {0}", "4c0a929bb86ce912": "Current: {0}",
"4d2b6e53be6002e5": "Cache Statistics Strategy: {0} ({1})",
"4d8c1c5b42830791": "Unknown", "4d8c1c5b42830791": "Unknown",
"4f0982ba1d37e51b": "Current outbound requests use environment variable proxy",
"51194c3ad014fb29": "Retest required", "51194c3ad014fb29": "Retest required",
"5205125c0e91d346": "Maximum tokens an Anthropic model may generate in a single response. Leave blank to use the default.", "5205125c0e91d346": "Maximum tokens an Anthropic model may generate in a single response. Leave blank to use the default.",
"56627c94a9decee6": "Max Output Tokens", "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.", "5beb1206c532729f": "Maximum number of tokens allowed in a single response. Leave blank to use the default.",
"5d1687a4a41883fd": "Stopping...", "5d1687a4a41883fd": "Stopping...",
"5e709712ce012f5d": "Current outbound requests use environment variable proxy: {0}",
"6106f0a12583a334": "Refresh failed", "6106f0a12583a334": "Refresh failed",
"62873083fcaed27d": "Session Statistics", "62873083fcaed27d": "Session Statistics",
"6309a3bb5ba4c714": "Save failed", "6309a3bb5ba4c714": "Save failed",
"636e3deffc1e960a": "Model {0}", "636e3deffc1e960a": "Model {0}",
"63d90d977348ab1f": "Duplicate", "63d90d977348ab1f": "Duplicate",
"64857b4338678314": "The value of custom header {0} must be a string",
"64d2730f2ae37997": "Raw Response", "64d2730f2ae37997": "Raw Response",
"65cc5fd2e6ce6e75": "Backend started, proxy not started", "65cc5fd2e6ce6e75": "Backend started, proxy not started",
"66af574b8948fe83": "{0} API key cannot be empty", "66af574b8948fe83": "{0} API key cannot be empty",
"6744b4c6a9aa0038": "Disabled",
"675109292da4eb36": "Not tested yet", "675109292da4eb36": "Not tested yet",
"699fe7ade5407687": "Direct Mode", "699fe7ade5407687": "Direct Mode",
"6a7b96f399e58138": "e.g. sk-xxxxxx", "6a7b96f399e58138": "e.g. sk-xxxxxx",
"6aa8f49cc992dfd7": "Test", "6aa8f49cc992dfd7": "Test",
"6ae23d6d7cb18592": "Service error", "6ae23d6d7cb18592": "Service error",
"6d7872164df9b36e": "Normal Input: {0} × ${1}/1M = {2}",
"6e584e3d5ce64aa0": "Save Settings", "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}", "737225e2904673fc": "Estimated output tokens: {0}",
"7520bd50a5ee5471": "Stop testing {0}/{1}", "7520bd50a5ee5471": "Stop testing {0}/{1}",
"753d8bb0da9913ce": "Duplication failed", "753d8bb0da9913ce": "Duplication failed",
"774d6e1b7cb89751": "Default Definition",
"77c9e582e85583af": "Test failed", "77c9e582e85583af": "Test failed",
"7a26bf794e9fb6bf": "Used only for display in the UI, so you can distinguish different models.", "7a26bf794e9fb6bf": "Used only for display in the UI, so you can distinguish different models.",
"7b6187c41e88b70c": "Testing...", "7b6187c41e88b70c": "Testing...",
"7bf8e2c07e084d09": "Model Editor", "7bf8e2c07e084d09": "Model Editor",
"7df7641e5e741346": "Cache Read / (Cache Read + Cache Creation + Non-cache Input)",
"7e9e334aeb0bdc07": "Service operation failed", "7e9e334aeb0bdc07": "Service operation failed",
"7f68ebad19ba6bcd": "Check for Updates", "7f68ebad19ba6bcd": "Check for Updates",
"80296f4aa3f4543b": "Cache Read/Write",
"81123c56d5d880d0": "API Key", "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", "8672864e90417138": "Max",
"86df7ec743047234": "Service running", "86df7ec743047234": "Service running",
"87ed126f7bd1121e": "Routing Mode", "87ed126f7bd1121e": "Routing Mode",
"899add6275682210": "Uses 200000 by default when left blank", "899add6275682210": "Uses 200000 by default when left blank",
"8a4ef3e48e4e8a5a": "Enabled",
"8c0d84831a3c3d5b": "Currently in Local Service Mode", "8c0d84831a3c3d5b": "Currently in Local Service Mode",
"8c1935935600e336": "Model Test", "8c1935935600e336": "Model Test",
"8cbcf741e727dbf7": "Model Settings", "8cbcf741e727dbf7": "Model Settings",
"8d1de152be6360ce": "Valid ratio: {0}", "8d1de152be6360ce": "Valid ratio: {0}",
"8e2dc7b0d2e8f6f8": "e.g. OpenAI - GPT-4.1", "8e2dc7b0d2e8f6f8": "e.g. OpenAI - GPT-4.1",
"8f6f8d979c981ced": "Copied", "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", "8faa670b512b6b9b": "Open Model Settings",
"907395551d0f3a93": "{0} OpenAI endpoint only supports /v1/responses, /v1/chat/completions, or a custom path starting with /",
"917b1c1f18d0276b": "Saving...", "917b1c1f18d0276b": "Saving...",
"9196835e388d2550": "Test All", "9196835e388d2550": "Test All",
"91cba5c107a51892": "/ Invalid", "91cba5c107a51892": "/ Invalid",
"92059fe6cd713db4": "The model name actually sent to the server, for example gpt-4.1 or claude-sonnet.", "92059fe6cd713db4": "The model name actually sent to the server, for example gpt-4.1 or claude-sonnet.",
"93e08803675e378b": "Model ID", "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...", "942ff2d88baca0c6": "Checking for updates...",
"970388573a3c88c9": "Cache Read: {0} × ${1}/1M = {2}",
"9730c15f3c1963a1": "Max",
"986678eccf56dc28": "Service status is being updated. Please try again later.", "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.", "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}", "9b17fa889b307f7f": "Valid turns: {0}",
"9c38b6e9bf94abec": "Switched to Direct Cursor Mode", "9c38b6e9bf94abec": "Switched to Direct Cursor Mode",
"9cd4ac17428b86e4": "Cache hit rate = cache read tokens / prompt tokens", "9c41b3a9e12ac994": "Thinking Effort",
"9d2ca261281a158a": "Later", "9d2ca261281a158a": "Later",
"9d2fb46c0ba890b9": "Custom Path",
"9dc0825fba5422e4": "Loading...", "9dc0825fba5422e4": "Loading...",
"9e02529bcaef36c6": "Duplicate model channel detected. Please check the combination of url, modelID, apiKey, displayName, and endpoint",
"a026f37e613cf48b": "Output Tokens", "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}).", "a1a038dfa16c3ede": "You're already on the latest version (v{0}).",
"a3030bf8f16dc63c": "Save", "a3030bf8f16dc63c": "Save",
"a325d25c69e7256d": "Model settings not found; cannot duplicate", "a325d25c69e7256d": "Model settings not found; cannot duplicate",
"a4dd8bb7e8b6eb31": "Show API Key", "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", "a55a88237df85d98": "Currently in Direct Mode",
"a567bdaa11367f26": "Medium", "a567bdaa11367f26": "Medium",
"a5f1bd344c92e195": "The API key required to call this model service.", "a5f1bd344c92e195": "The API key required to call this model service.",
"a693d69af48bfe48": "Save and Test", "a693d69af48bfe48": "Save and Test",
"a98585871c5313ff": "Display Name", "a98585871c5313ff": "Display Name",
"aa9e366f68d3d097": "Low", "aa9e366f68d3d097": "Low",
"ab607d54d86dc789": "Author leookun",
"ac217e4d1ca410f1": "New version available", "ac217e4d1ca410f1": "New version available",
"ad79540418be700a": "Open the settings folder, or manage model settings separately", "ad79540418be700a": "Open the settings folder, or manage model settings separately",
"ae5a738238463a92": "Hide API Key", "ae5a738238463a92": "Hide API Key",
"aed55419ce62f08e": "Switching...", "aed55419ce62f08e": "Switching...",
"b10041a13f5c55b1": "Model Output: {0} × ${1}/1M = {2}",
"b1c27820fec23edb": "High", "b1c27820fec23edb": "High",
"b42049dcf8a05ef7": "Switched to Local Service Mode", "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.", "b571037dc396a00c": "Total request tokens include both prompt and model output.",
"b765005f69fa971f": "e.g. gpt-4.1", "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", "b90a8ac9c488ce46": "Select language",
"ba3c66f90fd11725": "System proxy identified",
"ba40014ff496f64e": "Type", "ba40014ff496f64e": "Type",
"bb074b86a98f6911": "Context Window", "bb074b86a98f6911": "Context Window",
"bbacfde55a92869f": "A higher hit rate means more repeated context is being reused.",
"bc87a4121a0873b3": "Refresh Stats", "bc87a4121a0873b3": "Refresh Stats",
"bd4464ea88d3f24a": "Total turns: {0}", "bd4464ea88d3f24a": "Total turns: {0}",
"bddd504af0c92fd0": "System PAC/automatic proxy detected; current version is handled as a direct connection",
"bef280f9eb392495": "Conversation Turns", "bef280f9eb392495": "Conversation Turns",
"c228558cf257fc49": "Delete failed", "c228558cf257fc49": "Delete failed",
"c3e9c3c60020b8b7": "Select Mode", "c3e9c3c60020b8b7": "Select Mode",
"c5af02060847d167": "Thinking effort for Anthropic adaptive thinking. Requests will consistently use the new thinking.type=adaptive.",
"c69f5bce63b9f14c": "Settings Folder", "c69f5bce63b9f14c": "Settings Folder",
"c6f743953145f40b": "e.g. 4096 (leave blank to use the default)",
"c8c14507b2d37395": "Reasoning Effort", "c8c14507b2d37395": "Reasoning Effort",
"c98e118e0a43f078": "Model", "c98e118e0a43f078": "Model",
"c9dd59beefd7144f": "Cache Read / (Cache Read + Non-cache Input)",
"ca00a39fcea70dc6": "Starting...", "ca00a39fcea70dc6": "Starting...",
"ca1d1059408b3837": "Invalid turns: {0}", "ca1d1059408b3837": "Invalid turns: {0}",
"cd7ca5fb221e1c53": "{0} cannot be empty",
"ce46f23cea3bf3c5": "When enabled, Cursor connects directly to the official service. Do not enable this.", "ce46f23cea3bf3c5": "When enabled, Cursor connects directly to the official service. Do not enable this.",
"d0325067fed88e5a": "Cache hit rate {0}", "d0325067fed88e5a": "Cache hit rate {0}",
"d08fd4224abcd69d": "Switch failed", "d08fd4224abcd69d": "Switch failed",
@@ -151,26 +192,35 @@
"d3209b935ae86797": "Model settings not found; cannot delete", "d3209b935ae86797": "Model settings not found; cannot delete",
"d373809ab86ba93b": "Copy", "d373809ab86ba93b": "Copy",
"d3b1da3088ddd334": "Model test failed", "d3b1da3088ddd334": "Model test failed",
"d53d32f1a1211371": "Custom Headers JSON",
"d7889896c5b7732a": "Anthropic Extra Params JSON",
"d7da2aabd35772ec": "e.g. 200000 (leave blank to use the default)", "d7da2aabd35772ec": "e.g. 200000 (leave blank to use the default)",
"d95e5cb6bdcee553": "Include Cache Creation",
"da590a8fe3ce4de0": "Please select", "da590a8fe3ce4de0": "Please select",
"daede9881787abe7": "Notes", "daede9881787abe7": "Notes",
"dbb4b5be9b5723dc": "{0} reasoning effort only supports low, medium, high, xhigh, and max", "dbb4b5be9b5723dc": "{0} reasoning effort only supports low, medium, high, xhigh, and max",
"dbee6e7139243362": "{0} base URL cannot be empty", "dbee6e7139243362": "{0} base URL cannot be empty",
"dc82c5e8fb2ab777": "Version: v{0}", "dc82c5e8fb2ab777": "Version: v{0}",
"de8184da1ef88d03": "Configured", "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}", "e14c41ef2b7253c9": "Total request tokens: {0}",
"e406825e0a72d2c2": "Local Settings", "e406825e0a72d2c2": "Local Settings",
"e552c2accdbf5178": "Add Model", "e552c2accdbf5178": "Add Model",
"e6faccfddce722e8": "Cache read tokens: {0}", "e6faccfddce722e8": "Cache read tokens: {0}",
"eaffd48cd2ea9f1a": "e.g. https://api.anthropic.com", "eaffd48cd2ea9f1a": "e.g. https://api.anthropic.com",
"eb1be07f2ca6e506": "Estimated based on Claude Opus 4.7 pricing.",
"ec3b17a75db49e24": "{0} t/s | First token {1}", "ec3b17a75db49e24": "{0} t/s | First token {1}",
"ec99e5c45d648fd6": "Update failed", "ec99e5c45d648fd6": "Update failed",
"ee95057c6b0335d2": "Current outbound requests use system proxy: {0}",
"f1e0fc261d42fe29": "Notes shown when hovering over the model list.", "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.", "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", "f3a76d896853c1df": "Miss",
"f3fae6cccb9004b1": "Custom header name cannot be empty",
"f474a4108aba4c4c": "Stop Service", "f474a4108aba4c4c": "Stop Service",
"f4f0ead1116b5b62": "Enable",
"f56c6c82203b33f6": "Notice", "f56c6c82203b33f6": "Notice",
"f61e03f047b786d5": "{0} max output tokens must be a positive integer", "f61e03f047b786d5": "{0} max output tokens must be a positive integer",
"f6e1c8b1a6970db5": "Current outbound requests do not use system proxy",
"fac2a67ad87807c4": "OK", "fac2a67ad87807c4": "OK",
"fb7a4c81729ed0ca": "Stopping...", "fb7a4c81729ed0ca": "Stopping...",
"fec45092945f8790": "User Guide" "fec45092945f8790": "User Guide"
+60 -10
View File
@@ -2,7 +2,6 @@
"02216368edc68816": "更新内容はありません", "02216368edc68816": "更新内容はありません",
"02bc2e95bf49e587": "まだ", "02bc2e95bf49e587": "まだ",
"03b11112dc970014": "ベース URL", "03b11112dc970014": "ベース URL",
"045261cc748d4300": "Anthropic の思考フェーズで使用できる予算 Token 数。空欄の場合はデフォルト値を使用します。",
"047ec6b71d0cec08": "ホワイトリストのメイン経路のリクエストをローカルサービス経由にするか、元の Cursor 上流アドレスに戻すかを制御します", "047ec6b71d0cec08": "ホワイトリストのメイン経路のリクエストをローカルサービス経由にするか、元の Cursor 上流アドレスに戻すかを制御します",
"04f632dd4f034d5e": "{0} のコンテキストウィンドウは正の整数である必要があります", "04f632dd4f034d5e": "{0} のコンテキストウィンドウは正の整数である必要があります",
"051836569928a9f9": "編集", "051836569928a9f9": "編集",
@@ -10,34 +9,39 @@
"05c8a9238c702efa": "Cursor 直結モード", "05c8a9238c702efa": "Cursor 直結モード",
"0647728439b5da2e": "ルーティングモードとモデルチャネルを設定できます。実行ログは次にあります", "0647728439b5da2e": "ルーティングモードとモデルチャネルを設定できます。実行ログは次にあります",
"092b520558eff5f2": "未テスト", "092b520558eff5f2": "未テスト",
"09ebc2643631ba25": "価値見積もり",
"0b0e7478e41fe677": "{0} のツールチップは必須です", "0b0e7478e41fe677": "{0} のツールチップは必須です",
"0c3b4cf7aa259edb": "操作に失敗しました", "0c3b4cf7aa259edb": "操作に失敗しました",
"0dde813d719dbd01": "ホームページを開けませんでした", "0dde813d719dbd01": "ホームページを開けませんでした",
"0e40a09ab4e664ab": "モデルチャネルが重複しています。url、modelID、apiKey、displayName の組み合わせを確認してください",
"1117a2f86030d03b": "キャッシュの読み書きは Prompt 側の統計に含まれます。", "1117a2f86030d03b": "キャッシュの読み書きは Prompt 側の統計に含まれます。",
"11afd2a534395b18": "有効", "11afd2a534395b18": "有効",
"124be3f86f197802": "Token 使用量", "124be3f86f197802": "Token 使用量",
"13b61c5f697b6700": "キャッシュヒット率", "13b61c5f697b6700": "キャッシュヒット率",
"15d124b200ddabed": "モデルが1回のリクエストで受け取れる最大コンテキスト Token 数。空欄の場合はデフォルト値を使用します。", "15d124b200ddabed": "モデルが1回のリクエストで受け取れる最大コンテキスト Token 数。空欄の場合はデフォルト値を使用します。",
"185aebe19c77425d": "{0}はJSONオブジェクトである必要があります",
"18b7312022cd1840": "サービスを開始", "18b7312022cd1840": "サービスを開始",
"1af38868896cf53d": "ルーティングモードは local または upstream のみサポートします", "1af38868896cf53d": "ルーティングモードは local または upstream のみサポートします",
"1baddde657dd2720": "現在のアウトバウンドリクエストはシステムプロキシを使用しています",
"1bc77f5ab979f4c1": "モデル設定を追加", "1bc77f5ab979f4c1": "モデル設定を追加",
"1e238093b79b3165": "空欄で 65536", "1e238093b79b3165": "空欄で 65536",
"21296ab18ad9af25": "追加パラメータ JSON",
"24343a2096988d42": "開けませんでした", "24343a2096988d42": "開けませんでした",
"253d4a3428c648fb": "キャッシュ書き込み Token: {0}", "253d4a3428c648fb": "キャッシュ書き込み Token: {0}",
"258e4620e2108793": "空欄で 4096",
"26a3855aed1d8d17": "サービスは起動していません", "26a3855aed1d8d17": "サービスは起動していません",
"281eb6d08c9960d0": "{0} の思考予算 Token は正の整数である必要があります", "281eb6d08c9960d0": "{0} の思考予算 Token は正の整数である必要があります",
"28aeffc70ceb4267": "この画面の表示言語を切り替えます。設定はすぐに反映され、この端末に保存されます", "28aeffc70ceb4267": "この画面の表示言語を切り替えます。設定はすぐに反映され、この端末に保存されます",
"2caeaec539e78898": "思考予算 Token", "2a24519398684ed5": "ホームページへ",
"2cd0f3be8738a86c": "キャンセル", "2cd0f3be8738a86c": "キャンセル",
"2d706f7981b45a7b": "ローカル設定を保存しました", "2d706f7981b45a7b": "ローカル設定を保存しました",
"2f9daa828907b93f": "削除", "2f9daa828907b93f": "削除",
"30bb57a50caedc38": "例: 日常的なコード補完や Q&A 用", "30bb57a50caedc38": "例: 日常的なコード補完や Q&A 用",
"3275d7e70bbdc3d5": "{0} の {1}",
"32b3c9a50003f77a": "出力 Token は推定値です", "32b3c9a50003f77a": "出力 Token は推定値です",
"33d2d273e2bd5f88": "ユーザーガイドを開けませんでした", "33d2d273e2bd5f88": "ユーザーガイドを開けませんでした",
"3463c5585c246df9": "合計:{0}",
"3468b57e3edbc599": "履歴からスキャンした各ターンの summary を集計しています。", "3468b57e3edbc599": "履歴からスキャンした各ターンの summary を集計しています。",
"35076178fe79a210": "設定が変更されました。再テストしてください", "35076178fe79a210": "設定が変更されました。再テストしてください",
"358f07b2c1445ab1": "著者からのメッセージ",
"36c149a9b3e8dca0": "モデルは設定されていません。", "36c149a9b3e8dca0": "モデルは設定されていません。",
"37d23612f78a2e63": "今すぐ再起動して更新", "37d23612f78a2e63": "今すぐ再起動して更新",
"392d0dceb45998d3": "最高", "392d0dceb45998d3": "最高",
@@ -48,100 +52,137 @@
"3d13868593ae4eeb": "表示言語", "3d13868593ae4eeb": "表示言語",
"3ea83f9f55062582": "公開日時: {0}", "3ea83f9f55062582": "公開日時: {0}",
"3edda85621fd03b2": "件のモデルアダプター", "3edda85621fd03b2": "件のモデルアダプター",
"3fd47edce45b3603": "閉じる",
"42aa8e01e98c0d8c": "総所要時間", "42aa8e01e98c0d8c": "総所要時間",
"438bb77c1ecdfab0": "キャッシュ書き込み:{0} × ${1}/1M = {2}",
"46056425a48ef05b": "現在、再利用率の定義に従って表示されています",
"468adaa418ee1475": "例: https://api.openai.com/v1", "468adaa418ee1475": "例: https://api.openai.com/v1",
"472642d58d3d5a6d": "数式:{0}",
"4923eeb7bd75cccd": "{0} のモデル ID は必須です", "4923eeb7bd75cccd": "{0} のモデル ID は必須です",
"497c85690c4cc0fc": "データなし", "497c85690c4cc0fc": "データなし",
"4b8d11bf235e9213": "現在のヒット率: {0}", "4c0a929bb86ce912": "現在{0}",
"4d2b6e53be6002e5": "キャッシュ統計ポリシー:{0}{1})",
"4d8c1c5b42830791": "不明", "4d8c1c5b42830791": "不明",
"4f0982ba1d37e51b": "現在のアウトバウンドリクエストは環境変数プロキシを使用しています",
"51194c3ad014fb29": "再テストが必要", "51194c3ad014fb29": "再テストが必要",
"5205125c0e91d346": "Anthropic モデルが1回の応答で生成できる最大 Token 数。空欄の場合はデフォルト値を使用します。", "5205125c0e91d346": "Anthropic モデルが1回の応答で生成できる最大 Token 数。空欄の場合はデフォルト値を使用します。",
"56627c94a9decee6": "最大出力 Token", "56627c94a9decee6": "最大出力 Token",
"593a972852ba0004": "Cursor アシスタント | 永久無料 | カスタム API",
"59a2195a01a8b35b": "{0}は有効なJSONオブジェクトである必要があります",
"5aa8f5590c940829": "非キャッシュ入力:{0}",
"5beb1206c532729f": "1回の応答で生成できる最大 Token 数。空欄の場合はデフォルト値を使用します。", "5beb1206c532729f": "1回の応答で生成できる最大 Token 数。空欄の場合はデフォルト値を使用します。",
"5d1687a4a41883fd": "停止中...", "5d1687a4a41883fd": "停止中...",
"5e709712ce012f5d": "現在のアウトバウンドリクエストは環境変数プロキシを使用しています:{0}",
"6106f0a12583a334": "再読み込みに失敗しました", "6106f0a12583a334": "再読み込みに失敗しました",
"62873083fcaed27d": "セッション統計", "62873083fcaed27d": "セッション統計",
"6309a3bb5ba4c714": "保存に失敗しました", "6309a3bb5ba4c714": "保存に失敗しました",
"636e3deffc1e960a": "モデル {0}", "636e3deffc1e960a": "モデル {0}",
"63d90d977348ab1f": "複製", "63d90d977348ab1f": "複製",
"64857b4338678314": "カスタムヘッダー {0} の値は文字列である必要があります",
"64d2730f2ae37997": "生のレスポンス", "64d2730f2ae37997": "生のレスポンス",
"65cc5fd2e6ce6e75": "バックエンドは起動済み、プロキシは未起動です", "65cc5fd2e6ce6e75": "バックエンドは起動済み、プロキシは未起動です",
"66af574b8948fe83": "{0} の API キーは必須です", "66af574b8948fe83": "{0} の API キーは必須です",
"6744b4c6a9aa0038": "無効化",
"675109292da4eb36": "まだテストしていません", "675109292da4eb36": "まだテストしていません",
"699fe7ade5407687": "直結モード", "699fe7ade5407687": "直結モード",
"6a7b96f399e58138": "例: sk-xxxxxx", "6a7b96f399e58138": "例: sk-xxxxxx",
"6aa8f49cc992dfd7": "テスト", "6aa8f49cc992dfd7": "テスト",
"6ae23d6d7cb18592": "サービスエラー", "6ae23d6d7cb18592": "サービスエラー",
"6d7872164df9b36e": "通常入力:{0} × ${1}/1M = {2}",
"6e584e3d5ce64aa0": "設定を保存", "6e584e3d5ce64aa0": "設定を保存",
"6ec87609a8769425": "デフォルト {0} / 作成カウント {1}",
"72f6c3525c0192a7": "有効にすると、キャッシュ作成が分母に含まれます",
"737225e2904673fc": "推定出力 Token: {0}", "737225e2904673fc": "推定出力 Token: {0}",
"7520bd50a5ee5471": "テスト停止 {0}/{1}", "7520bd50a5ee5471": "テスト停止 {0}/{1}",
"753d8bb0da9913ce": "複製に失敗しました", "753d8bb0da9913ce": "複製に失敗しました",
"774d6e1b7cb89751": "デフォルト定義",
"77c9e582e85583af": "テスト失敗", "77c9e582e85583af": "テスト失敗",
"7a26bf794e9fb6bf": "UI 上の表示専用で、異なるモデルを見分けやすくします。", "7a26bf794e9fb6bf": "UI 上の表示専用で、異なるモデルを見分けやすくします。",
"7b6187c41e88b70c": "テスト中...", "7b6187c41e88b70c": "テスト中...",
"7bf8e2c07e084d09": "モデル編集", "7bf8e2c07e084d09": "モデル編集",
"7df7641e5e741346": "キャッシュ読み取り / (キャッシュ読み取り + キャッシュ作成 + 非キャッシュ入力)",
"7e9e334aeb0bdc07": "サービス操作に失敗しました", "7e9e334aeb0bdc07": "サービス操作に失敗しました",
"7f68ebad19ba6bcd": "アップデートを確認", "7f68ebad19ba6bcd": "アップデートを確認",
"80296f4aa3f4543b": "キャッシュ読み書き",
"81123c56d5d880d0": "API キー", "81123c56d5d880d0": "API キー",
"8139cb3dd11f5a67": "有効にすると、JSONオブジェクトが最終的なリクエストヘッダーを上書きします。同名のヘッダーはこの設定が優先され、値は文字列である必要があります。",
"8672864e90417138": "最大", "8672864e90417138": "最大",
"86df7ec743047234": "サービス稼働中", "86df7ec743047234": "サービス稼働中",
"87ed126f7bd1121e": "ルーティングモード", "87ed126f7bd1121e": "ルーティングモード",
"899add6275682210": "空欄で 200000", "899add6275682210": "空欄で 200000",
"8a4ef3e48e4e8a5a": "有効",
"8c0d84831a3c3d5b": "現在はローカルサービスモードです", "8c0d84831a3c3d5b": "現在はローカルサービスモードです",
"8c1935935600e336": "モデルテスト", "8c1935935600e336": "モデルテスト",
"8cbcf741e727dbf7": "モデル設定", "8cbcf741e727dbf7": "モデル設定",
"8d1de152be6360ce": "有効率: {0}", "8d1de152be6360ce": "有効率: {0}",
"8e2dc7b0d2e8f6f8": "例: OpenAI - GPT-4.1", "8e2dc7b0d2e8f6f8": "例: OpenAI - GPT-4.1",
"8f6f8d979c981ced": "コピーしました", "8f6f8d979c981ced": "コピーしました",
"8f8baf5d18dd0492": "有効にすると、JSONオブジェクトがOpenAIのリクエストボディを上書きします。同名のフィールドはこの設定が優先されます。OpenAIのservice_tierはauto、default、flex、scale、priorityをサポートしており、priorityは高優先度/Fastのシナリオで使用できます。",
"8faa670b512b6b9b": "モデル設定を開く", "8faa670b512b6b9b": "モデル設定を開く",
"907395551d0f3a93": "{0} のOpenAIエンドポイントは、/v1/responses、/v1/chat/completions、または / で始まるカスタムパスのみをサポートしています",
"917b1c1f18d0276b": "保存中...", "917b1c1f18d0276b": "保存中...",
"9196835e388d2550": "すべてテスト", "9196835e388d2550": "すべてテスト",
"91cba5c107a51892": "/ 異常", "91cba5c107a51892": "/ 異常",
"92059fe6cd713db4": "実際にサーバーへ送信されるモデル名です。例: gpt-4.1 または claude-sonnet。", "92059fe6cd713db4": "実際にサーバーへ送信されるモデル名です。例: gpt-4.1 または claude-sonnet。",
"93e08803675e378b": "モデル ID", "93e08803675e378b": "モデル ID",
"93faf55cd25c8319": "このソフトウェアは完全に無料です。もし料金を請求された場合は、詐欺の可能性が高いです。\n著者のホームページ https://space.bilibili.com/311706663/upload/video にアクセスして、更新情報や利用方法などを確認してください。",
"942ff2d88baca0c6": "アップデートを確認中...", "942ff2d88baca0c6": "アップデートを確認中...",
"970388573a3c88c9": "キャッシュ読み取り:{0} × ${1}/1M = {2}",
"9730c15f3c1963a1": "最大",
"986678eccf56dc28": "サービス状態を更新中です。しばらくしてからもう一度お試しください", "986678eccf56dc28": "サービス状態を更新中です。しばらくしてからもう一度お試しください",
"991e374fce0f4492": "Cursorアシスタント",
"9970736b36ff2b68": "モデルサービスの API ルート URL。通常は OpenAI または Anthropic 互換のエンドポイントです。", "9970736b36ff2b68": "モデルサービスの API ルート URL。通常は OpenAI または Anthropic 互換のエンドポイントです。",
"9a6e7d6c17471711": "現在、デフォルトのヒット率定義に従って表示されています",
"9b17fa889b307f7f": "有効ターン: {0}", "9b17fa889b307f7f": "有効ターン: {0}",
"9c38b6e9bf94abec": "Cursor 直結モードに切り替えました", "9c38b6e9bf94abec": "Cursor 直結モードに切り替えました",
"9cd4ac17428b86e4": "キャッシュヒット率 = キャッシュ読込 Token / Prompt Token", "9c41b3a9e12ac994": "思考強度",
"9d2ca261281a158a": "後で", "9d2ca261281a158a": "後で",
"9d2fb46c0ba890b9": "カスタムパス",
"9dc0825fba5422e4": "読み込み中...", "9dc0825fba5422e4": "読み込み中...",
"9e02529bcaef36c6": "モデルチャネルが重複しています。url、modelID、apiKey、displayName、endpointの組み合わせを確認してください",
"a026f37e613cf48b": "出力 Token", "a026f37e613cf48b": "出力 Token",
"a1a038dfa16c3ede": "すでに最新バージョンです(v{0})。", "a0d36236c523667c": "{0} のAnthropic思考強度はlow、medium、high、xhigh、maxのみをサポートしています",
"a1a038dfa16c3ede": "すでに最新バージョン(v{0})です。",
"a3030bf8f16dc63c": "保存", "a3030bf8f16dc63c": "保存",
"a325d25c69e7256d": "モデル設定が存在しないため複製できません", "a325d25c69e7256d": "モデル設定が存在しないため複製できません",
"a4dd8bb7e8b6eb31": "API キーを表示", "a4dd8bb7e8b6eb31": "API キーを表示",
"a54d745d9a9249e2": "有効にすると、JSONオブジェクトがAnthropicのリクエストボディを上書きします。同名のフィールドはこの設定が優先されます。",
"a55a88237df85d98": "現在は直結モードです", "a55a88237df85d98": "現在は直結モードです",
"a567bdaa11367f26": "中", "a567bdaa11367f26": "中",
"a5f1bd344c92e195": "このモデルサービスを呼び出すために必要な API キーです。", "a5f1bd344c92e195": "このモデルサービスを呼び出すために必要な API キーです。",
"a693d69af48bfe48": "保存してテスト", "a693d69af48bfe48": "保存してテスト",
"a98585871c5313ff": "表示名", "a98585871c5313ff": "表示名",
"aa9e366f68d3d097": "低", "aa9e366f68d3d097": "低",
"ab607d54d86dc789": "著者 leookun",
"ac217e4d1ca410f1": "新しいバージョンがあります", "ac217e4d1ca410f1": "新しいバージョンがあります",
"ad79540418be700a": "設定フォルダーを開くか、モデル設定を個別に管理できます", "ad79540418be700a": "設定フォルダーを開くか、モデル設定を個別に管理できます",
"ae5a738238463a92": "API キーを隠す", "ae5a738238463a92": "API キーを隠す",
"aed55419ce62f08e": "切替中...", "aed55419ce62f08e": "切替中...",
"b10041a13f5c55b1": "モデル出力:{0} × ${1}/1M = {2}",
"b1c27820fec23edb": "高", "b1c27820fec23edb": "高",
"b42049dcf8a05ef7": "ローカルサービスモードに切り替えました", "b42049dcf8a05ef7": "ローカルサービスモードに切り替えました",
"b5409d4049286061": "カスタムパス(完全なリクエストURLを入力してください)",
"b571037dc396a00c": "総リクエスト Token には Prompt とモデル出力の両方が含まれます。", "b571037dc396a00c": "総リクエスト Token には Prompt とモデル出力の両方が含まれます。",
"b765005f69fa971f": "例: gpt-4.1", "b765005f69fa971f": "例: gpt-4.1",
"b76a22622020f849": "インターフェースプロトコルのエンドポイントを選択します。「カスタムパス」を選択する場合は、APIアドレスバーに完全なリクエストURL/chat/completions または /responses のサフィックスを含む)を入力してください。システムは末尾 of セグメントに基づいてプロトコルタイプを自動的に判断します。",
"b870928f8f9a24c4": "APIエンドポイント",
"b90a8ac9c488ce46": "言語を選択", "b90a8ac9c488ce46": "言語を選択",
"ba3c66f90fd11725": "システムプロキシを認識しました",
"ba40014ff496f64e": "タイプ", "ba40014ff496f64e": "タイプ",
"bb074b86a98f6911": "コンテキストウィンドウ", "bb074b86a98f6911": "コンテキストウィンドウ",
"bbacfde55a92869f": "ヒット率が高いほど、重複するコンテキストがより多く再利用されていることを示します。",
"bc87a4121a0873b3": "統計を更新", "bc87a4121a0873b3": "統計を更新",
"bd4464ea88d3f24a": "総ターン: {0}", "bd4464ea88d3f24a": "総ターン: {0}",
"bddd504af0c92fd0": "システムのPAC/自動プロキシが検出されました。現在のバージョンは直接接続として処理されます",
"bef280f9eb392495": "会話ターン", "bef280f9eb392495": "会話ターン",
"c228558cf257fc49": "削除に失敗しました", "c228558cf257fc49": "削除に失敗しました",
"c3e9c3c60020b8b7": "モードを選択", "c3e9c3c60020b8b7": "モードを選択",
"c5af02060847d167": "Anthropic adaptive thinkingの思考強度。リクエストは一貫して新しいthinking.type=adaptiveを使用します。",
"c69f5bce63b9f14c": "設定フォルダー", "c69f5bce63b9f14c": "設定フォルダー",
"c6f743953145f40b": "例: 4096(空欄でデフォルト値)",
"c8c14507b2d37395": "推論強度", "c8c14507b2d37395": "推論強度",
"c98e118e0a43f078": "モデル", "c98e118e0a43f078": "モデル",
"c9dd59beefd7144f": "キャッシュ読み取り / (キャッシュ読み取り + 非キャッシュ入力)",
"ca00a39fcea70dc6": "起動中...", "ca00a39fcea70dc6": "起動中...",
"ca1d1059408b3837": "異常ターン: {0}", "ca1d1059408b3837": "異常ターン: {0}",
"cd7ca5fb221e1c53": "{0}は空にできません",
"ce46f23cea3bf3c5": "有効にすると、Cursor は公式サービスへ直接接続します。オンにしないでください", "ce46f23cea3bf3c5": "有効にすると、Cursor は公式サービスへ直接接続します。オンにしないでください",
"d0325067fed88e5a": "キャッシュヒット率 {0}", "d0325067fed88e5a": "キャッシュヒット率 {0}",
"d08fd4224abcd69d": "切替に失敗しました", "d08fd4224abcd69d": "切替に失敗しました",
@@ -151,26 +192,35 @@
"d3209b935ae86797": "モデル設定が存在しないため削除できません", "d3209b935ae86797": "モデル設定が存在しないため削除できません",
"d373809ab86ba93b": "コピー", "d373809ab86ba93b": "コピー",
"d3b1da3088ddd334": "モデルテストに失敗しました", "d3b1da3088ddd334": "モデルテストに失敗しました",
"d53d32f1a1211371": "カスタムヘッダー JSON",
"d7889896c5b7732a": "Anthropic 追加パラメータ JSON",
"d7da2aabd35772ec": "例: 200000(空欄でデフォルト値)", "d7da2aabd35772ec": "例: 200000(空欄でデフォルト値)",
"d95e5cb6bdcee553": "キャッシュ作成を含める",
"da590a8fe3ce4de0": "選択してください", "da590a8fe3ce4de0": "選択してください",
"daede9881787abe7": "メモ", "daede9881787abe7": "メモ",
"dbb4b5be9b5723dc": "{0} の推論強度は low、medium、high、xhigh、max のみサポートします", "dbb4b5be9b5723dc": "{0} の推論強度は low、medium、high、xhigh、max のみサポートします",
"dbee6e7139243362": "{0} のベース URL は必須です", "dbee6e7139243362": "{0} のベース URL は必須です",
"dc82c5e8fb2ab777": "バージョン: v{0}", "dc82c5e8fb2ab777": "バージョン: v{0}",
"de8184da1ef88d03": "設定済み", "de8184da1ef88d03": "設定済み",
"e01c5dae36cf8c35": "有効にすると、JSONオブジェクトがOpenAIのリクエストボディを上書きします。同名のフィールドはこの設定が優先されます。OpenAIのservice_tierはauto、default、flex、scale、priorityをサポートしています。",
"e14c41ef2b7253c9": "総リクエスト Token: {0}", "e14c41ef2b7253c9": "総リクエスト Token: {0}",
"e406825e0a72d2c2": "ローカル設定", "e406825e0a72d2c2": "ローカル設定",
"e552c2accdbf5178": "モデルを追加", "e552c2accdbf5178": "モデルを追加",
"e6faccfddce722e8": "キャッシュ読込 Token: {0}", "e6faccfddce722e8": "キャッシュ読込 Token: {0}",
"eaffd48cd2ea9f1a": "例: https://api.anthropic.com", "eaffd48cd2ea9f1a": "例: https://api.anthropic.com",
"eb1be07f2ca6e506": "Claude Opus 4.7の価格に基づいて見積もられます。",
"ec3b17a75db49e24": "{0} t/s | 初回 Token {1}", "ec3b17a75db49e24": "{0} t/s | 初回 Token {1}",
"ec99e5c45d648fd6": "アップデートに失敗しました", "ec99e5c45d648fd6": "アップデートに失敗しました",
"ee95057c6b0335d2": "現在のアウトバウンドリクエストはシステムプロキシを使用しています:{0}",
"f1e0fc261d42fe29": "モデル一覧にホバーしたときに表示されるメモです。", "f1e0fc261d42fe29": "モデル一覧にホバーしたときに表示されるメモです。",
"f363622480699c52": "推論強度は reasoning_effort をサポートする一部のモデルでのみ有効です。すべてのモデルが対応しているわけではありません。値が高いほど安定しやすい反面、遅くなることがあります。", "f363622480699c52": "推論強度は reasoning_effort をサポートする一部のモデルでのみ有効です。すべてのモデルが対応しているわけではありません。値が高いほど安定しやすい反面、遅くなることがあります。",
"f3a76d896853c1df": "ミス", "f3a76d896853c1df": "ミス",
"f3fae6cccb9004b1": "カスタムヘッダー名は空にできません",
"f474a4108aba4c4c": "サービスを停止", "f474a4108aba4c4c": "サービスを停止",
"f4f0ead1116b5b62": "有効化",
"f56c6c82203b33f6": "お知らせ", "f56c6c82203b33f6": "お知らせ",
"f61e03f047b786d5": "{0} の最大出力 Token は正の整数である必要があります", "f61e03f047b786d5": "{0} の最大出力 Token は正の整数である必要があります",
"f6e1c8b1a6970db5": "現在のアウトバウンドリクエストはシステムプロキシを使用していません",
"fac2a67ad87807c4": "OK", "fac2a67ad87807c4": "OK",
"fb7a4c81729ed0ca": "停止中...", "fb7a4c81729ed0ca": "停止中...",
"fec45092945f8790": "ユーザーガイド" "fec45092945f8790": "ユーザーガイド"
+227
View File
@@ -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": "Руководство пользователя"
}
+59 -9
View File
@@ -2,7 +2,6 @@
"02216368edc68816": "无更新说明", "02216368edc68816": "无更新说明",
"02bc2e95bf49e587": "当前还没有配置任何", "02bc2e95bf49e587": "当前还没有配置任何",
"03b11112dc970014": "接口地址", "03b11112dc970014": "接口地址",
"045261cc748d4300": "Anthropic 思考阶段允许消耗的预算 Token 数。留空时使用默认值。",
"047ec6b71d0cec08": "控制白名单主链路请求走本地服务,还是回到原始 Cursor 上游地址", "047ec6b71d0cec08": "控制白名单主链路请求走本地服务,还是回到原始 Cursor 上游地址",
"04f632dd4f034d5e": "{0} 的上下文窗口必须为正整数", "04f632dd4f034d5e": "{0} 的上下文窗口必须为正整数",
"051836569928a9f9": "编辑", "051836569928a9f9": "编辑",
@@ -10,34 +9,39 @@
"05c8a9238c702efa": "直连 Cursor 模式", "05c8a9238c702efa": "直连 Cursor 模式",
"0647728439b5da2e": "可配置运行模式和模型渠道;运行日志位于", "0647728439b5da2e": "可配置运行模式和模型渠道;运行日志位于",
"092b520558eff5f2": "未测试", "092b520558eff5f2": "未测试",
"09ebc2643631ba25": "价值估算",
"0b0e7478e41fe677": "{0} 的悬停提示不能为空", "0b0e7478e41fe677": "{0} 的悬停提示不能为空",
"0c3b4cf7aa259edb": "操作失败", "0c3b4cf7aa259edb": "操作失败",
"0dde813d719dbd01": "打开主页失败", "0dde813d719dbd01": "打开主页失败",
"0e40a09ab4e664ab": "模型渠道重复,请检查 url、modelID、apiKey、displayName 组合",
"1117a2f86030d03b": "缓存读写已计入 Prompt 侧统计。", "1117a2f86030d03b": "缓存读写已计入 Prompt 侧统计。",
"11afd2a534395b18": "有效", "11afd2a534395b18": "有效",
"124be3f86f197802": "Token 消耗", "124be3f86f197802": "Token 消耗",
"13b61c5f697b6700": "缓存命中率", "13b61c5f697b6700": "缓存命中率",
"15d124b200ddabed": "模型单次可接受的最大上下文 Token 数。留空时使用默认值。", "15d124b200ddabed": "模型单次可接受的最大上下文 Token 数。留空时使用默认值。",
"185aebe19c77425d": "{0}必须是 JSON 对象",
"18b7312022cd1840": "启动服务", "18b7312022cd1840": "启动服务",
"1af38868896cf53d": "运行模式仅支持 local 或 upstream", "1af38868896cf53d": "运行模式仅支持 local 或 upstream",
"1baddde657dd2720": "当前出站请求使用系统代理",
"1bc77f5ab979f4c1": "新增模型配置", "1bc77f5ab979f4c1": "新增模型配置",
"1e238093b79b3165": "留空时默认 65536", "1e238093b79b3165": "留空时默认 65536",
"21296ab18ad9af25": "额外参数 JSON",
"24343a2096988d42": "打开失败", "24343a2096988d42": "打开失败",
"253d4a3428c648fb": "缓存写入:{0}", "253d4a3428c648fb": "缓存写入:{0}",
"258e4620e2108793": "留空时默认 4096",
"26a3855aed1d8d17": "服务未启动", "26a3855aed1d8d17": "服务未启动",
"281eb6d08c9960d0": "{0} 的思考预算 Token 必须为正整数", "281eb6d08c9960d0": "{0} 的思考预算 Token 必须为正整数",
"28aeffc70ceb4267": "切换当前界面显示语言,设置会立即生效并保存在本机", "28aeffc70ceb4267": "切换当前界面显示语言,设置会立即生效并保存在本机",
"2caeaec539e78898": "思考预算 Token", "2a24519398684ed5": "访问主页",
"2cd0f3be8738a86c": "取消", "2cd0f3be8738a86c": "取消",
"2d706f7981b45a7b": "本地配置已保存", "2d706f7981b45a7b": "本地配置已保存",
"2f9daa828907b93f": "删除", "2f9daa828907b93f": "删除",
"30bb57a50caedc38": "例如:用于日常代码补全与问答", "30bb57a50caedc38": "例如:用于日常代码补全与问答",
"3275d7e70bbdc3d5": "{0} 的 {1}",
"32b3c9a50003f77a": "输出 Token 为估算值", "32b3c9a50003f77a": "输出 Token 为估算值",
"33d2d273e2bd5f88": "打开使用教程失败", "33d2d273e2bd5f88": "打开使用教程失败",
"3463c5585c246df9": "合计:{0}",
"3468b57e3edbc599": "按历史记录里扫描到的回合 summary 汇总。", "3468b57e3edbc599": "按历史记录里扫描到的回合 summary 汇总。",
"35076178fe79a210": "配置已变更,请重新测试", "35076178fe79a210": "配置已变更,请重新测试",
"358f07b2c1445ab1": "作者寄语",
"36c149a9b3e8dca0": "模型。", "36c149a9b3e8dca0": "模型。",
"37d23612f78a2e63": "立即重启更新", "37d23612f78a2e63": "立即重启更新",
"392d0dceb45998d3": "极高", "392d0dceb45998d3": "极高",
@@ -48,100 +52,137 @@
"3d13868593ae4eeb": "界面语言", "3d13868593ae4eeb": "界面语言",
"3ea83f9f55062582": "发布时间:{0}", "3ea83f9f55062582": "发布时间:{0}",
"3edda85621fd03b2": "个模型适配器", "3edda85621fd03b2": "个模型适配器",
"3fd47edce45b3603": "关闭",
"42aa8e01e98c0d8c": "总耗时", "42aa8e01e98c0d8c": "总耗时",
"438bb77c1ecdfab0": "缓存写入:{0} × ${1}/1M = {2}",
"46056425a48ef05b": "当前按复用率口径显示",
"468adaa418ee1475": "例如:https://api.openai.com/v1", "468adaa418ee1475": "例如:https://api.openai.com/v1",
"472642d58d3d5a6d": "公式:{0}",
"4923eeb7bd75cccd": "{0} 的模型标识不能为空", "4923eeb7bd75cccd": "{0} 的模型标识不能为空",
"497c85690c4cc0fc": "暂无数据", "497c85690c4cc0fc": "暂无数据",
"4b8d11bf235e9213": "当前命中率{0}", "4c0a929bb86ce912": "当前:{0}",
"4d2b6e53be6002e5": "缓存统计策略:{0}{1}",
"4d8c1c5b42830791": "未知", "4d8c1c5b42830791": "未知",
"4f0982ba1d37e51b": "当前出站请求使用环境变量代理",
"51194c3ad014fb29": "需重测", "51194c3ad014fb29": "需重测",
"5205125c0e91d346": "Anthropic 模型单次回复允许生成的最大 Token 数。留空时使用默认值。", "5205125c0e91d346": "Anthropic 模型单次回复允许生成的最大 Token 数。留空时使用默认值。",
"56627c94a9decee6": "最大输出 Token", "56627c94a9decee6": "最大输出 Token",
"593a972852ba0004": "Cursor助手|永久免费|自定义API",
"59a2195a01a8b35b": "{0}必须是合法 JSON 对象",
"5aa8f5590c940829": "非缓存输入:{0}",
"5beb1206c532729f": "单次回复允许生成的最大 Token 数。留空时使用默认值。", "5beb1206c532729f": "单次回复允许生成的最大 Token 数。留空时使用默认值。",
"5d1687a4a41883fd": "停止中...", "5d1687a4a41883fd": "停止中...",
"5e709712ce012f5d": "当前出站请求使用环境变量代理:{0}",
"6106f0a12583a334": "刷新失败", "6106f0a12583a334": "刷新失败",
"62873083fcaed27d": "会话统计", "62873083fcaed27d": "会话统计",
"6309a3bb5ba4c714": "保存失败", "6309a3bb5ba4c714": "保存失败",
"636e3deffc1e960a": "模型 {0}", "636e3deffc1e960a": "模型 {0}",
"63d90d977348ab1f": "复制", "63d90d977348ab1f": "复制",
"64857b4338678314": "自定义请求头 {0} 的值必须是字符串",
"64d2730f2ae37997": "原始返回", "64d2730f2ae37997": "原始返回",
"65cc5fd2e6ce6e75": "后端已启动,代理未启动", "65cc5fd2e6ce6e75": "后端已启动,代理未启动",
"66af574b8948fe83": "{0} 的访问密钥不能为空", "66af574b8948fe83": "{0} 的访问密钥不能为空",
"6744b4c6a9aa0038": "已关闭",
"675109292da4eb36": "尚未测试", "675109292da4eb36": "尚未测试",
"699fe7ade5407687": "直连模式", "699fe7ade5407687": "直连模式",
"6a7b96f399e58138": "例如:sk-xxxxxx", "6a7b96f399e58138": "例如:sk-xxxxxx",
"6aa8f49cc992dfd7": "测试", "6aa8f49cc992dfd7": "测试",
"6ae23d6d7cb18592": "服务错误", "6ae23d6d7cb18592": "服务错误",
"6d7872164df9b36e": "普通输入:{0} × ${1}/1M = {2}",
"6e584e3d5ce64aa0": "保存配置", "6e584e3d5ce64aa0": "保存配置",
"6ec87609a8769425": "默认 {0} / 计入创建 {1}",
"72f6c3525c0192a7": "开启后把缓存创建纳入分母",
"737225e2904673fc": "输出推算:{0}", "737225e2904673fc": "输出推算:{0}",
"7520bd50a5ee5471": "停止测试 {0}/{1}", "7520bd50a5ee5471": "停止测试 {0}/{1}",
"753d8bb0da9913ce": "复制失败", "753d8bb0da9913ce": "复制失败",
"774d6e1b7cb89751": "默认口径",
"77c9e582e85583af": "测试失败", "77c9e582e85583af": "测试失败",
"7a26bf794e9fb6bf": "仅用于界面展示,便于你区分不同模型。", "7a26bf794e9fb6bf": "仅用于界面展示,便于你区分不同模型。",
"7b6187c41e88b70c": "测试中...", "7b6187c41e88b70c": "测试中...",
"7bf8e2c07e084d09": "模型编辑", "7bf8e2c07e084d09": "模型编辑",
"7df7641e5e741346": "缓存读取 /(缓存读取 + 缓存创建 + 非缓存输入)",
"7e9e334aeb0bdc07": "服务操作失败", "7e9e334aeb0bdc07": "服务操作失败",
"7f68ebad19ba6bcd": "检查更新", "7f68ebad19ba6bcd": "检查更新",
"80296f4aa3f4543b": "缓存读写",
"81123c56d5d880d0": "访问密钥", "81123c56d5d880d0": "访问密钥",
"8139cb3dd11f5a67": "开启后会把 JSON 对象覆盖到最终请求头。同名请求头以这里为准,值必须是字符串。",
"8672864e90417138": "最高", "8672864e90417138": "最高",
"86df7ec743047234": "服务运行中", "86df7ec743047234": "服务运行中",
"87ed126f7bd1121e": "运行模式", "87ed126f7bd1121e": "运行模式",
"899add6275682210": "留空时默认 200000", "899add6275682210": "留空时默认 200000",
"8a4ef3e48e4e8a5a": "已开启",
"8c0d84831a3c3d5b": "当前为本地服务模式", "8c0d84831a3c3d5b": "当前为本地服务模式",
"8c1935935600e336": "模型测试", "8c1935935600e336": "模型测试",
"8cbcf741e727dbf7": "模型配置", "8cbcf741e727dbf7": "模型配置",
"8d1de152be6360ce": "有效占比:{0}", "8d1de152be6360ce": "有效占比:{0}",
"8e2dc7b0d2e8f6f8": "例如:OpenAI - GPT-4.1", "8e2dc7b0d2e8f6f8": "例如:OpenAI - GPT-4.1",
"8f6f8d979c981ced": "已复制", "8f6f8d979c981ced": "已复制",
"8f8baf5d18dd0492": "开启后会把 JSON 对象覆盖到 OpenAI 请求体。同名字段以这里为准。OpenAI service_tier 支持 auto、default、flex、scale、prioritypriority 可用于高优先级/Fast 类场景。",
"8faa670b512b6b9b": "打开模型配置", "8faa670b512b6b9b": "打开模型配置",
"907395551d0f3a93": "{0} 的 OpenAI 端点仅支持 /v1/responses、/v1/chat/completions 或以 / 开头的自定义路径",
"917b1c1f18d0276b": "保存中...", "917b1c1f18d0276b": "保存中...",
"9196835e388d2550": "测试全部", "9196835e388d2550": "测试全部",
"91cba5c107a51892": "/ 异常", "91cba5c107a51892": "/ 异常",
"92059fe6cd713db4": "请求实际发送给服务端的模型名称,例如 gpt-4.1 或 claude-sonnet。", "92059fe6cd713db4": "请求实际发送给服务端的模型名称,例如 gpt-4.1 或 claude-sonnet。",
"93e08803675e378b": "模型标识", "93e08803675e378b": "模型标识",
"93faf55cd25c8319": "本软件是纯免费软件,如果你被收费,那大概率就是被骗了。\n欢迎点击访问作者主页 https://space.bilibili.com/311706663/upload/video\n查看更多更新动态、使用分享和后续内容。",
"942ff2d88baca0c6": "检查更新中...", "942ff2d88baca0c6": "检查更新中...",
"970388573a3c88c9": "缓存读取:{0} × ${1}/1M = {2}",
"9730c15f3c1963a1": "最大",
"986678eccf56dc28": "服务状态更新中,请稍后再试", "986678eccf56dc28": "服务状态更新中,请稍后再试",
"991e374fce0f4492": "Cursor助手",
"9970736b36ff2b68": "模型服务的 API 根地址,通常为兼容 OpenAI 或 Anthropic 的接口入口。", "9970736b36ff2b68": "模型服务的 API 根地址,通常为兼容 OpenAI 或 Anthropic 的接口入口。",
"9a6e7d6c17471711": "当前按默认命中率口径显示",
"9b17fa889b307f7f": "有效轮次:{0}", "9b17fa889b307f7f": "有效轮次:{0}",
"9c38b6e9bf94abec": "已切换到直连 Cursor 模式", "9c38b6e9bf94abec": "已切换到直连 Cursor 模式",
"9cd4ac17428b86e4": "缓存命中率 = 缓存读取 Token / Prompt Token", "9c41b3a9e12ac994": "思考强度",
"9d2ca261281a158a": "稍后", "9d2ca261281a158a": "稍后",
"9d2fb46c0ba890b9": "自定义路径",
"9dc0825fba5422e4": "加载中...", "9dc0825fba5422e4": "加载中...",
"9e02529bcaef36c6": "模型渠道重复,请检查 url、modelID、apiKey、displayName、endpoint 组合",
"a026f37e613cf48b": "输出 Token", "a026f37e613cf48b": "输出 Token",
"a0d36236c523667c": "{0} 的 Anthropic 思考强度仅支持 low、medium、high、xhigh、max",
"a1a038dfa16c3ede": "当前已是最新版本(v{0})。", "a1a038dfa16c3ede": "当前已是最新版本(v{0})。",
"a3030bf8f16dc63c": "保存", "a3030bf8f16dc63c": "保存",
"a325d25c69e7256d": "模型配置不存在,无法复制", "a325d25c69e7256d": "模型配置不存在,无法复制",
"a4dd8bb7e8b6eb31": "显示访问密钥", "a4dd8bb7e8b6eb31": "显示访问密钥",
"a54d745d9a9249e2": "开启后会把 JSON 对象覆盖到 Anthropic 请求体。同名字段以这里为准。",
"a55a88237df85d98": "当前为直连模式", "a55a88237df85d98": "当前为直连模式",
"a567bdaa11367f26": "中", "a567bdaa11367f26": "中",
"a5f1bd344c92e195": "调用该模型服务需要使用的访问密钥。", "a5f1bd344c92e195": "调用该模型服务需要使用的访问密钥。",
"a693d69af48bfe48": "保存并测试", "a693d69af48bfe48": "保存并测试",
"a98585871c5313ff": "显示名称", "a98585871c5313ff": "显示名称",
"aa9e366f68d3d097": "低", "aa9e366f68d3d097": "低",
"ab607d54d86dc789": "作者 leookun",
"ac217e4d1ca410f1": "发现新版本", "ac217e4d1ca410f1": "发现新版本",
"ad79540418be700a": "打开设置目录,或单独管理模型配置", "ad79540418be700a": "打开设置目录,或单独管理模型配置",
"ae5a738238463a92": "隐藏访问密钥", "ae5a738238463a92": "隐藏访问密钥",
"aed55419ce62f08e": "切换中...", "aed55419ce62f08e": "切换中...",
"b10041a13f5c55b1": "模型输出:{0} × ${1}/1M = {2}",
"b1c27820fec23edb": "高", "b1c27820fec23edb": "高",
"b42049dcf8a05ef7": "已切换到本地服务模式", "b42049dcf8a05ef7": "已切换到本地服务模式",
"b5409d4049286061": "自定义路径(请输入完整请求地址)",
"b571037dc396a00c": "总请求 Token 包含 Prompt 和模型输出。", "b571037dc396a00c": "总请求 Token 包含 Prompt 和模型输出。",
"b765005f69fa971f": "例如:gpt-4.1", "b765005f69fa971f": "例如:gpt-4.1",
"b76a22622020f849": "选择接口协议端点。选“自定义路径”时,请在接口地址栏填写完整请求地址(含 /chat/completions 或 /responses 路径后缀),系统会根据末段自动判断协议形态。",
"b870928f8f9a24c4": "接口端点",
"b90a8ac9c488ce46": "选择语言", "b90a8ac9c488ce46": "选择语言",
"ba3c66f90fd11725": "已识别系统代理",
"ba40014ff496f64e": "类型", "ba40014ff496f64e": "类型",
"bb074b86a98f6911": "上下文窗口", "bb074b86a98f6911": "上下文窗口",
"bbacfde55a92869f": "命中率越高,说明重复上下文复用得越多。",
"bc87a4121a0873b3": "刷新统计", "bc87a4121a0873b3": "刷新统计",
"bd4464ea88d3f24a": "总轮次:{0}", "bd4464ea88d3f24a": "总轮次:{0}",
"bddd504af0c92fd0": "检测到系统 PAC/自动代理,当前版本按直连处理",
"bef280f9eb392495": "对话轮次", "bef280f9eb392495": "对话轮次",
"c228558cf257fc49": "删除失败", "c228558cf257fc49": "删除失败",
"c3e9c3c60020b8b7": "选择模式", "c3e9c3c60020b8b7": "选择模式",
"c5af02060847d167": "Anthropic adaptive thinking 的思考强度。请求会固定使用新版 thinking.type=adaptive。",
"c69f5bce63b9f14c": "设置文件夹", "c69f5bce63b9f14c": "设置文件夹",
"c6f743953145f40b": "例如:4096(留空用默认值)",
"c8c14507b2d37395": "推理强度", "c8c14507b2d37395": "推理强度",
"c98e118e0a43f078": "模型", "c98e118e0a43f078": "模型",
"c9dd59beefd7144f": "缓存读取 /(缓存读取 + 非缓存输入)",
"ca00a39fcea70dc6": "启动中...", "ca00a39fcea70dc6": "启动中...",
"ca1d1059408b3837": "异常轮次:{0}", "ca1d1059408b3837": "异常轮次:{0}",
"cd7ca5fb221e1c53": "{0}不能为空",
"ce46f23cea3bf3c5": "开启后,Cursor将直接接通官方,请勿开启", "ce46f23cea3bf3c5": "开启后,Cursor将直接接通官方,请勿开启",
"d0325067fed88e5a": "缓存命中率 {0}", "d0325067fed88e5a": "缓存命中率 {0}",
"d08fd4224abcd69d": "切换失败", "d08fd4224abcd69d": "切换失败",
@@ -151,26 +192,35 @@
"d3209b935ae86797": "模型配置不存在,无法删除", "d3209b935ae86797": "模型配置不存在,无法删除",
"d373809ab86ba93b": "拷贝", "d373809ab86ba93b": "拷贝",
"d3b1da3088ddd334": "模型测试失败", "d3b1da3088ddd334": "模型测试失败",
"d53d32f1a1211371": "自定义请求头 JSON",
"d7889896c5b7732a": "Anthropic 额外参数 JSON",
"d7da2aabd35772ec": "例如:200000(留空用默认值)", "d7da2aabd35772ec": "例如:200000(留空用默认值)",
"d95e5cb6bdcee553": "计入缓存创建",
"da590a8fe3ce4de0": "请选择", "da590a8fe3ce4de0": "请选择",
"daede9881787abe7": "备注", "daede9881787abe7": "备注",
"dbb4b5be9b5723dc": "{0} 的推理强度仅支持 low、medium、high、xhigh、max", "dbb4b5be9b5723dc": "{0} 的推理强度仅支持 low、medium、high、xhigh、max",
"dbee6e7139243362": "{0} 的接口地址不能为空", "dbee6e7139243362": "{0} 的接口地址不能为空",
"dc82c5e8fb2ab777": "版本:v{0}", "dc82c5e8fb2ab777": "版本:v{0}",
"de8184da1ef88d03": "已配置", "de8184da1ef88d03": "已配置",
"e01c5dae36cf8c35": "开启后会把 JSON 对象覆盖到 OpenAI 请求体。同名字段以这里为准。OpenAI service_tier 支持 auto、default、flex、scale、priority。",
"e14c41ef2b7253c9": "总请求:{0}", "e14c41ef2b7253c9": "总请求:{0}",
"e406825e0a72d2c2": "本地配置", "e406825e0a72d2c2": "本地配置",
"e552c2accdbf5178": "新增模型", "e552c2accdbf5178": "新增模型",
"e6faccfddce722e8": "缓存读取:{0}", "e6faccfddce722e8": "缓存读取:{0}",
"eaffd48cd2ea9f1a": "例如:https://api.anthropic.com", "eaffd48cd2ea9f1a": "例如:https://api.anthropic.com",
"eb1be07f2ca6e506": "按 Claude Opus 4.7 价格估算。",
"ec3b17a75db49e24": "{0} t/s | 首字 {1}", "ec3b17a75db49e24": "{0} t/s | 首字 {1}",
"ec99e5c45d648fd6": "更新失败", "ec99e5c45d648fd6": "更新失败",
"ee95057c6b0335d2": "当前出站请求使用系统代理:{0}",
"f1e0fc261d42fe29": "模型列表 hover 时显示的备注说明。", "f1e0fc261d42fe29": "模型列表 hover 时显示的备注说明。",
"f363622480699c52": "推理强度仅对部分支持 reasoning_effort 的模型生效,并不是所有模型都支持。越高通常越稳,但也可能更慢。", "f363622480699c52": "推理强度仅对部分支持 reasoning_effort 的模型生效,并不是所有模型都支持。越高通常越稳,但也可能更慢。",
"f3a76d896853c1df": "未命中", "f3a76d896853c1df": "未命中",
"f3fae6cccb9004b1": "自定义请求头名称不能为空",
"f474a4108aba4c4c": "关闭服务", "f474a4108aba4c4c": "关闭服务",
"f4f0ead1116b5b62": "启用",
"f56c6c82203b33f6": "提示", "f56c6c82203b33f6": "提示",
"f61e03f047b786d5": "{0} 的最大输出 Token 必须为正整数", "f61e03f047b786d5": "{0} 的最大输出 Token 必须为正整数",
"f6e1c8b1a6970db5": "当前出站请求未使用系统代理",
"fac2a67ad87807c4": "确定", "fac2a67ad87807c4": "确定",
"fb7a4c81729ed0ca": "关闭中...", "fb7a4c81729ed0ca": "关闭中...",
"fec45092945f8790": "使用教程" "fec45092945f8790": "使用教程"
+6
View File
@@ -1,4 +1,5 @@
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { Events } from "@wailsio/runtime";
import { import {
DEFAULT_LOCALE, DEFAULT_LOCALE,
LOCALE_OPTIONS, LOCALE_OPTIONS,
@@ -10,17 +11,20 @@ import {
import zhCNMessages from "@/i18n/locales/zh-CN.json"; import zhCNMessages from "@/i18n/locales/zh-CN.json";
import enUSMessages from "@/i18n/locales/en-US.json"; import enUSMessages from "@/i18n/locales/en-US.json";
import jaJPMessages from "@/i18n/locales/ja-JP.json"; import jaJPMessages from "@/i18n/locales/ja-JP.json";
import ruRUMessages from "@/i18n/locales/ru-RU.json";
const localeMessages = { const localeMessages = {
"zh-CN": zhCNMessages, "zh-CN": zhCNMessages,
"en-US": enUSMessages, "en-US": enUSMessages,
"ja-JP": jaJPMessages, "ja-JP": jaJPMessages,
"ru-RU": ruRUMessages,
}; };
const languageLocaleMap = { const languageLocaleMap = {
zh: "zh-CN", zh: "zh-CN",
en: "en-US", en: "en-US",
ja: "ja-JP", ja: "ja-JP",
ru: "ru-RU",
}; };
function isSupportedLocale(locale) { function isSupportedLocale(locale) {
@@ -143,6 +147,7 @@ class LocalizedText extends String {
const currentLocale = ref(resolveInitialLocale()); const currentLocale = ref(resolveInitialLocale());
applyLocaleToDocument(currentLocale.value); applyLocaleToDocument(currentLocale.value);
Events.Emit("locale:changed", currentLocale.value);
const localizedCache = new Map(); const localizedCache = new Map();
@@ -155,6 +160,7 @@ export function setLocale(locale) {
currentLocale.value = nextLocale; currentLocale.value = nextLocale;
persistManualLocale(nextLocale); persistManualLocale(nextLocale);
applyLocaleToDocument(nextLocale); applyLocaleToDocument(nextLocale);
Events.Emit("locale:changed", nextLocale);
return nextLocale; return nextLocale;
} }
+18 -7
View File
@@ -25,6 +25,17 @@ const title = computed(() => route.meta.title ?? "Cursor助手|永久免费|
const directlyClose = computed(() => route.meta.directlyClose === true); const directlyClose = computed(() => route.meta.directlyClose === true);
const showFooter = computed(() => route.path === "/"); const showFooter = computed(() => route.path === "/");
const footerAuthorInfo = ref(null); 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"; const usageDocsURL = "https://docs.leokun.cn";
let proxyStateTimer = null; let proxyStateTimer = null;
const proxyStatePollIntervalMs = 10000; const proxyStatePollIntervalMs = 10000;
@@ -106,14 +117,14 @@ async function showActionError(title, error) {
} }
async function handleOpenAuthorHome() { async function handleOpenAuthorHome() {
if (!footerAuthorInfo.value) { if (!localizedAuthorInfo.value) {
return; return;
} }
const confirmed = await showModal({ const confirmed = await showModal({
title: footerAuthorInfo.value.dialogTitle, title: localizedAuthorInfo.value.dialogTitle,
content: footerAuthorInfo.value.dialogContent, content: localizedAuthorInfo.value.dialogContent,
confirmText: footerAuthorInfo.value.dialogConfirmText, confirmText: localizedAuthorInfo.value.dialogConfirmText,
cancelText: footerAuthorInfo.value.dialogCancelText, cancelText: localizedAuthorInfo.value.dialogCancelText,
showCancel: true, showCancel: true,
}); });
if (!confirmed) { if (!confirmed) {
@@ -221,13 +232,13 @@ onUnmounted(() => {
<span>使用教程</span> <span>使用教程</span>
</button> </button>
<button <button
v-if="footerAuthorInfo" v-if="localizedAuthorInfo"
type="button" 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]" 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" @click="handleOpenAuthorHome"
> >
<span class="icon-[ant-design--bilibili-outlined] text-[14px]"></span> <span class="icon-[ant-design--bilibili-outlined] text-[14px]"></span>
<span>{{ footerAuthorInfo.buttonText }}</span> <span>{{ localizedAuthorInfo.buttonText }}</span>
</button> </button>
<div <div
v-if="updateViewState.footerDownloading" v-if="updateViewState.footerDownloading"
+38 -14
View File
@@ -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({ export const updateViewState = reactive({
footerDownloading: computed(() => appState.updateState === "downloading"), footerDownloading: computed(() => appState.updateState === "downloading"),
footerBusy: computed(() => ["checking", "installing"].includes(appState.updateState)), footerBusy: computed(() => ["checking", "installing"].includes(appState.updateState)),
@@ -1016,24 +1039,25 @@ export const updateViewState = reactive({
promptContent: computed(() => { promptContent: computed(() => {
switch (appState.updatePromptKind) { switch (appState.updatePromptKind) {
case "ready": case "ready":
return [ return localizeReadyContent();
`版本:v${appState.updateVersion || appState.appVersion || "..."}`,
`发布时间:${formatReleaseDate(appState.updateReleaseDate)}`,
"",
appState.updateReleaseNotes || "无更新说明",
].join("\n");
case "error": case "error":
return appState.updateError || appState.updateMessage || GENERIC_SERVICE_ERROR; return appState.updateError || localizeUpdateMessage(appState.updateMessage) || GENERIC_SERVICE_ERROR;
default: default:
return appState.updateMessage || `当前已是最新版本(v${appState.appVersion || "..."})。`; return localizeUpdateMessage(appState.updateMessage) || localizeUpdateMessage(`当前已是最新版本(v${appState.appVersion || "..."})。`);
} }
}), }),
promptConfirmText: computed(() => promptConfirmText: computed(() => {
appState.updatePromptKind === "ready" ? "立即重启更新" : "确定", if (appState.updatePromptKind === "ready") {
), return "立即重启更新";
promptCancelText: computed(() => }
appState.updatePromptKind === "ready" ? "稍后" : "取消", return "确定";
), }),
promptCancelText: computed(() => {
if (appState.updatePromptKind === "ready") {
return "稍后";
}
return "取消";
}),
promptShowCancel: computed(() => appState.updatePromptKind === "ready"), promptShowCancel: computed(() => appState.updatePromptKind === "ready"),
}); });
+1 -1
View File
@@ -5,6 +5,7 @@ go 1.25.0
require ( require (
codeberg.org/readeck/go-readability/v2 v2.1.1 codeberg.org/readeck/go-readability/v2 v2.1.1
connectrpc.com/connect v1.19.1 connectrpc.com/connect v1.19.1
github.com/PuerkitoBio/goquery v1.9.2
github.com/denisbrodbeck/machineid v1.0.1 github.com/denisbrodbeck/machineid v1.0.1
github.com/elazarl/goproxy v1.7.2 github.com/elazarl/goproxy v1.7.2
github.com/firecrawl/html-to-markdown v0.0.0-20260312013131-1af9901a5d61 github.com/firecrawl/html-to-markdown v0.0.0-20260312013131-1af9901a5d61
@@ -29,7 +30,6 @@ require (
dario.cat/mergo v1.0.2 // indirect dario.cat/mergo v1.0.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // 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/adrg/xdg v0.5.3 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
+70 -7
View File
@@ -217,7 +217,7 @@ func Run(resources EmbeddedResources) error {
Hidden: false, Hidden: false,
HideOnEscape: false, HideOnEscape: false,
MinimiseButtonState: application.ButtonEnabled, MinimiseButtonState: application.ButtonEnabled,
MaximiseButtonState: application.ButtonHidden, MaximiseButtonState: application.ButtonEnabled,
CloseButtonState: application.ButtonEnabled, CloseButtonState: application.ButtonEnabled,
BackgroundColour: application.RGBA{Red: 25, Green: 25, Blue: 25, Alpha: 255}, BackgroundColour: application.RGBA{Red: 25, Green: 25, Blue: 25, Alpha: 255},
Mac: application.MacWindow{ Mac: application.MacWindow{
@@ -268,37 +268,100 @@ func Run(resources EmbeddedResources) error {
menu.AddSeparator() menu.AddSeparator()
startItem := menu.Add("启动服务") startItem := menu.Add("启动服务")
stopItem := menu.Add("停止服务") stopItem := menu.Add("停止服务")
menu.Add("检查更新").OnClick(func(ctx *application.Context) { updateItem := menu.Add("检查更新").OnClick(func(ctx *application.Context) {
updateManager.CheckNow(true) updateManager.CheckNow(true)
}) })
menu.AddSeparator() menu.AddSeparator()
menu.Add("显示窗口").OnClick(func(ctx *application.Context) { showItem := menu.Add("显示窗口").OnClick(func(ctx *application.Context) {
showMainWindow() showMainWindow()
}) })
menu.Add("隐藏窗口").OnClick(func(ctx *application.Context) { hideItem := menu.Add("隐藏窗口").OnClick(func(ctx *application.Context) {
window.Hide() window.Hide()
}) })
menu.AddSeparator() menu.AddSeparator()
menu.Add("退出").OnClick(func(ctx *application.Context) { quitItem := menu.Add("退出").OnClick(func(ctx *application.Context) {
proxyService.ShutdownForQuit() proxyService.ShutdownForQuit()
app.Quit() 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() { refreshTray := func() {
state := proxyService.GetState() state := proxyService.GetState()
if state.Running { if state.Running {
statusItem.SetLabel("状态:运行中")
startItem.SetEnabled(false) startItem.SetEnabled(false)
stopItem.SetEnabled(true) stopItem.SetEnabled(true)
} else { } else {
statusItem.SetLabel("状态:未启动")
startItem.SetEnabled(true) startItem.SetEnabled(true)
stopItem.SetEnabled(false) stopItem.SetEnabled(false)
} }
updateTrayLabels(currentLocale)
if refreshAdAssetBaseURL() { if refreshAdAssetBaseURL() {
refreshAdRuntime() 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) { app.Event.On("proxy:state", func(event *application.CustomEvent) {
refreshTray() 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) { 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") return nil, "", fmt.Errorf("web search search_term is required")
} }
client := bridge.httpClient client := bridge.httpClient
if client == nil { if client == nil {
client = netproxy.NewHTTPClient(15 * time.Second) 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) requestURL := webSearchURLOverride + neturl.QueryEscape(searchTerm)
request, err := http.NewRequest(http.MethodGet, requestURL, nil) request, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil { if err != nil {
+110 -9
View File
@@ -156,6 +156,18 @@ func anthropicEndpointURL(baseURL string) string {
return base + "/v1/messages" 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 代理。 // ApplyAnthropicCompatibleAuthHeaders 同时兼容 Anthropic 原生 x-api-key 和 Bearer token 代理。
func ApplyAnthropicCompatibleAuthHeaders(httpReq *http.Request, apiKey string) { func ApplyAnthropicCompatibleAuthHeaders(httpReq *http.Request, apiKey string) {
if httpReq == nil { if httpReq == nil {
@@ -215,8 +227,9 @@ func (adapter *AnthropicAdapter) Stream(ctx context.Context, req StreamRequest,
body := cloneRequestBodyOverride(req.RequestBodyOverride) body := cloneRequestBodyOverride(req.RequestBodyOverride)
if len(body) == 0 { if len(body) == 0 {
thinkingConfig := buildAnthropicThinkingConfig(req) thinkingConfig := buildAnthropicThinkingConfig(req)
relocateImages := shouldRelocateAnthropicImages(baseURL)
stableMessageCount := anthropicStableProviderMessageCount(req.Messages, req.StableMessageCount, thinkingConfig != nil) 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 { if err != nil {
return err return err
} }
@@ -261,18 +274,17 @@ func (adapter *AnthropicAdapter) Stream(ctx context.Context, req StreamRequest,
body["tools"] = tools body["tools"] = tools
} }
body["system"] = anthropicProviderSystemBlocks(systemParts) body["system"] = anthropicProviderSystemBlocks(systemParts)
if thinkingConfig != nil {
body["thinking"] = thinkingConfig
if normalizeRuntimeThinkingEffort(req.ThinkingEffort) != "disabled" {
body["output_config"] = buildAnthropicOutputConfig(req)
}
}
frontier := buildAnthropicCacheFrontier(body, stableMessageCount) frontier := buildAnthropicCacheFrontier(body, stableMessageCount)
req.RequestKnobs = annotateAnthropicRequestKnobs(req.RequestKnobs, body, frontier) req.RequestKnobs = annotateAnthropicRequestKnobs(req.RequestKnobs, body, frontier)
body = cloneRequestBodyOverride(body) body = cloneRequestBodyOverride(body)
applyAnthropicCacheBreakpoints(body, frontier.BreakpointPositions) applyAnthropicCacheBreakpoints(body, frontier.BreakpointPositions)
frontier.BreakpointCount = len(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 { if err := ApplyAnthropicExtraParams(body, req.AnthropicExtraParamsEnabled, req.AnthropicExtraParamsJSON); err != nil {
finishedAt = time.Now().UTC() finishedAt = time.Now().UTC()
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "anthropic", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err)) 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 { if len(stableReplayMessages) == 0 {
return 0 return 0
} }
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled) _, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled, false)
if err != nil { if err != nil {
return 0 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)) systemParts := make([]string, 0, len(input))
messages := make([]anthropicMessage, 0, len(input)) messages := make([]anthropicMessage, 0, len(input))
pendingToolResults := make([]map[string]any, 0, 2) pendingToolResults := make([]map[string]any, 0, 2)
@@ -1208,9 +1220,70 @@ func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool) (
} }
} }
flushToolResults() flushToolResults()
if relocateImages {
messages = relocateAnthropicImagesToLastUserMessage(messages)
}
return systemParts, messages, nil 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) { func anthropicProviderContentBlocks(message Message, thinkingEnabled bool) ([]map[string]any, error) {
blocks, err := anthropicContentBlocks(message) blocks, err := anthropicContentBlocks(message)
if err != nil { 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_configdisabled 时会被正确覆盖。
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 { func buildAnthropicOutputConfig(req StreamRequest) map[string]any {
return map[string]any{ return map[string]any{
"effort": anthropicThinkingEffort(req), "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
}
+4 -1
View File
@@ -1901,10 +1901,13 @@ func openAIThinkingDisableKind(baseURL string, modelID string, endpoint string)
strings.Contains(base, "bigmodel") || strings.Contains(base, "bigmodel") ||
strings.Contains(base, "z.ai") || strings.Contains(base, "z.ai") ||
strings.Contains(base, "zhipu") || strings.Contains(base, "zhipu") ||
strings.Contains(base, "xiaomimimo") ||
strings.Contains(base, "mimo") ||
strings.Contains(model, "deepseek") || strings.Contains(model, "deepseek") ||
strings.Contains(model, "glm") || strings.Contains(model, "glm") ||
strings.Contains(model, "zai") || strings.Contains(model, "zai") ||
strings.Contains(model, "zhipu"): strings.Contains(model, "zhipu") ||
strings.Contains(model, "mimo"):
return "thinking_type" return "thinking_type"
case openAIModelSupportsReasoningNone(model): case openAIModelSupportsReasoningNone(model):
return "reasoning_none" return "reasoning_none"
@@ -0,0 +1,84 @@
package modeladapter
import (
"testing"
)
// TestOpenAIThinkingDisableKindMiMo 验证小米 MiMobase_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"])
}
}
+1 -1
View File
@@ -249,7 +249,7 @@ func canMergeProviderAssistantToolCalls(last Message, current Message) bool {
if strings.TrimSpace(last.Role) != "assistant" || strings.TrimSpace(current.Role) != "assistant" { if strings.TrimSpace(last.Role) != "assistant" || strings.TrimSpace(current.Role) != "assistant" {
return false return false
} }
if len(last.ToolCalls) == 0 || len(current.ToolCalls) == 0 { if len(current.ToolCalls) == 0 {
return false return false
} }
if strings.TrimSpace(last.ToolCallID) != "" || strings.TrimSpace(last.Name) != "" { 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)
}
}
+4 -23
View File
@@ -897,8 +897,8 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
reasoningSignature := "" reasoningSignature := ""
pendingToolCalls := make([]ToolCallDescriptor, 0, len(payload.Content)) pendingToolCalls := make([]ToolCallDescriptor, 0, len(payload.Content))
pendingToolResults := make([]Message, 0, len(payload.Content)) pendingToolResults := make([]Message, 0, len(payload.Content))
flushAssistantText := func(forceReasoningOnly bool) { flushAssistantTurn := func() {
if len(textParts) == 0 && (!forceReasoningOnly || len(reasoningParts) == 0) { if len(textParts) == 0 && len(reasoningParts) == 0 && len(pendingToolCalls) == 0 {
return return
} }
messages = append(messages, Message{ messages = append(messages, Message{
@@ -906,23 +906,10 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
Content: formatMessageText(strings.Join(textParts, "")), Content: formatMessageText(strings.Join(textParts, "")),
ReasoningContent: strings.Join(reasoningParts, ""), ReasoningContent: strings.Join(reasoningParts, ""),
ReasoningSignature: reasoningSignature, 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...), ToolCalls: append([]ToolCallDescriptor(nil), pendingToolCalls...),
}) })
messages = append(messages, pendingToolResults...) messages = append(messages, pendingToolResults...)
textParts = textParts[:0]
reasoningParts = reasoningParts[:0] reasoningParts = reasoningParts[:0]
reasoningSignature = "" reasoningSignature = ""
pendingToolCalls = pendingToolCalls[:0] pendingToolCalls = pendingToolCalls[:0]
@@ -932,8 +919,6 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
for _, item := range payload.Content { for _, item := range payload.Content {
switch strings.TrimSpace(item.Type) { switch strings.TrimSpace(item.Type) {
case "reasoning": case "reasoning":
flushToolCalls()
flushAssistantText(false)
if item.Text != "" { if item.Text != "" {
reasoningParts = append(reasoningParts, item.Text) reasoningParts = append(reasoningParts, item.Text)
} }
@@ -941,12 +926,10 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
reasoningSignature = strings.TrimSpace(item.Signature) reasoningSignature = strings.TrimSpace(item.Signature)
} }
case "text": case "text":
flushToolCalls()
if item.Text != "" { if item.Text != "" {
textParts = append(textParts, item.Text) textParts = append(textParts, item.Text)
} }
case "tool-call": case "tool-call":
flushAssistantText(false)
pendingToolCalls = append(pendingToolCalls, ToolCallDescriptor{ pendingToolCalls = append(pendingToolCalls, ToolCallDescriptor{
ID: strings.TrimSpace(item.ToolCallID), ID: strings.TrimSpace(item.ToolCallID),
Index: len(pendingToolCalls), Index: len(pendingToolCalls),
@@ -966,9 +949,7 @@ func buildMessagesFromPendingAssistantRaw(raw string) []Message {
} }
} }
} }
flushAssistantText(false) flushAssistantTurn()
flushToolCalls()
flushAssistantText(true)
return messages 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 -16
View File
@@ -23,10 +23,10 @@ type artifactRecorder struct {
} }
type artifactSession struct { type artifactSession struct {
conversationID string conversationID string
turnSeq int64 turnSeq int64
requestPayload map[string]any requestPrefix requestArtifactPrefix
summaryPayload map[string]any hasRequestPrefix bool
} }
type requestArtifactPrefix struct { type requestArtifactPrefix struct {
@@ -57,14 +57,22 @@ func (recorder *artifactRecorder) RecordLLMRequest(requestID string, _ string, m
if err != nil { if err != nil {
return "", err 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.mu.Lock()
recorder.sessions[artifactSessionKey(requestID, modelCallID)] = session recorder.sessions[artifactSessionKey(requestID, modelCallID)] = session
recorder.mu.Unlock() 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.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 return "", nil
} }
@@ -86,17 +94,16 @@ func (recorder *artifactRecorder) RecordLLMSummary(requestID string, _ string, m
if err != nil { if err != nil {
return "", err return "", err
} }
session.summaryPayload = cloneStringAnyMap(payload) if session.hasRequestPrefix {
recorder.mu.Lock() if tokens := readInt64Value(payload["prompt_tokens_total"]); tokens > 0 {
recorder.sessions[artifactSessionKey(requestID, modelCallID)] = session session.requestPrefix.PromptTokensTotal = tokens
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
} }
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 return "", nil
} }
@@ -10,21 +10,6 @@ import (
modeladapter "cursor/internal/backend/agent/model" 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 { func parseRFC3339Time(value string) time.Time {
trimmed := strings.TrimSpace(value) trimmed := strings.TrimSpace(value)
if trimmed == "" { if trimmed == "" {
+13
View File
@@ -25,6 +25,7 @@ func (gateway *DefaultProviderGateway) StartStream(ctx context.Context, req Prov
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
defer releaseArtifactSession(req.Observer, req.RequestID, req.ModelCallID)
requestKnobs := make(map[string]any, len(req.RequestKnobs)+2) requestKnobs := make(map[string]any, len(req.RequestKnobs)+2)
for key, value := range req.RequestKnobs { for key, value := range req.RequestKnobs {
requestKnobs[key] = value requestKnobs[key] = value
@@ -60,3 +61,15 @@ func (gateway *DefaultProviderGateway) StartStream(ctx context.Context, req Prov
} }
return nil 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)
}
+1 -1
View File
@@ -183,7 +183,7 @@ func (s *WindowService) OpenModelEditorWindow(index int, adapterJSON string) {
Hidden: false, Hidden: false,
HideOnEscape: false, HideOnEscape: false,
MinimiseButtonState: application.ButtonEnabled, MinimiseButtonState: application.ButtonEnabled,
MaximiseButtonState: application.ButtonHidden, MaximiseButtonState: application.ButtonEnabled,
CloseButtonState: application.ButtonEnabled, CloseButtonState: application.ButtonEnabled,
BackgroundColour: application.RGBA{Red: 25, Green: 25, Blue: 25, Alpha: 255}, BackgroundColour: application.RGBA{Red: 25, Green: 25, Blue: 25, Alpha: 255},
Mac: application.MacWindow{ Mac: application.MacWindow{
+10 -5
View File
@@ -1,6 +1,11 @@
QQ10群:1049354347 - 修复内存泄漏问题,该可能导致内存异常占用
---- - 支持俄语增加翻译范围
- 支持 GPT 5.6 - 修复qwen-3.8-max中断问题(mimo也应该属于同一类问题)
- 修复一些bug - 修复claude模型可能无法识别图片问题 @GGHansome
- 支持自定义Openai端点 @Sxuan-Coder
- WebSearch 接入百度搜索,DuckDuckGo 作为兜底 @杨超
- 修复一些兼容性问题 @kael-odin
- 修复window上一些表现问题 @philau2512
🔔 如何让AI自动拉模型配置? (以下为提示词,把地址和密钥换为你的)
我的模型配置在 /.cursor-local-assistant-v2/config.yaml,我的API地址是:https://xxx 密钥是xxx,帮我拉所有模型配置进去,不要影响已有模型,根据models标准接口拉取。