feat(cursor): add contributor profile link and improve UI for Cursor account card

- Integrated a tooltip with a button to open the contributor's GitHub profile in the CursorAccountCard component.
- Updated the layout of the account card for better visual organization.
- Removed routing mode options from the configuration view and adjusted related translations in multiple languages.
- Cleaned up unused routing mode logic in the app state management.
This commit is contained in:
leookun
2026-08-02 22:53:57 +08:00
parent 674de0b041
commit a5437e60fe
22 changed files with 293 additions and 753 deletions
+71 -35
View File
@@ -1,6 +1,7 @@
<script setup>
import Button from "@/components/ui/Button.vue";
import Card from "@/components/ui/Card.vue";
import Tooltip from "@/components/ui/Tooltip.vue";
import { showModal } from "@/composables/useModal";
import {
disconnectCursorAccount,
@@ -8,8 +9,11 @@ import {
startCursorAccountLogin,
} from "@/services/clientApi";
import { toUserError } from "@/state/appState";
import { Browser } from "@wailsio/runtime";
import { computed, onMounted, onUnmounted, ref } from "vue";
const CURSOR_ACCOUNT_CONTRIBUTOR_URL = "https://github.com/aike0210";
const cursorAccountStatus = ref({
state: "signed_out",
authId: "",
@@ -38,6 +42,14 @@ async function showActionError(title, error) {
});
}
async function handleOpenContributor() {
try {
await Browser.OpenURL(CURSOR_ACCOUNT_CONTRIBUTOR_URL);
} catch (error) {
await showActionError("打开贡献者主页失败", toUserError(error));
}
}
async function refreshCursorAccountStatus() {
cursorAccountStatus.value = await getCursorAccountStatus();
}
@@ -93,9 +105,9 @@ onUnmounted(() => {
<template>
<Card>
<div class="flex items-center justify-between gap-4">
<div class="min-w-0">
<div class="flex items-center gap-2">
<div class="flex flex-col gap-3">
<div class="flex items-center justify-between gap-4">
<div class="flex min-w-0 flex-wrap items-center gap-2">
<h2 class="text-base font-medium text-white">Cursor 控制面账号</h2>
<span
class="rounded-full border border-[#3a3a3a] bg-[#202020] px-2 py-0.5 text-xs text-[#b8b8b8]"
@@ -103,40 +115,64 @@ onUnmounted(() => {
{{ cursorAccountStateText }}
</span>
</div>
<div
v-if="cursorAccountSignedIn && (cursorAccountStatus.email || cursorAccountStatus.authId)"
class="mt-1 truncate text-sm text-[#d0d0d0]"
>
{{ cursorAccountStatus.email || cursorAccountStatus.authId }}
</div>
<div class="mt-1 text-sm text-[#a3a3a3]">
独立用于插件Skills MCP不会改变 Cursor 客户端当前账号
</div>
<div v-if="cursorAccountWaiting" class="mt-1 text-sm text-[#d6a84b]">
请在浏览器完成登录完成后返回 Cursor 重新打开插件市场
</div>
<div
v-if="cursorAccountStatus.error"
class="mt-1 break-all text-sm text-[#e06c75]"
>
{{ cursorAccountStatus.error }}
<div class="flex shrink-0 items-center gap-1 text-xs text-[#737373]">
<span>@aike0210</span>
<Tooltip>
<div class="flex min-w-[220px] flex-col gap-2">
<div>感谢 @aike0210 Cursor 控制面账号功能的贡献</div>
<button
type="button"
class="flex items-center gap-2 text-left text-[#8ab4f8] transition-colors duration-150 hover:text-[#b6d0fb]"
@click="handleOpenContributor"
>
<span class="icon-[mdi--github] text-[14px]"></span>
<span>github.com/aike0210</span>
<span class="icon-[mdi--open-in-new] text-[12px]"></span>
</button>
</div>
</Tooltip>
</div>
</div>
<Button
v-if="cursorAccountSignedIn"
:disabled="cursorAccountBusy"
@click="handleCursorAccountDisconnect"
>
退出登录
</Button>
<Button
v-else
variant="primary"
:disabled="cursorAccountBusy || cursorAccountWaiting"
@click="handleCursorAccountLogin"
>
{{ cursorAccountWaiting ? "等待登录..." : "登录 Cursor" }}
</Button>
<div class="flex items-end justify-between gap-4">
<div class="min-w-0">
<div
v-if="cursorAccountSignedIn && (cursorAccountStatus.email || cursorAccountStatus.authId)"
class="truncate text-sm text-[#d0d0d0]"
>
{{ cursorAccountStatus.email || cursorAccountStatus.authId }}
</div>
<div class="mt-1 text-sm text-[#a3a3a3]">
独立用于插件Skills MCP不会改变 Cursor 客户端当前账号
</div>
<div v-if="cursorAccountWaiting" class="mt-1 text-sm text-[#d6a84b]">
请在浏览器完成登录完成后返回 Cursor 重新打开插件市场
</div>
<div
v-if="cursorAccountStatus.error"
class="mt-1 break-all text-sm text-[#e06c75]"
>
{{ cursorAccountStatus.error }}
</div>
</div>
<Button
v-if="cursorAccountSignedIn"
class="shrink-0"
:disabled="cursorAccountBusy"
@click="handleCursorAccountDisconnect"
>
退出登录
</Button>
<Button
v-else
class="shrink-0"
variant="primary"
:disabled="cursorAccountBusy || cursorAccountWaiting"
@click="handleCursorAccountLogin"
>
{{ cursorAccountWaiting ? "等待登录..." : "登录 Cursor" }}
</Button>
</div>
</div>
</Card>
</template>
File diff suppressed because it is too large Load Diff
+3 -14
View File
@@ -2,16 +2,14 @@
"02216368edc68816": "No release notes",
"02bc2e95bf49e587": "No",
"03b11112dc970014": "Base URL",
"047ec6b71d0cec08": "Control whether requests on the whitelist main path go through the local service or return to the original Cursor upstream endpoint",
"04f632dd4f034d5e": "{0} context window must be a positive integer",
"051836569928a9f9": "Edit",
"054d763265603305": "e.g. 65536 (leave blank to use the default)",
"05c8a9238c702efa": "Direct Cursor Mode",
"0647728439b5da2e": "You can configure the routing mode and model channels. Runtime logs are stored in",
"092b520558eff5f2": "Not tested",
"09ebc2643631ba25": "Cost Estimate",
"0b0e7478e41fe677": "{0} tooltip text cannot be empty",
"0c3b4cf7aa259edb": "Operation failed",
"0d6b7efd5ccefd8a": "You can configure model channels. Runtime logs are stored in",
"0dde813d719dbd01": "Failed to open homepage",
"1117a2f86030d03b": "Cache reads and writes are included in Prompt-side statistics.",
"11afd2a534395b18": "Valid",
@@ -20,7 +18,6 @@
"15d124b200ddabed": "Maximum number of context tokens the model can accept in a single request. Leave blank to use the default.",
"185aebe19c77425d": "{0} must be a JSON object",
"18b7312022cd1840": "Start Service",
"1af38868896cf53d": "Routing mode only supports local or upstream",
"1baddde657dd2720": "Current outbound requests use system proxy",
"1bc77f5ab979f4c1": "Add Model Settings",
"1c631615c1d85c9e": "Log in to Cursor",
@@ -49,7 +46,6 @@
"393df9bb13ea4900": "Hit",
"3ab8cc15939f3b5c": "Log out",
"3af7e5489e61ea51": "Refreshing",
"3bf8512aa520ed21": "Local Service Mode",
"3c2a9f9901109e75": "{0} type only supports OpenAI or Anthropic",
"3d13868593ae4eeb": "Interface Language",
"3d52574ce1500561": "Not connected",
@@ -70,6 +66,7 @@
"51194c3ad014fb29": "Retest required",
"5205125c0e91d346": "Maximum tokens an Anthropic model may generate in a single response. Leave blank to use the default.",
"56627c94a9decee6": "Max Output Tokens",
"58c6b0935a7216da": "Failed to open contributor profile",
"593a972852ba0004": "Cursor Assistant | Permanently Free | Custom API",
"59a2195a01a8b35b": "{0} must be a valid JSON object",
"5aa8f5590c940829": "Non-cache Input: {0}",
@@ -88,7 +85,6 @@
"6744b4c6a9aa0038": "Disabled",
"675109292da4eb36": "Not tested yet",
"688102a402ba015a": "Waiting for login...",
"699fe7ade5407687": "Direct Mode",
"6a7b96f399e58138": "e.g. sk-xxxxxx",
"6aa8f49cc992dfd7": "Test",
"6ae23d6d7cb18592": "Service error",
@@ -113,10 +109,8 @@
"83be9cac28873059": "Cursor Control Plane Account",
"8672864e90417138": "Max",
"86df7ec743047234": "Service running",
"87ed126f7bd1121e": "Routing Mode",
"899add6275682210": "Uses 200000 by default when left blank",
"8a4ef3e48e4e8a5a": "Enabled",
"8c0d84831a3c3d5b": "Currently in Local Service Mode",
"8c1935935600e336": "Model Test",
"8cbcf741e727dbf7": "Model Settings",
"8d1de152be6360ce": "Valid ratio: {0}",
@@ -138,7 +132,6 @@
"9970736b36ff2b68": "The base URL of the model service, usually an OpenAI- or Anthropic-compatible endpoint.",
"9a6e7d6c17471711": "Currently displayed using the default hit rate definition",
"9b17fa889b307f7f": "Valid turns: {0}",
"9c38b6e9bf94abec": "Switched to Direct Cursor Mode",
"9c41b3a9e12ac994": "Thinking Effort",
"9d2ca261281a158a": "Later",
"9d2fb46c0ba890b9": "Custom Path",
@@ -151,7 +144,6 @@
"a325d25c69e7256d": "Model settings not found; cannot duplicate",
"a4dd8bb7e8b6eb31": "Show API Key",
"a54d745d9a9249e2": "When enabled, the JSON object will override the Anthropic request body. Duplicate fields are determined by this field.",
"a55a88237df85d98": "Currently in Direct Mode",
"a567bdaa11367f26": "Medium",
"a5f1bd344c92e195": "The API key required to call this model service.",
"a693d69af48bfe48": "Save and Test",
@@ -164,7 +156,6 @@
"aed55419ce62f08e": "Switching...",
"b10041a13f5c55b1": "Model Output: {0} × ${1}/1M = {2}",
"b1c27820fec23edb": "High",
"b42049dcf8a05ef7": "Switched to Local Service Mode",
"b5409d4049286061": "Custom Path (Please enter the full request URL)",
"b571037dc396a00c": "Total request tokens include both prompt and model output.",
"b765005f69fa971f": "e.g. gpt-4.1",
@@ -180,7 +171,6 @@
"bef280f9eb392495": "Conversation Turns",
"c228558cf257fc49": "Delete failed",
"c3d46b387eeadb23": "This only logs the Cursor account out of cursor-byok; it does not log out of the Cursor client. Continue?",
"c3e9c3c60020b8b7": "Select Mode",
"c5af02060847d167": "Thinking effort for Anthropic adaptive thinking. Requests will consistently use the new thinking.type=adaptive.",
"c69f5bce63b9f14c": "Settings Folder",
"c8a52b66651d294c": "Failed to log out",
@@ -190,10 +180,8 @@
"ca00a39fcea70dc6": "Starting...",
"ca1d1059408b3837": "Invalid turns: {0}",
"cd7ca5fb221e1c53": "{0} cannot be empty",
"ce46f23cea3bf3c5": "When enabled, Cursor connects directly to the official service. Do not enable this.",
"cfa6c803eb3fc713": "Waiting for browser login",
"d0325067fed88e5a": "Cache hit rate {0}",
"d08fd4224abcd69d": "Switch failed",
"d1bde4a4e057b2c7": "[MainLayout] Failed to load author info",
"d20ab96566d33f25": "{0} display name cannot be empty",
"d2243e1d44b2a94e": "Edit Model Settings",
@@ -215,6 +203,7 @@
"e14c41ef2b7253c9": "Total request tokens: {0}",
"e406825e0a72d2c2": "Local Settings",
"e4343921c928a856": "Login failed",
"e4c0daa3c4bea691": "Thanks to @aike0210 for contributing the Cursor control-plane account feature.",
"e53580f8031f13c0": "Complete login in the browser, then return to Cursor and reopen the plugin marketplace",
"e552c2accdbf5178": "Add Model",
"e6faccfddce722e8": "Cache read tokens: {0}",
+3 -14
View File
@@ -2,16 +2,14 @@
"02216368edc68816": "更新内容はありません",
"02bc2e95bf49e587": "まだ",
"03b11112dc970014": "ベース URL",
"047ec6b71d0cec08": "ホワイトリストのメイン経路のリクエストをローカルサービス経由にするか、元の Cursor 上流アドレスに戻すかを制御します",
"04f632dd4f034d5e": "{0} のコンテキストウィンドウは正の整数である必要があります",
"051836569928a9f9": "編集",
"054d763265603305": "例: 65536(空欄でデフォルト値)",
"05c8a9238c702efa": "Cursor 直結モード",
"0647728439b5da2e": "ルーティングモードとモデルチャネルを設定できます。実行ログは次にあります",
"092b520558eff5f2": "未テスト",
"09ebc2643631ba25": "価値見積もり",
"0b0e7478e41fe677": "{0} のツールチップは必須です",
"0c3b4cf7aa259edb": "操作に失敗しました",
"0d6b7efd5ccefd8a": "モデルチャネルを設定できます。実行ログは次にあります",
"0dde813d719dbd01": "ホームページを開けませんでした",
"1117a2f86030d03b": "キャッシュの読み書きは Prompt 側の統計に含まれます。",
"11afd2a534395b18": "有効",
@@ -20,7 +18,6 @@
"15d124b200ddabed": "モデルが1回のリクエストで受け取れる最大コンテキスト Token 数。空欄の場合はデフォルト値を使用します。",
"185aebe19c77425d": "{0}はJSONオブジェクトである必要があります",
"18b7312022cd1840": "サービスを開始",
"1af38868896cf53d": "ルーティングモードは local または upstream のみサポートします",
"1baddde657dd2720": "現在のアウトバウンドリクエストはシステムプロキシを使用しています",
"1bc77f5ab979f4c1": "モデル設定を追加",
"1c631615c1d85c9e": "Cursor にログイン",
@@ -49,7 +46,6 @@
"393df9bb13ea4900": "ヒット",
"3ab8cc15939f3b5c": "ログアウト",
"3af7e5489e61ea51": "更新中",
"3bf8512aa520ed21": "ローカルサービスモード",
"3c2a9f9901109e75": "{0} のタイプは OpenAI または Anthropic のみサポートします",
"3d13868593ae4eeb": "表示言語",
"3d52574ce1500561": "未接続",
@@ -70,6 +66,7 @@
"51194c3ad014fb29": "再テストが必要",
"5205125c0e91d346": "Anthropic モデルが1回の応答で生成できる最大 Token 数。空欄の場合はデフォルト値を使用します。",
"56627c94a9decee6": "最大出力 Token",
"58c6b0935a7216da": "コントリビューターのプロフィールを開けませんでした",
"593a972852ba0004": "Cursor アシスタント | 永久無料 | カスタム API",
"59a2195a01a8b35b": "{0}は有効なJSONオブジェクトである必要があります",
"5aa8f5590c940829": "非キャッシュ入力:{0}",
@@ -88,7 +85,6 @@
"6744b4c6a9aa0038": "無効化",
"675109292da4eb36": "まだテストしていません",
"688102a402ba015a": "ログインを待っています...",
"699fe7ade5407687": "直結モード",
"6a7b96f399e58138": "例: sk-xxxxxx",
"6aa8f49cc992dfd7": "テスト",
"6ae23d6d7cb18592": "サービスエラー",
@@ -113,10 +109,8 @@
"83be9cac28873059": "Cursor コントロールプレーンアカウント",
"8672864e90417138": "最大",
"86df7ec743047234": "サービス稼働中",
"87ed126f7bd1121e": "ルーティングモード",
"899add6275682210": "空欄で 200000",
"8a4ef3e48e4e8a5a": "有効",
"8c0d84831a3c3d5b": "現在はローカルサービスモードです",
"8c1935935600e336": "モデルテスト",
"8cbcf741e727dbf7": "モデル設定",
"8d1de152be6360ce": "有効率: {0}",
@@ -138,7 +132,6 @@
"9970736b36ff2b68": "モデルサービスの API ルート URL。通常は OpenAI または Anthropic 互換のエンドポイントです。",
"9a6e7d6c17471711": "現在、デフォルトのヒット率定義に従って表示されています",
"9b17fa889b307f7f": "有効ターン: {0}",
"9c38b6e9bf94abec": "Cursor 直結モードに切り替えました",
"9c41b3a9e12ac994": "思考強度",
"9d2ca261281a158a": "後で",
"9d2fb46c0ba890b9": "カスタムパス",
@@ -151,7 +144,6 @@
"a325d25c69e7256d": "モデル設定が存在しないため複製できません",
"a4dd8bb7e8b6eb31": "API キーを表示",
"a54d745d9a9249e2": "有効にすると、JSONオブジェクトがAnthropicのリクエストボディを上書きします。同名のフィールドはこの設定が優先されます。",
"a55a88237df85d98": "現在は直結モードです",
"a567bdaa11367f26": "中",
"a5f1bd344c92e195": "このモデルサービスを呼び出すために必要な API キーです。",
"a693d69af48bfe48": "保存してテスト",
@@ -164,7 +156,6 @@
"aed55419ce62f08e": "切替中...",
"b10041a13f5c55b1": "モデル出力:{0} × ${1}/1M = {2}",
"b1c27820fec23edb": "高",
"b42049dcf8a05ef7": "ローカルサービスモードに切り替えました",
"b5409d4049286061": "カスタムパス(完全なリクエストURLを入力してください)",
"b571037dc396a00c": "総リクエスト Token には Prompt とモデル出力の両方が含まれます。",
"b765005f69fa971f": "例: gpt-4.1",
@@ -180,7 +171,6 @@
"bef280f9eb392495": "会話ターン",
"c228558cf257fc49": "削除に失敗しました",
"c3d46b387eeadb23": "cursor-byok 内の Cursor アカウントからのみログアウトします。Cursor クライアントからはログアウトしません。続行しますか?",
"c3e9c3c60020b8b7": "モードを選択",
"c5af02060847d167": "Anthropic adaptive thinkingの思考強度。リクエストは一貫して新しいthinking.type=adaptiveを使用します。",
"c69f5bce63b9f14c": "設定フォルダー",
"c8a52b66651d294c": "ログアウトに失敗しました",
@@ -190,10 +180,8 @@
"ca00a39fcea70dc6": "起動中...",
"ca1d1059408b3837": "異常ターン: {0}",
"cd7ca5fb221e1c53": "{0}は空にできません",
"ce46f23cea3bf3c5": "有効にすると、Cursor は公式サービスへ直接接続します。オンにしないでください",
"cfa6c803eb3fc713": "ブラウザでのログインを待っています",
"d0325067fed88e5a": "キャッシュヒット率 {0}",
"d08fd4224abcd69d": "切替に失敗しました",
"d1bde4a4e057b2c7": "[MainLayout] 作者情報の読み込みに失敗しました",
"d20ab96566d33f25": "{0} の表示名は必須です",
"d2243e1d44b2a94e": "モデル設定を編集",
@@ -215,6 +203,7 @@
"e14c41ef2b7253c9": "総リクエスト Token: {0}",
"e406825e0a72d2c2": "ローカル設定",
"e4343921c928a856": "ログインに失敗しました",
"e4c0daa3c4bea691": "Cursor コントロールプレーンアカウント機能への @aike0210 の貢献に感謝します。",
"e53580f8031f13c0": "ブラウザでログインを完了し、Cursor に戻ってプラグインマーケットを開き直してください",
"e552c2accdbf5178": "モデルを追加",
"e6faccfddce722e8": "キャッシュ読込 Token: {0}",
+3 -14
View File
@@ -2,16 +2,14 @@
"02216368edc68816": "Нет примечаний к выпуску",
"02bc2e95bf49e587": "Пока нет настроенных",
"03b11112dc970014": "Базовый URL",
"047ec6b71d0cec08": "Определяет, будут ли запросы основного маршрута из белого списка проходить через локальный сервис или исходный сервер Cursor",
"04f632dd4f034d5e": "Размер контекстного окна {0} должен быть положительным целым числом",
"051836569928a9f9": "Изменить",
"054d763265603305": "например, 65536 (оставьте пустым для значения по умолчанию)",
"05c8a9238c702efa": "Прямое подключение к Cursor",
"0647728439b5da2e": "Здесь можно настроить режим маршрутизации и каналы моделей. Журналы выполнения находятся в",
"092b520558eff5f2": "Не проверено",
"09ebc2643631ba25": "Оценка стоимости",
"0b0e7478e41fe677": "Текст подсказки {0} не может быть пустым",
"0c3b4cf7aa259edb": "Не удалось выполнить операцию",
"0d6b7efd5ccefd8a": "Здесь можно настроить каналы моделей. Журналы выполнения находятся в",
"0dde813d719dbd01": "Не удалось открыть домашнюю страницу",
"1117a2f86030d03b": "Чтение и запись кеша включены в статистику Prompt.",
"11afd2a534395b18": "Успешно",
@@ -20,7 +18,6 @@
"15d124b200ddabed": "Максимальное число токенов контекста, которое модель может принять за один запрос. Оставьте поле пустым для значения по умолчанию.",
"185aebe19c77425d": "{0} должен быть объектом JSON",
"18b7312022cd1840": "Запустить сервис",
"1af38868896cf53d": "Режим маршрутизации поддерживает только local или upstream",
"1baddde657dd2720": "Исходящие запросы используют системный прокси",
"1bc77f5ab979f4c1": "Добавить настройки модели",
"1c631615c1d85c9e": "Войти в Cursor",
@@ -49,7 +46,6 @@
"393df9bb13ea4900": "Попадание",
"3ab8cc15939f3b5c": "Выйти",
"3af7e5489e61ea51": "Обновление",
"3bf8512aa520ed21": "Режим локального сервиса",
"3c2a9f9901109e75": "Тип {0} поддерживает только OpenAI или Anthropic",
"3d13868593ae4eeb": "Язык интерфейса",
"3d52574ce1500561": "Не подключено",
@@ -70,6 +66,7 @@
"51194c3ad014fb29": "Требуется повторная проверка",
"5205125c0e91d346": "Максимальное число токенов, которое модель Anthropic может сгенерировать за один ответ. Оставьте поле пустым для значения по умолчанию.",
"56627c94a9decee6": "Макс. выходных токенов",
"58c6b0935a7216da": "Не удалось открыть профиль участника",
"593a972852ba0004": "Cursor Assistant | Всегда бесплатно | Пользовательский API",
"59a2195a01a8b35b": "{0} должен быть допустимым объектом JSON",
"5aa8f5590c940829": "Ввод без кеша: {0}",
@@ -88,7 +85,6 @@
"6744b4c6a9aa0038": "Выключено",
"675109292da4eb36": "Еще не проверено",
"688102a402ba015a": "Ожидание входа...",
"699fe7ade5407687": "Прямой режим",
"6a7b96f399e58138": "например, sk-xxxxxx",
"6aa8f49cc992dfd7": "Проверить",
"6ae23d6d7cb18592": "Ошибка сервиса",
@@ -113,10 +109,8 @@
"83be9cac28873059": "Аккаунт управляющего уровня Cursor",
"8672864e90417138": "Максимальная",
"86df7ec743047234": "Сервис запущен",
"87ed126f7bd1121e": "Режим маршрутизации",
"899add6275682210": "Если оставить пустым, используется 200000",
"8a4ef3e48e4e8a5a": "Включено",
"8c0d84831a3c3d5b": "Сейчас используется режим локального сервиса",
"8c1935935600e336": "Проверка модели",
"8cbcf741e727dbf7": "Настройки модели",
"8d1de152be6360ce": "Доля успешных: {0}",
@@ -138,7 +132,6 @@
"9970736b36ff2b68": "Корневой адрес API сервиса модели, обычно совместимый с OpenAI или Anthropic.",
"9a6e7d6c17471711": "Сейчас используется стандартный расчет доли попаданий",
"9b17fa889b307f7f": "Успешных ходов: {0}",
"9c38b6e9bf94abec": "Включено прямое подключение к Cursor",
"9c41b3a9e12ac994": "Интенсивность рассуждений",
"9d2ca261281a158a": "Позже",
"9d2fb46c0ba890b9": "Пользовательский путь",
@@ -151,7 +144,6 @@
"a325d25c69e7256d": "Настройки модели не найдены; дублирование невозможно",
"a4dd8bb7e8b6eb31": "Показать ключ API",
"a54d745d9a9249e2": "Если включено, объект JSON переопределит тело запроса Anthropic. При совпадении полей используются значения отсюда.",
"a55a88237df85d98": "Сейчас используется прямой режим",
"a567bdaa11367f26": "Средняя",
"a5f1bd344c92e195": "Ключ API, необходимый для обращения к сервису модели.",
"a693d69af48bfe48": "Сохранить и проверить",
@@ -164,7 +156,6 @@
"aed55419ce62f08e": "Переключение...",
"b10041a13f5c55b1": "Вывод модели: {0} × ${1}/1M = {2}",
"b1c27820fec23edb": "Высокая",
"b42049dcf8a05ef7": "Включен режим локального сервиса",
"b5409d4049286061": "Пользовательский путь (введите полный URL запроса)",
"b571037dc396a00c": "Общее число токенов запроса включает Prompt и вывод модели.",
"b765005f69fa971f": "например, gpt-4.1",
@@ -180,7 +171,6 @@
"bef280f9eb392495": "Ходы диалога",
"c228558cf257fc49": "Не удалось удалить",
"c3d46b387eeadb23": "Будет выполнен выход только из аккаунта Cursor в cursor-byok. В клиенте Cursor вы останетесь в системе. Продолжить?",
"c3e9c3c60020b8b7": "Выберите режим",
"c5af02060847d167": "Интенсивность для адаптивных рассуждений Anthropic. В запросах всегда используется новый режим thinking.type=adaptive.",
"c69f5bce63b9f14c": "Папка настроек",
"c8a52b66651d294c": "Не удалось выйти",
@@ -190,10 +180,8 @@
"ca00a39fcea70dc6": "Запуск...",
"ca1d1059408b3837": "Ошибочных ходов: {0}",
"cd7ca5fb221e1c53": "{0} не может быть пустым",
"ce46f23cea3bf3c5": "Если включено, Cursor подключается напрямую к официальному сервису. Не включайте этот режим.",
"cfa6c803eb3fc713": "Ожидание входа в браузере",
"d0325067fed88e5a": "Доля попаданий в кеш: {0}",
"d08fd4224abcd69d": "Не удалось переключить",
"d1bde4a4e057b2c7": "[MainLayout] Не удалось загрузить сведения об авторе",
"d20ab96566d33f25": "Отображаемое имя {0} не может быть пустым",
"d2243e1d44b2a94e": "Изменить настройки модели",
@@ -215,6 +203,7 @@
"e14c41ef2b7253c9": "Всего токенов запроса: {0}",
"e406825e0a72d2c2": "Локальные настройки",
"e4343921c928a856": "Не удалось войти",
"e4c0daa3c4bea691": "Спасибо @aike0210 за вклад в функцию аккаунта панели управления Cursor.",
"e53580f8031f13c0": "Завершите вход в браузере, затем вернитесь в Cursor и снова откройте магазин плагинов",
"e552c2accdbf5178": "Добавить модель",
"e6faccfddce722e8": "Токены чтения из кеша: {0}",
+3 -14
View File
@@ -2,16 +2,14 @@
"02216368edc68816": "无更新说明",
"02bc2e95bf49e587": "当前还没有配置任何",
"03b11112dc970014": "接口地址",
"047ec6b71d0cec08": "控制白名单主链路请求走本地服务,还是回到原始 Cursor 上游地址",
"04f632dd4f034d5e": "{0} 的上下文窗口必须为正整数",
"051836569928a9f9": "编辑",
"054d763265603305": "例如:65536(留空用默认值)",
"05c8a9238c702efa": "直连 Cursor 模式",
"0647728439b5da2e": "可配置运行模式和模型渠道;运行日志位于",
"092b520558eff5f2": "未测试",
"09ebc2643631ba25": "价值估算",
"0b0e7478e41fe677": "{0} 的悬停提示不能为空",
"0c3b4cf7aa259edb": "操作失败",
"0d6b7efd5ccefd8a": "可配置模型渠道;运行日志位于",
"0dde813d719dbd01": "打开主页失败",
"1117a2f86030d03b": "缓存读写已计入 Prompt 侧统计。",
"11afd2a534395b18": "有效",
@@ -20,7 +18,6 @@
"15d124b200ddabed": "模型单次可接受的最大上下文 Token 数。留空时使用默认值。",
"185aebe19c77425d": "{0}必须是 JSON 对象",
"18b7312022cd1840": "启动服务",
"1af38868896cf53d": "运行模式仅支持 local 或 upstream",
"1baddde657dd2720": "当前出站请求使用系统代理",
"1bc77f5ab979f4c1": "新增模型配置",
"1c631615c1d85c9e": "登录 Cursor",
@@ -49,7 +46,6 @@
"393df9bb13ea4900": "命中",
"3ab8cc15939f3b5c": "退出登录",
"3af7e5489e61ea51": "刷新中",
"3bf8512aa520ed21": "本地服务模式",
"3c2a9f9901109e75": "{0} 的类型仅支持 OpenAI 或 Anthropic",
"3d13868593ae4eeb": "界面语言",
"3d52574ce1500561": "未连接",
@@ -70,6 +66,7 @@
"51194c3ad014fb29": "需重测",
"5205125c0e91d346": "Anthropic 模型单次回复允许生成的最大 Token 数。留空时使用默认值。",
"56627c94a9decee6": "最大输出 Token",
"58c6b0935a7216da": "打开贡献者主页失败",
"593a972852ba0004": "Cursor助手|永久免费|自定义API",
"59a2195a01a8b35b": "{0}必须是合法 JSON 对象",
"5aa8f5590c940829": "非缓存输入:{0}",
@@ -88,7 +85,6 @@
"6744b4c6a9aa0038": "已关闭",
"675109292da4eb36": "尚未测试",
"688102a402ba015a": "等待登录...",
"699fe7ade5407687": "直连模式",
"6a7b96f399e58138": "例如:sk-xxxxxx",
"6aa8f49cc992dfd7": "测试",
"6ae23d6d7cb18592": "服务错误",
@@ -113,10 +109,8 @@
"83be9cac28873059": "Cursor 控制面账号",
"8672864e90417138": "最高",
"86df7ec743047234": "服务运行中",
"87ed126f7bd1121e": "运行模式",
"899add6275682210": "留空时默认 200000",
"8a4ef3e48e4e8a5a": "已开启",
"8c0d84831a3c3d5b": "当前为本地服务模式",
"8c1935935600e336": "模型测试",
"8cbcf741e727dbf7": "模型配置",
"8d1de152be6360ce": "有效占比:{0}",
@@ -138,7 +132,6 @@
"9970736b36ff2b68": "模型服务的 API 根地址,通常为兼容 OpenAI 或 Anthropic 的接口入口。",
"9a6e7d6c17471711": "当前按默认命中率口径显示",
"9b17fa889b307f7f": "有效轮次:{0}",
"9c38b6e9bf94abec": "已切换到直连 Cursor 模式",
"9c41b3a9e12ac994": "思考强度",
"9d2ca261281a158a": "稍后",
"9d2fb46c0ba890b9": "自定义路径",
@@ -151,7 +144,6 @@
"a325d25c69e7256d": "模型配置不存在,无法复制",
"a4dd8bb7e8b6eb31": "显示访问密钥",
"a54d745d9a9249e2": "开启后会把 JSON 对象覆盖到 Anthropic 请求体。同名字段以这里为准。",
"a55a88237df85d98": "当前为直连模式",
"a567bdaa11367f26": "中",
"a5f1bd344c92e195": "调用该模型服务需要使用的访问密钥。",
"a693d69af48bfe48": "保存并测试",
@@ -164,7 +156,6 @@
"aed55419ce62f08e": "切换中...",
"b10041a13f5c55b1": "模型输出:{0} × ${1}/1M = {2}",
"b1c27820fec23edb": "高",
"b42049dcf8a05ef7": "已切换到本地服务模式",
"b5409d4049286061": "自定义路径(请输入完整请求地址)",
"b571037dc396a00c": "总请求 Token 包含 Prompt 和模型输出。",
"b765005f69fa971f": "例如:gpt-4.1",
@@ -180,7 +171,6 @@
"bef280f9eb392495": "对话轮次",
"c228558cf257fc49": "删除失败",
"c3d46b387eeadb23": "只会退出 cursor-byok 中的 Cursor 账号,不会退出 Cursor 客户端。是否继续?",
"c3e9c3c60020b8b7": "选择模式",
"c5af02060847d167": "Anthropic adaptive thinking 的思考强度。请求会固定使用新版 thinking.type=adaptive。",
"c69f5bce63b9f14c": "设置文件夹",
"c8a52b66651d294c": "退出登录失败",
@@ -190,10 +180,8 @@
"ca00a39fcea70dc6": "启动中...",
"ca1d1059408b3837": "异常轮次:{0}",
"cd7ca5fb221e1c53": "{0}不能为空",
"ce46f23cea3bf3c5": "开启后,Cursor将直接接通官方,请勿开启",
"cfa6c803eb3fc713": "等待浏览器登录",
"d0325067fed88e5a": "缓存命中率 {0}",
"d08fd4224abcd69d": "切换失败",
"d1bde4a4e057b2c7": "[MainLayout] 加载作者信息失败",
"d20ab96566d33f25": "{0} 的显示名称不能为空",
"d2243e1d44b2a94e": "编辑模型配置",
@@ -215,6 +203,7 @@
"e14c41ef2b7253c9": "总请求:{0}",
"e406825e0a72d2c2": "本地配置",
"e4343921c928a856": "登录失败",
"e4c0daa3c4bea691": "感谢 @aike0210 对 Cursor 控制面账号功能的贡献。",
"e53580f8031f13c0": "请在浏览器完成登录,完成后返回 Cursor 重新打开插件市场",
"e552c2accdbf5178": "新增模型",
"e6faccfddce722e8": "缓存读取:{0}",
-48
View File
@@ -36,7 +36,6 @@ export const EXTRA_PARAMS_DEFAULT_JSON = `{
export const CUSTOM_HEADERS_DEFAULT_JSON = `{
}`;
const SUPPORTED_OPENAI_ENDPOINTS = new Set([OPENAI_ENDPOINT_RESPONSES, OPENAI_ENDPOINT_CHAT_COMPLETIONS, OPENAI_ENDPOINT_CUSTOM]);
const SUPPORTED_ROUTE_MODES = new Set(["local", "upstream"]);
const PROXY_STATE_EVENT = "proxy:state";
const USER_CONFIG_CHANGED_EVENT = "user-config:changed";
const UPDATE_STATE_EVENT = "update:state";
@@ -47,11 +46,6 @@ const MODEL_ADAPTER_TEST_UPDATED_EVENT = "model-adapter-test:updated";
const SUPPORTED_MODEL_ADAPTER_TEST_STATUSES = new Set(["idle", "running", "success", "error"]);
const HOME_METRICS_MIN_LOADING_MS = 600;
export const ROUTE_MODE_OPTIONS = [
{ label: "本地服务模式", value: "local" },
{ label: "直连 Cursor 模式", value: "upstream" },
];
function asString(value) {
if (typeof value === "string") {
return value.trim();
@@ -126,14 +120,6 @@ function formatReleaseDate(value) {
return parsed.format("YYYY-MM-DD HH:mm");
}
function normalizeRouteMode(value, fallback = "local") {
const text = asString(value).toLowerCase();
if (SUPPORTED_ROUTE_MODES.has(text)) {
return text;
}
return fallback;
}
function normalizeBaseURL(value) {
const text = asString(value);
if (!text) {
@@ -479,13 +465,6 @@ export function validateModelAdapters(source) {
return "";
}
function validateConfigPayload(payload) {
if (!SUPPORTED_ROUTE_MODES.has(normalizeRouteMode(payload?.routing?.mode, ""))) {
return "运行模式仅支持 local 或 upstream";
}
return "";
}
function canUseLocalStorage() {
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
}
@@ -531,7 +510,6 @@ function loadCachedState() {
function normalizeConfig(source) {
const raw = source && typeof source === "object" ? source : {};
const routing = raw.routing && typeof raw.routing === "object" ? raw.routing : {};
const homeMetrics = raw.homeMetrics && typeof raw.homeMetrics === "object" ? raw.homeMetrics : {};
return {
log: asBoolean(raw.log),
@@ -539,9 +517,6 @@ function normalizeConfig(source) {
backendListenAddr: asString(raw.configBackendListenAddr) || asString(raw.backendListenAddr),
proxyListenAddr: asString(raw.configProxyListenAddr) || asString(raw.proxyListenAddr),
modelAdapters: normalizeModelAdapters(raw.modelAdapters),
routing: {
mode: normalizeRouteMode(routing.mode),
},
homeMetrics: {
includeCacheWriteInHitRate: asBoolean(homeMetrics.includeCacheWriteInHitRate),
},
@@ -584,7 +559,6 @@ function buildConfigPayload(source = appState) {
backendListenAddr: normalized.backendListenAddr,
proxyListenAddr: normalized.proxyListenAddr,
modelAdapters: normalized.modelAdapters.map(({ id, ...adapter }) => adapter),
routing: normalized.routing,
homeMetrics: normalized.homeMetrics,
lastAgentModelHash: normalized.lastAgentModelHash,
};
@@ -599,7 +573,6 @@ function applyConfigToState(config, { modelAdaptersOnly = false } = {}) {
appState.modelAdapters = normalized.modelAdapters;
appState.configBackendListenAddr = normalized.backendListenAddr;
appState.configProxyListenAddr = normalized.proxyListenAddr;
appState.routingMode = normalized.routing.mode;
appState.includeCacheWriteInHitRate = normalized.homeMetrics.includeCacheWriteInHitRate;
return normalized;
}
@@ -610,13 +583,6 @@ async function loadPersistedUserConfig() {
async function persistConfigPayload(config, { modelAdaptersOnly = false } = {}) {
const payload = buildConfigPayload(config);
const configValidationError = validateConfigPayload(payload);
if (configValidationError) {
return {
ok: false,
error: configValidationError,
};
}
const validationError = validateModelAdapters(payload.modelAdapters);
if (validationError) {
return {
@@ -830,7 +796,6 @@ export const appState = reactive({
modelAdapterTestResults: {},
configBackendListenAddr: cachedConfig.backendListenAddr,
configProxyListenAddr: cachedConfig.proxyListenAddr,
routingMode: cachedConfig.routing.mode,
includeCacheWriteInHitRate: cachedConfig.homeMetrics.includeCacheWriteInHitRate,
serviceRunning: asBoolean(cachedState.serviceRunning),
@@ -1118,9 +1083,6 @@ export async function persistUserConfig() {
return persistConfigPayload({
...currentConfig,
modelAdapters: normalizeModelAdapters(appState.modelAdapters),
routing: {
mode: appState.routingMode,
},
homeMetrics: {
...currentConfig.homeMetrics,
includeCacheWriteInHitRate: appState.includeCacheWriteInHitRate,
@@ -1146,16 +1108,6 @@ export async function saveIncludeCacheWriteInHitRate(value) {
return result;
}
export async function saveRoutingMode(mode) {
const currentConfig = await loadPersistedUserConfig();
return persistConfigPayload({
...currentConfig,
routing: {
mode: normalizeRouteMode(mode),
},
});
}
export async function reloadUserConfig(options = {}) {
const config = await loadPersistedUserConfig();
applyConfigToState(config, options);
+1 -23
View File
@@ -2,20 +2,16 @@
import Button from "@/components/ui/Button.vue";
import Card from "@/components/ui/Card.vue";
import LocaleSelect from "@/components/LocaleSelect.vue";
import Select from "@/components/ui/Select.vue";
import { showModal } from "@/composables/useModal";
import {
appState,
openModelConfigWindow,
persistUserConfig,
reloadUserConfig,
ROUTE_MODE_OPTIONS,
toUserError,
} from "@/state/appState";
import { onMounted } from "vue";
const routeModeOptions = ROUTE_MODE_OPTIONS;
async function showActionError(title, error) {
await showModal({
title,
@@ -55,7 +51,7 @@ onMounted(async () => {
<div>
<h2 class="text-base font-medium text-white">本地配置</h2>
<div class="text-sm text-[#a3a3a3]">
可配置运行模式和模型渠道运行日志位于 <code>~/.cursor-local-assistant-v2/logs/</code>
可配置模型渠道运行日志位于 <code>~/.cursor-local-assistant-v2/logs/</code>
</div>
</div>
<Button variant="primary" :disabled="appState.configSaving" @click="handleSaveConfig">
@@ -64,24 +60,6 @@ onMounted(async () => {
</div>
</Card>
<Card>
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="text-base font-medium text-white">运行模式</h2>
<div class="text-sm text-[#a3a3a3]">
控制白名单主链路请求走本地服务还是回到原始 Cursor 上游地址
</div>
</div>
<div class="w-[220px] max-w-full">
<Select
v-model="appState.routingMode"
:options="routeModeOptions"
placeholder="选择模式"
/>
</div>
</div>
</Card>
<Card>
<div class="flex items-center justify-between gap-4">
<div>
-25
View File
@@ -1,10 +1,8 @@
<script setup>
import Button from "@/components/ui/Button.vue";
import Card from "@/components/ui/Card.vue";
import Switch from "@/components/ui/Switch.vue";
import HomeMetricsCard from "@/components/HomeMetricsCard.vue";
import CursorAccountCard from "@/components/CursorAccountCard.vue";
import { useMessage } from "@/composables/useMessage";
import { showModal } from "@/composables/useModal";
import { getAdRuntime } from "@/services/clientApi";
import {
@@ -12,7 +10,6 @@ import {
appViewState,
openConfigWindow,
openModelConfigWindow,
saveRoutingMode,
syncHomeMetrics,
syncServiceState,
toUserError,
@@ -21,8 +18,6 @@ import {
import { Events } from "@wailsio/runtime";
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
const directModeEnabled = computed(() => appState.routingMode === "upstream");
const message = useMessage();
const AD_UPDATED_EVENT = "ad:updated";
const OPEN_AD_EVENT = "cursor:open-ad";
@@ -128,15 +123,6 @@ async function handleOpenModelConfig() {
}
}
async function handleDirectModeChange(enabled) {
const result = await saveRoutingMode(enabled ? "upstream" : "local");
if (!result.ok) {
await showActionError("切换失败", result.error);
return;
}
message.success(enabled ? "已切换到直连 Cursor 模式" : "已切换到本地服务模式");
}
onMounted(() => {
unsubscribeAdUpdated = Events.On(AD_UPDATED_EVENT, handleAdUpdated);
void syncAdRuntimeQuietly();
@@ -181,17 +167,6 @@ onBeforeUnmount(() => {
class="rounded-[8px] border border-[#4b1d1d] bg-[#2a1313] px-3 py-2 text-sm text-[#fca5a5]">
{{ appState.serviceLastError }}
</div>
<Switch
label="直连模式"
description="开启后,Cursor将直接接通官方,请勿开启"
enabled-text="当前为直连模式"
disabled-text="当前为本地服务模式"
:enabled="directModeEnabled"
:busy="appState.configSaving"
:disabled="appState.configSaving"
@change="handleDirectModeChange"
/>
</div>
</Card>