mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-04 02:52:55 +08:00
plugin runtime
This commit is contained in:
Generated
+22
@@ -1248,6 +1248,7 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
"x509-parser",
|
"x509-parser",
|
||||||
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1929,6 +1930,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
|
"zlib-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9079,16 +9081,36 @@ checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
|
"flate2",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"zopfli",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zlib-rs"
|
||||||
|
version = "0.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zopfli"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"crc32fast",
|
||||||
|
"log",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.13.3"
|
version = "0.13.3"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { CallsPage } from "./features/calls/CallsPage";
|
|||||||
import { CallDetailsPage } from "./features/calls/CallDetailsPage";
|
import { CallDetailsPage } from "./features/calls/CallDetailsPage";
|
||||||
import { CursorSettingsPage } from "./features/models/CursorSettingsPage";
|
import { CursorSettingsPage } from "./features/models/CursorSettingsPage";
|
||||||
import { HomePage } from "./features/home/HomePage";
|
import { HomePage } from "./features/home/HomePage";
|
||||||
|
import { PluginManagementPage } from "./features/plugins/PluginManagementPage";
|
||||||
import { SettingsPage } from "./features/settings/SettingsPage";
|
import { SettingsPage } from "./features/settings/SettingsPage";
|
||||||
import { useAppStore } from "./shared/store/appStore";
|
import { useAppStore } from "./shared/store/appStore";
|
||||||
import { updateStore } from "./shared/store/updateStore";
|
import { updateStore } from "./shared/store/updateStore";
|
||||||
@@ -23,6 +24,7 @@ export function App() {
|
|||||||
<Route index element={<HomePage />} />
|
<Route index element={<HomePage />} />
|
||||||
<Route path="calls" element={<CallsPage />} />
|
<Route path="calls" element={<CallsPage />} />
|
||||||
<Route path="harness/cursor" element={<CursorSettingsPage />} />
|
<Route path="harness/cursor" element={<CursorSettingsPage />} />
|
||||||
|
<Route path="plugins" element={<PluginManagementPage />} />
|
||||||
<Route path="settings" element={<SettingsPage />} />
|
<Route path="settings" element={<SettingsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
@use "../../styles/typography" as type;
|
||||||
|
|
||||||
|
.page {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gate {
|
||||||
|
min-height: 250px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 9px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
text-align: center;
|
||||||
|
border: 1px dashed var(--vscode-sideBar-border);
|
||||||
|
border-radius: var(--oa-overlay-radius);
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
font-size: type.$font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
max-width: 500px;
|
||||||
|
font-size: type.$font-size-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.runtimeDetails {
|
||||||
|
display: grid;
|
||||||
|
padding: 8px 16px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
min-height: 38px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
border-bottom: 1px solid var(--vscode-sideBar-border);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
font-size: type.$font-size-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ready {
|
||||||
|
color: var(--vscode-testing-iconPassed, #73c991);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-size: type.$font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
accent-color: var(--vscode-progressBar-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
font-size: type.$font-size-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--vscode-errorForeground);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import type { PluginRuntimePhase, PluginRuntimeStatus } from "../../shared/api";
|
||||||
|
import { PageContent } from "../../shell/layout/PageContent";
|
||||||
|
import { appStore, useAppStore } from "../../shared/store/appStore";
|
||||||
|
import { Button } from "../../shared/ui/Button";
|
||||||
|
import { Modal } from "../../shared/ui/Modal";
|
||||||
|
import { TitledCard } from "../../shared/ui/TitledCard";
|
||||||
|
import styles from "./PluginManagementPage.module.scss";
|
||||||
|
|
||||||
|
export function PluginManagementPage() {
|
||||||
|
const { pluginRuntime } = useAppStore();
|
||||||
|
const [progressOpen, setProgressOpen] = useState(false);
|
||||||
|
const [starting, setStarting] = useState(false);
|
||||||
|
const cancelRequested = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pluginRuntime) void appStore.refreshPluginRuntime();
|
||||||
|
}, [pluginRuntime]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pluginRuntime?.state !== "initializing") return;
|
||||||
|
if (!cancelRequested.current) setProgressOpen(true);
|
||||||
|
const timer = window.setInterval(() => void appStore.refreshPluginRuntime(), 300);
|
||||||
|
return () => window.clearInterval(timer);
|
||||||
|
}, [pluginRuntime?.state]);
|
||||||
|
|
||||||
|
const initialize = async () => {
|
||||||
|
if (starting) return;
|
||||||
|
cancelRequested.current = false;
|
||||||
|
setStarting(true);
|
||||||
|
setProgressOpen(true);
|
||||||
|
const status = await appStore.initializePluginRuntime();
|
||||||
|
setStarting(false);
|
||||||
|
if (!status) {
|
||||||
|
setProgressOpen(false);
|
||||||
|
} else if (cancelRequested.current && status.state === "initializing") {
|
||||||
|
void appStore.cancelPluginRuntimeInitialization();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeProgress = () => {
|
||||||
|
setProgressOpen(false);
|
||||||
|
cancelRequested.current = true;
|
||||||
|
if (pluginRuntime?.state === "initializing") {
|
||||||
|
void appStore.cancelPluginRuntimeInitialization();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = pluginRuntime?.state === "ready"
|
||||||
|
? <RuntimeReady status={pluginRuntime} />
|
||||||
|
: <RuntimeGate status={pluginRuntime} starting={starting} onInitialize={() => void initialize()} />;
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<PageContent
|
||||||
|
title={t("插件管理")}
|
||||||
|
sections={[{ key: "plugin-runtime", estimatedHeight: 320, content }]}
|
||||||
|
/>
|
||||||
|
<RuntimeProgressModal
|
||||||
|
open={progressOpen}
|
||||||
|
status={pluginRuntime}
|
||||||
|
starting={starting}
|
||||||
|
onClose={closeProgress}
|
||||||
|
/>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RuntimeGate({ status, starting, onInitialize }: { status: PluginRuntimeStatus | null; starting: boolean; onInitialize: () => void }) {
|
||||||
|
const checking = status === null;
|
||||||
|
const initializing = starting || status?.state === "initializing";
|
||||||
|
const failed = status?.state === "failed";
|
||||||
|
const unsupported = status?.state === "unsupported";
|
||||||
|
const title = checking
|
||||||
|
? t("正在检查插件运行时")
|
||||||
|
: failed
|
||||||
|
? t("插件运行时初始化失败")
|
||||||
|
: unsupported
|
||||||
|
? t("当前系统不支持插件运行时")
|
||||||
|
: t("需要先初始化插件运行时");
|
||||||
|
const description = failed
|
||||||
|
? t("请重试初始化")
|
||||||
|
: unsupported
|
||||||
|
? status.error ?? t("当前操作系统或 CPU 架构暂不受支持")
|
||||||
|
: t("初始化将下载并安装插件运行时。");
|
||||||
|
|
||||||
|
return <div className={styles.gate}>
|
||||||
|
<strong>{title}</strong>
|
||||||
|
<span>{description}</span>
|
||||||
|
{!unsupported && <Button variant="primary" disabled={checking || initializing} onClick={onInitialize}>
|
||||||
|
{checking ? t("检查中…") : initializing ? t("初始化中…") : failed ? t("重新初始化插件") : t("初始化插件")}
|
||||||
|
</Button>}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RuntimeReady({ status }: { status: PluginRuntimeStatus }) {
|
||||||
|
return <div className={styles.page}>
|
||||||
|
<TitledCard title={t("插件运行时")}>
|
||||||
|
<div className={styles.runtimeDetails}>
|
||||||
|
<div><strong>{t("状态")}</strong><span className={styles.ready}>{t("已就绪")}</span></div>
|
||||||
|
<div><strong>{t("插件运行时版本")}</strong><span>{status.version}</span></div>
|
||||||
|
<div><strong>{t("运行平台")}</strong><span>{status.target}</span></div>
|
||||||
|
</div>
|
||||||
|
</TitledCard>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RuntimeProgressModal({ open, status, starting, onClose }: { open: boolean; status: PluginRuntimeStatus | null; starting: boolean; onClose: () => void }) {
|
||||||
|
const initializing = starting || status?.state === "initializing";
|
||||||
|
const downloaded = status?.downloaded_bytes ?? 0;
|
||||||
|
const total = status?.total_bytes ?? null;
|
||||||
|
const percent = total && total > 0 ? Math.min(100, Math.round((downloaded / total) * 100)) : null;
|
||||||
|
const stage = status?.state === "ready"
|
||||||
|
? t("插件运行时初始化完成")
|
||||||
|
: status?.state === "failed"
|
||||||
|
? t("插件运行时初始化失败")
|
||||||
|
: phaseText(status?.phase ?? null);
|
||||||
|
|
||||||
|
return <Modal
|
||||||
|
open={open}
|
||||||
|
title={t("初始化插件运行时")}
|
||||||
|
closeLabel={status?.state === "ready" ? t("完成") : initializing ? t("取消") : t("关闭")}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<div className={styles.progressContent} aria-live="polite">
|
||||||
|
<strong>{stage}</strong>
|
||||||
|
{status?.phase === "downloading" && <>
|
||||||
|
<progress
|
||||||
|
aria-label={t("下载进度")}
|
||||||
|
value={percent ?? undefined}
|
||||||
|
max={100}
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{total ? t("已下载 {downloaded} / {total}", { downloaded: formatBytes(downloaded), total: formatBytes(total) }) : t("已下载 {downloaded}", { downloaded: formatBytes(downloaded) })}
|
||||||
|
</span>
|
||||||
|
</>}
|
||||||
|
{status?.state === "failed" && <span className={styles.error}>{t("请重试初始化")}</span>}
|
||||||
|
{status?.state === "ready" && <span>{t("插件运行时 {version} 已安装,可以开始使用插件。", { version: status.version })}</span>}
|
||||||
|
</div>
|
||||||
|
</Modal>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function phaseText(phase: PluginRuntimePhase | null) {
|
||||||
|
switch (phase) {
|
||||||
|
case "checking": return t("正在检查插件运行时");
|
||||||
|
case "downloading": return t("正在下载插件运行时");
|
||||||
|
case "verifying": return t("正在验证插件运行时下载文件");
|
||||||
|
case "installing": return t("正在安装插件运行时");
|
||||||
|
case "validating": return t("正在验证插件运行时");
|
||||||
|
default: return t("正在准备插件运行时");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes(bytes: number) {
|
||||||
|
if (bytes < 1024) return `${bytes} B`;
|
||||||
|
const units = ["KB", "MB", "GB"];
|
||||||
|
let value = bytes / 1024;
|
||||||
|
let unit = 0;
|
||||||
|
while (value >= 1024 && unit < units.length - 1) {
|
||||||
|
value /= 1024;
|
||||||
|
unit += 1;
|
||||||
|
}
|
||||||
|
return `${value < 10 ? value.toFixed(1) : value.toFixed(0)} ${units[unit]}`;
|
||||||
|
}
|
||||||
@@ -124,6 +124,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"06619f339fa0ab46": {
|
||||||
|
"source": "正在准备插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 130,
|
||||||
|
"column": 21
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"076832c1b2de22c3": {
|
"076832c1b2de22c3": {
|
||||||
"source": "缓存写入:{tokens}",
|
"source": "缓存写入:{tokens}",
|
||||||
"kind": "template",
|
"kind": "template",
|
||||||
@@ -752,6 +764,20 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"2400fbd0aeab9e13": {
|
||||||
|
"source": "已下载 {downloaded}",
|
||||||
|
"kind": "template",
|
||||||
|
"placeholders": [
|
||||||
|
"downloaded"
|
||||||
|
],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 114,
|
||||||
|
"column": 122
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"24a0a24864454575": {
|
"24a0a24864454575": {
|
||||||
"source": "已存在,跳过",
|
"source": "已存在,跳过",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -797,7 +823,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 238,
|
"line": 240,
|
||||||
"column": 14
|
"column": 14
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -883,12 +909,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 228,
|
"line": 230,
|
||||||
"column": 20
|
"column": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 239,
|
"line": 241,
|
||||||
"column": 20
|
"column": 20
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -993,7 +1019,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 227,
|
"line": 229,
|
||||||
"column": 14
|
"column": 14
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1046,6 +1072,23 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"35fcbd57d58a9394": {
|
||||||
|
"source": "插件管理",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 37,
|
||||||
|
"column": 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "shell/AppLayout.tsx",
|
||||||
|
"line": 80,
|
||||||
|
"column": 46
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"36f33adaf0942634": {
|
"36f33adaf0942634": {
|
||||||
"source": "确认",
|
"source": "确认",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -1058,7 +1101,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 240,
|
"line": 242,
|
||||||
"column": 21
|
"column": 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1275,6 +1318,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"3e0b34ddc2121f7d": {
|
||||||
|
"source": "运行平台",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 81,
|
||||||
|
"column": 23
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"3f6c25aa329163a4": {
|
"3f6c25aa329163a4": {
|
||||||
"source": "原接口路径会追加到此服务地址。",
|
"source": "原接口路径会追加到此服务地址。",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -1308,6 +1363,23 @@
|
|||||||
"file": "features/models/CursorSettingsPage.tsx",
|
"file": "features/models/CursorSettingsPage.tsx",
|
||||||
"line": 262,
|
"line": 262,
|
||||||
"column": 80
|
"column": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 102,
|
||||||
|
"column": 55
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"402495402ce333b1": {
|
||||||
|
"source": "重新初始化插件",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 70,
|
||||||
|
"column": 68
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1541,6 +1613,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"4a861200ad513a3c": {
|
||||||
|
"source": "初始化插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 100,
|
||||||
|
"column": 12
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"4a8d6841b4023edf": {
|
"4a8d6841b4023edf": {
|
||||||
"source": "确认导入",
|
"source": "确认导入",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -1553,6 +1637,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"4aca6a31090fe2b8": {
|
||||||
|
"source": "初始化中…",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 70,
|
||||||
|
"column": 46
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"4b458e6e147221d7": {
|
"4b458e6e147221d7": {
|
||||||
"source": "系统会根据请求协议自动追加标准端点路径。",
|
"source": "系统会根据请求协议自动追加标准端点路径。",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -1577,7 +1673,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 200,
|
"line": 202,
|
||||||
"column": 67
|
"column": 67
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1871,6 +1967,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"5c62e36c152dfc7c": {
|
||||||
|
"source": "插件运行时初始化完成",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 93,
|
||||||
|
"column": 7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"5d59857bf039cac9": {
|
"5d59857bf039cac9": {
|
||||||
"source": "Cursor 助手 v{version}",
|
"source": "Cursor 助手 v{version}",
|
||||||
"kind": "template",
|
"kind": "template",
|
||||||
@@ -1904,7 +2012,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 244,
|
"line": 246,
|
||||||
"column": 11
|
"column": 11
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1994,6 +2102,11 @@
|
|||||||
"file": "features/calls/CallTable.tsx",
|
"file": "features/calls/CallTable.tsx",
|
||||||
"line": 16,
|
"line": 16,
|
||||||
"column": 15
|
"column": 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 79,
|
||||||
|
"column": 23
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2028,7 +2141,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shared/api.ts",
|
"file": "shared/api.ts",
|
||||||
"line": 318,
|
"line": 333,
|
||||||
"column": 43
|
"column": 43
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -2280,12 +2393,12 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shared/store/appStore.ts",
|
"file": "shared/store/appStore.ts",
|
||||||
"line": 153,
|
"line": 177,
|
||||||
"column": 23
|
"column": 23
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "shared/store/appStore.ts",
|
"file": "shared/store/appStore.ts",
|
||||||
"line": 160,
|
"line": 184,
|
||||||
"column": 25
|
"column": 25
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -2333,7 +2446,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shared/api.ts",
|
"file": "shared/api.ts",
|
||||||
"line": 313,
|
"line": 328,
|
||||||
"column": 43
|
"column": 43
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -2362,6 +2475,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"7c10d97162c96dbd": {
|
||||||
|
"source": "正在验证插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 129,
|
||||||
|
"column": 31
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"7cea2f3c46565d29": {
|
"7cea2f3c46565d29": {
|
||||||
"source": "OpenAI 额外参数",
|
"source": "OpenAI 额外参数",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -2388,7 +2513,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "App.tsx",
|
"file": "App.tsx",
|
||||||
"line": 50,
|
"line": 52,
|
||||||
"column": 19
|
"column": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -2448,11 +2573,23 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shared/api.ts",
|
"file": "shared/api.ts",
|
||||||
"line": 262,
|
"line": 275,
|
||||||
"column": 21
|
"column": 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"8213941f12320ce1": {
|
||||||
|
"source": "当前操作系统或 CPU 架构暂不受支持",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 63,
|
||||||
|
"column": 25
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"83c4efccd9a6bf69": {
|
"83c4efccd9a6bf69": {
|
||||||
"source": "连通性测试已取消:成功 {successful},失败 {failed}",
|
"source": "连通性测试已取消:成功 {successful},失败 {failed}",
|
||||||
"kind": "template",
|
"kind": "template",
|
||||||
@@ -2468,6 +2605,21 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"83e8d0b7aff2b394": {
|
||||||
|
"source": "已下载 {downloaded} / {total}",
|
||||||
|
"kind": "template",
|
||||||
|
"placeholders": [
|
||||||
|
"downloaded",
|
||||||
|
"total"
|
||||||
|
],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 114,
|
||||||
|
"column": 20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"83fcfb4c1f2c1641": {
|
"83fcfb4c1f2c1641": {
|
||||||
"source": "获取模型",
|
"source": "获取模型",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -2572,6 +2724,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"8911e4f1407d58cb": {
|
||||||
|
"source": "正在下载插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 126,
|
||||||
|
"column": 32
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"89a101b809be7cfc": {
|
"89a101b809be7cfc": {
|
||||||
"source": "系统会原样使用此地址,不追加或修改请求路径。",
|
"source": "系统会原样使用此地址,不追加或修改请求路径。",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -2649,7 +2813,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 246,
|
"line": 248,
|
||||||
"column": 16
|
"column": 16
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -2757,6 +2921,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"945fb1c67eca8493": {
|
||||||
|
"source": "正在安装插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 128,
|
||||||
|
"column": 31
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"946b3ffc02f026c0": {
|
"946b3ffc02f026c0": {
|
||||||
"source": "确定删除这个模型吗?",
|
"source": "确定删除这个模型吗?",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -2824,7 +3000,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 233,
|
"line": 235,
|
||||||
"column": 11
|
"column": 11
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -2856,6 +3032,23 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"9db205c6055bacc4": {
|
||||||
|
"source": "插件运行时初始化失败",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 56,
|
||||||
|
"column": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 95,
|
||||||
|
"column": 9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"9e356080c56877f8": {
|
"9e356080c56877f8": {
|
||||||
"source": "已关闭静默启动",
|
"source": "已关闭静默启动",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -2880,6 +3073,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"9ed11266ead88f5b": {
|
||||||
|
"source": "正在验证插件运行时下载文件",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 127,
|
||||||
|
"column": 30
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"9f6fee1aba17a565": {
|
"9f6fee1aba17a565": {
|
||||||
"source": "语言",
|
"source": "语言",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -3069,7 +3274,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 229,
|
"line": 231,
|
||||||
"column": 21
|
"column": 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -3176,6 +3381,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ab27f80d046f3d7f": {
|
||||||
|
"source": "已就绪",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 79,
|
||||||
|
"column": 72
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"ab9084a640fbb864": {
|
"ab9084a640fbb864": {
|
||||||
"source": "全不选",
|
"source": "全不选",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -3281,12 +3498,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 260,
|
"line": 262,
|
||||||
"column": 64
|
"column": 64
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 260,
|
"line": 262,
|
||||||
"column": 125
|
"column": 125
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -3320,6 +3537,23 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"b254ff315d861346": {
|
||||||
|
"source": "请重试初始化",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 61,
|
||||||
|
"column": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 117,
|
||||||
|
"column": 70
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"b4411558b932266f": {
|
"b4411558b932266f": {
|
||||||
"source": "上游类型",
|
"source": "上游类型",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -3332,6 +3566,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"b4c9e08870d41aa2": {
|
||||||
|
"source": "需要先初始化插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 59,
|
||||||
|
"column": 11
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"b502b1d414664337": {
|
"b502b1d414664337": {
|
||||||
"source": "提示词:{tokens}",
|
"source": "提示词:{tokens}",
|
||||||
"kind": "template",
|
"kind": "template",
|
||||||
@@ -3556,11 +3802,23 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 251,
|
"line": 253,
|
||||||
"column": 24
|
"column": 24
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"bda74b5674b6a57d": {
|
||||||
|
"source": "初始化插件",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 70,
|
||||||
|
"column": 83
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"bf57afd709694b55": {
|
"bf57afd709694b55": {
|
||||||
"source": "概览时间范围",
|
"source": "概览时间范围",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -3587,6 +3845,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"c0b3fbff51ccc40b": {
|
||||||
|
"source": "完成",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 102,
|
||||||
|
"column": 45
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"c1e98892a77f7a19": {
|
"c1e98892a77f7a19": {
|
||||||
"source": "{count} 条/页",
|
"source": "{count} 条/页",
|
||||||
"kind": "template",
|
"kind": "template",
|
||||||
@@ -3618,6 +3888,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"c54863655e879b36": {
|
||||||
|
"source": "当前系统不支持插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 58,
|
||||||
|
"column": 11
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"c7ea2c9bc43134bd": {
|
"c7ea2c9bc43134bd": {
|
||||||
"source": "编辑模型",
|
"source": "编辑模型",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -3719,6 +4001,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"cb99f0138b032687": {
|
||||||
|
"source": "初始化将下载并安装插件运行时。",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 64,
|
||||||
|
"column": 9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"cea1aafe9416de7b": {
|
"cea1aafe9416de7b": {
|
||||||
"source": "请求头",
|
"source": "请求头",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -3905,6 +4199,32 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"d6e61888b07853ea": {
|
||||||
|
"source": "高级",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "shell/AppLayout.tsx",
|
||||||
|
"line": 79,
|
||||||
|
"column": 29
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"d766536c18e8e990": {
|
||||||
|
"source": "插件运行时 {version} 已安装,可以开始使用插件。",
|
||||||
|
"kind": "template",
|
||||||
|
"placeholders": [
|
||||||
|
"version"
|
||||||
|
],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 118,
|
||||||
|
"column": 44
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"d86fa42c3848c680": {
|
"d86fa42c3848c680": {
|
||||||
"source": "使用系统代理",
|
"source": "使用系统代理",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -3929,7 +4249,7 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "shell/AppLayout.tsx",
|
"file": "shell/AppLayout.tsx",
|
||||||
"line": 186,
|
"line": 188,
|
||||||
"column": 54
|
"column": 54
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -4270,6 +4590,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"e7cef7b834f301e7": {
|
||||||
|
"source": "插件运行时版本",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 80,
|
||||||
|
"column": 23
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"e825a2a42c22380e": {
|
"e825a2a42c22380e": {
|
||||||
"source": "模型类型",
|
"source": "模型类型",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -4451,6 +4783,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"f4f4c81a4d719711": {
|
||||||
|
"source": "插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 77,
|
||||||
|
"column": 24
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"f4fa9f31ea2ae58d": {
|
"f4fa9f31ea2ae58d": {
|
||||||
"source": "过去一年的 Token 用量日历",
|
"source": "过去一年的 Token 用量日历",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
@@ -4575,11 +4919,28 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"fad86bf65f72c747": {
|
||||||
|
"source": "下载进度",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 109,
|
||||||
|
"column": 23
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"fb11aa6f29827095": {
|
"fb11aa6f29827095": {
|
||||||
"source": "检查中…",
|
"source": "检查中…",
|
||||||
"kind": "text",
|
"kind": "text",
|
||||||
"placeholders": [],
|
"placeholders": [],
|
||||||
"refs": [
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 70,
|
||||||
|
"column": 19
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "features/settings/AppLifecycleSettingsCard.tsx",
|
"file": "features/settings/AppLifecycleSettingsCard.tsx",
|
||||||
"line": 160,
|
"line": 160,
|
||||||
@@ -4599,6 +4960,23 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"fc22d1ab9ac73c6f": {
|
||||||
|
"source": "正在检查插件运行时",
|
||||||
|
"kind": "text",
|
||||||
|
"placeholders": [],
|
||||||
|
"refs": [
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 54,
|
||||||
|
"column": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "features/plugins/PluginManagementPage.tsx",
|
||||||
|
"line": 125,
|
||||||
|
"column": 29
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"fc3947ebe6b2177b": {
|
"fc3947ebe6b2177b": {
|
||||||
"source": "默认 {defaultRate} / 计入创建 {reuseRate}",
|
"source": "默认 {defaultRate} / 计入创建 {reuseRate}",
|
||||||
"kind": "template",
|
"kind": "template",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"051836569928a9f9": "Edit",
|
"051836569928a9f9": "Edit",
|
||||||
"05468af47054d488": "Connectivity test for {model} succeeded ({duration} ms)",
|
"05468af47054d488": "Connectivity test for {model} succeeded ({duration} ms)",
|
||||||
"0580e0a99a6f1afc": "Artifacts",
|
"0580e0a99a6f1afc": "Artifacts",
|
||||||
|
"06619f339fa0ab46": "Preparing the plugin runtime",
|
||||||
"076832c1b2de22c3": "Cache write: {tokens}",
|
"076832c1b2de22c3": "Cache write: {tokens}",
|
||||||
"07879e064ae16542": "Estimated output: {tokens}",
|
"07879e064ae16542": "Estimated output: {tokens}",
|
||||||
"07c657ed4747126e": "Anthropic extra parameters",
|
"07c657ed4747126e": "Anthropic extra parameters",
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
"22d7895ea5fca72e": "By provider",
|
"22d7895ea5fca72e": "By provider",
|
||||||
"23ae7a90b1b9816d": "Clear scope",
|
"23ae7a90b1b9816d": "Clear scope",
|
||||||
"23e49479e15e6770": "Version {version} is available",
|
"23e49479e15e6770": "Version {version} is available",
|
||||||
|
"2400fbd0aeab9e13": "Downloaded {downloaded}",
|
||||||
"24a0a24864454575": "Existing, skipped",
|
"24a0a24864454575": "Existing, skipped",
|
||||||
"2555d6c7fbb7e070": "Enter a model ID directly or load models returned by the API.",
|
"2555d6c7fbb7e070": "Enter a model ID directly or load models returned by the API.",
|
||||||
"29585d7193539200": "Current version {version}",
|
"29585d7193539200": "Current version {version}",
|
||||||
@@ -68,6 +70,7 @@
|
|||||||
"2f7dec3be28d7597": "{count} selected",
|
"2f7dec3be28d7597": "{count} selected",
|
||||||
"2f9daa828907b93f": "Delete",
|
"2f9daa828907b93f": "Delete",
|
||||||
"346ff60e6c7c5181": "Reading…",
|
"346ff60e6c7c5181": "Reading…",
|
||||||
|
"35fcbd57d58a9394": "Plugin management",
|
||||||
"36f33adaf0942634": "Confirm",
|
"36f33adaf0942634": "Confirm",
|
||||||
"37125ef2e1d707cb": "Server address or complete request URL, API Key, model name, display name, and note are required",
|
"37125ef2e1d707cb": "Server address or complete request URL, API Key, model name, display name, and note are required",
|
||||||
"378bb0eec39fa8a2": "Last page",
|
"378bb0eec39fa8a2": "Last page",
|
||||||
@@ -85,9 +88,11 @@
|
|||||||
"3cfae5728b92b334": "Token usage: {tokens}",
|
"3cfae5728b92b334": "Token usage: {tokens}",
|
||||||
"3d13868593ae4eeb": "Display language",
|
"3d13868593ae4eeb": "Display language",
|
||||||
"3da0bf1610ff5db5": "Recommended",
|
"3da0bf1610ff5db5": "Recommended",
|
||||||
|
"3e0b34ddc2121f7d": "Runtime platform",
|
||||||
"3f6c25aa329163a4": "The original endpoint path is appended to this service address.",
|
"3f6c25aa329163a4": "The original endpoint path is appended to this service address.",
|
||||||
"3fd118e2ffe0b2b6": "Cancel all tests",
|
"3fd118e2ffe0b2b6": "Cancel all tests",
|
||||||
"3fd47edce45b3603": "Close",
|
"3fd47edce45b3603": "Close",
|
||||||
|
"402495402ce333b1": "Reinitialize plugins",
|
||||||
"40a08e7cf320ae07": "Clear detailed records?",
|
"40a08e7cf320ae07": "Clear detailed records?",
|
||||||
"4125fc7ba333524c": "Default light",
|
"4125fc7ba333524c": "Default light",
|
||||||
"42655ed8e4108ae2": "Input (non-cached)",
|
"42655ed8e4108ae2": "Input (non-cached)",
|
||||||
@@ -104,7 +109,9 @@
|
|||||||
"497c85690c4cc0fc": "No data",
|
"497c85690c4cc0fc": "No data",
|
||||||
"499c729eb09aa2a6": "Context window tokens",
|
"499c729eb09aa2a6": "Context window tokens",
|
||||||
"49be72e6045c007d": "Cancel test",
|
"49be72e6045c007d": "Cancel test",
|
||||||
|
"4a861200ad513a3c": "Initialize plugin runtime",
|
||||||
"4a8d6841b4023edf": "Confirm import",
|
"4a8d6841b4023edf": "Confirm import",
|
||||||
|
"4aca6a31090fe2b8": "Initializing…",
|
||||||
"4b458e6e147221d7": "The standard endpoint path is appended automatically for the selected protocol.",
|
"4b458e6e147221d7": "The standard endpoint path is appended automatically for the selected protocol.",
|
||||||
"4d0680f9efaef147": "Unread",
|
"4d0680f9efaef147": "Unread",
|
||||||
"4e30d7c9ed2b0eee": "Not set",
|
"4e30d7c9ed2b0eee": "Not set",
|
||||||
@@ -127,6 +134,7 @@
|
|||||||
"5b17f59d33bde39e": "Error: {error}",
|
"5b17f59d33bde39e": "Error: {error}",
|
||||||
"5ba65a74c4e792c5": "By type",
|
"5ba65a74c4e792c5": "By type",
|
||||||
"5c55a67935af8f45": "All",
|
"5c55a67935af8f45": "All",
|
||||||
|
"5c62e36c152dfc7c": "Plugin runtime initialized",
|
||||||
"5d59857bf039cac9": "Cursor Assistant v{version}",
|
"5d59857bf039cac9": "Cursor Assistant v{version}",
|
||||||
"5f8d556a9c47da3c": "Launch at login disabled",
|
"5f8d556a9c47da3c": "Launch at login disabled",
|
||||||
"5f9acfb945229062": "Are you sure you no longer want to see this ad?",
|
"5f9acfb945229062": "Are you sure you no longer want to see this ad?",
|
||||||
@@ -163,6 +171,7 @@
|
|||||||
"7a2229f6a6d330a5": "Open a terminal from the desktop app to install the CA",
|
"7a2229f6a6d330a5": "Open a terminal from the desktop app to install the CA",
|
||||||
"7a3cec4ca715de80": "Call statistics",
|
"7a3cec4ca715de80": "Call statistics",
|
||||||
"7ba2d6728fe2531b": "Confirm clear",
|
"7ba2d6728fe2531b": "Confirm clear",
|
||||||
|
"7c10d97162c96dbd": "Validating the plugin runtime",
|
||||||
"7cea2f3c46565d29": "OpenAI extra parameters",
|
"7cea2f3c46565d29": "OpenAI extra parameters",
|
||||||
"7d9f043f8f7ab45c": "Version {version} is available in Settings",
|
"7d9f043f8f7ab45c": "Version {version} is available in Settings",
|
||||||
"7e0891860c9e6374": "TAB service address is required",
|
"7e0891860c9e6374": "TAB service address is required",
|
||||||
@@ -170,7 +179,9 @@
|
|||||||
"7f3c8312816fe26a": "Refreshing…",
|
"7f3c8312816fe26a": "Refreshing…",
|
||||||
"7f68ebad19ba6bcd": "Check for updates",
|
"7f68ebad19ba6bcd": "Check for updates",
|
||||||
"811a3b22a5a7f2d5": "Unable to connect to the local management service",
|
"811a3b22a5a7f2d5": "Unable to connect to the local management service",
|
||||||
|
"8213941f12320ce1": "This operating system or CPU architecture is not currently supported",
|
||||||
"83c4efccd9a6bf69": "Connectivity test cancelled: {successful} succeeded, {failed} failed",
|
"83c4efccd9a6bf69": "Connectivity test cancelled: {successful} succeeded, {failed} failed",
|
||||||
|
"83e8d0b7aff2b394": "Downloaded {downloaded} / {total}",
|
||||||
"83fcfb4c1f2c1641": "Fetch models",
|
"83fcfb4c1f2c1641": "Fetch models",
|
||||||
"842b9f11cdd96bda": "Launch at login",
|
"842b9f11cdd96bda": "Launch at login",
|
||||||
"843ac7e15a5047a7": "Confirm legacy model configuration import",
|
"843ac7e15a5047a7": "Confirm legacy model configuration import",
|
||||||
@@ -178,6 +189,7 @@
|
|||||||
"86b7355ec3bd55ef": "Hide API Key",
|
"86b7355ec3bd55ef": "Hide API Key",
|
||||||
"8716e1344b0daddb": "Cursor official",
|
"8716e1344b0daddb": "Cursor official",
|
||||||
"878a8ab176429a86": "View instructions",
|
"878a8ab176429a86": "View instructions",
|
||||||
|
"8911e4f1407d58cb": "Downloading the plugin runtime",
|
||||||
"89a101b809be7cfc": "This address is used exactly as entered without changing or appending the request path.",
|
"89a101b809be7cfc": "This address is used exactly as entered without changing or appending the request path.",
|
||||||
"8a8542f6964852dc": "Next page",
|
"8a8542f6964852dc": "Next page",
|
||||||
"8b6ff498515bcc2f": "Time",
|
"8b6ff498515bcc2f": "Time",
|
||||||
@@ -192,14 +204,17 @@
|
|||||||
"91aaf184cfc17ffd": "Overview",
|
"91aaf184cfc17ffd": "Overview",
|
||||||
"92156a483d4ba248": "Only request, response, and trace attachments are deleted; call summaries, metrics, and configuration are kept.",
|
"92156a483d4ba248": "Only request, response, and trace attachments are deleted; call summaries, metrics, and configuration are kept.",
|
||||||
"940a168911ade998": "Items per page",
|
"940a168911ade998": "Items per page",
|
||||||
|
"945fb1c67eca8493": "Installing the plugin runtime",
|
||||||
"946b3ffc02f026c0": "Delete this model?",
|
"946b3ffc02f026c0": "Delete this model?",
|
||||||
"966498853d801a52": "TAB connection",
|
"966498853d801a52": "TAB connection",
|
||||||
"9850ed41a5bfbb0c": "{count} selected",
|
"9850ed41a5bfbb0c": "{count} selected",
|
||||||
"997ec8201c2adeda": "Open terminal to install CA",
|
"997ec8201c2adeda": "Open terminal to install CA",
|
||||||
"9b1b7ed518ee401d": "This will open the tutorial in your system browser. Continue?",
|
"9b1b7ed518ee401d": "This will open the tutorial in your system browser. Continue?",
|
||||||
"9c41b3a9e12ac994": "Reasoning effort",
|
"9c41b3a9e12ac994": "Reasoning effort",
|
||||||
|
"9db205c6055bacc4": "Plugin runtime initialization failed",
|
||||||
"9e356080c56877f8": "Silent start disabled",
|
"9e356080c56877f8": "Silent start disabled",
|
||||||
"9e46da6923836182": "For example: 2026-08-23 09:00, 1 hour ago",
|
"9e46da6923836182": "For example: 2026-08-23 09:00, 1 hour ago",
|
||||||
|
"9ed11266ead88f5b": "Verifying the plugin runtime download",
|
||||||
"9f6fee1aba17a565": "Language",
|
"9f6fee1aba17a565": "Language",
|
||||||
"9fb48101d237ff96": "Last week",
|
"9fb48101d237ff96": "Last week",
|
||||||
"a026f37e613cf48b": "Output Tokens",
|
"a026f37e613cf48b": "Output Tokens",
|
||||||
@@ -220,6 +235,7 @@
|
|||||||
"a7617f42f898b2bf": "Use complete request URL",
|
"a7617f42f898b2bf": "Use complete request URL",
|
||||||
"a8036485f9227f2c": "Drag to reorder",
|
"a8036485f9227f2c": "Drag to reorder",
|
||||||
"a98585871c5313ff": "Display name",
|
"a98585871c5313ff": "Display name",
|
||||||
|
"ab27f80d046f3d7f": "Ready",
|
||||||
"ab9084a640fbb864": "Deselect all",
|
"ab9084a640fbb864": "Deselect all",
|
||||||
"abecab6701177721": "Launch at login enabled",
|
"abecab6701177721": "Launch at login enabled",
|
||||||
"ac58d0f9a3f8d389": "Enter model notes",
|
"ac58d0f9a3f8d389": "Enter model notes",
|
||||||
@@ -230,7 +246,9 @@
|
|||||||
"aee88743413144a2": "Refresh",
|
"aee88743413144a2": "Refresh",
|
||||||
"b06325c5660f0c29": "Direct",
|
"b06325c5660f0c29": "Direct",
|
||||||
"b16c3b2ecedd6fe1": "Cursor integration is active. Add a model configuration to use a BYOK model.",
|
"b16c3b2ecedd6fe1": "Cursor integration is active. Add a model configuration to use a BYOK model.",
|
||||||
|
"b254ff315d861346": "Try initializing again",
|
||||||
"b4411558b932266f": "Provider type",
|
"b4411558b932266f": "Provider type",
|
||||||
|
"b4c9e08870d41aa2": "Initialize the plugin runtime first",
|
||||||
"b502b1d414664337": "Prompt: {tokens}",
|
"b502b1d414664337": "Prompt: {tokens}",
|
||||||
"b5141d3d19e9a048": "Yes",
|
"b5141d3d19e9a048": "Yes",
|
||||||
"b6725f218ebaef26": "Dock icon shown",
|
"b6725f218ebaef26": "Dock icon shown",
|
||||||
@@ -247,10 +265,13 @@
|
|||||||
"bb2b7736433ae867": "Cursor tracing",
|
"bb2b7736433ae867": "Cursor tracing",
|
||||||
"bb7efdcb6af6e805": "Default dark",
|
"bb7efdcb6af6e805": "Default dark",
|
||||||
"bda62ce1d5e4ace9": "Tell us why",
|
"bda62ce1d5e4ace9": "Tell us why",
|
||||||
|
"bda74b5674b6a57d": "Initialize plugins",
|
||||||
"bf57afd709694b55": "Overview time range",
|
"bf57afd709694b55": "Overview time range",
|
||||||
"bfc01caf9fe0c841": "Cache hit rate {rate}",
|
"bfc01caf9fe0c841": "Cache hit rate {rate}",
|
||||||
|
"c0b3fbff51ccc40b": "Done",
|
||||||
"c1e98892a77f7a19": "{count} per page",
|
"c1e98892a77f7a19": "{count} per page",
|
||||||
"c3760858cdb6d9f4": "Request body",
|
"c3760858cdb6d9f4": "Request body",
|
||||||
|
"c54863655e879b36": "Plugin runtime is not supported on this system",
|
||||||
"c7ea2c9bc43134bd": "Edit model",
|
"c7ea2c9bc43134bd": "Edit model",
|
||||||
"c8c14507b2d37395": "Reasoning effort",
|
"c8c14507b2d37395": "Reasoning effort",
|
||||||
"c8df3c14a003bfcd": "Unable to load call details",
|
"c8df3c14a003bfcd": "Unable to load call details",
|
||||||
@@ -258,6 +279,7 @@
|
|||||||
"c9b9ae7a61444ab7": "Previous page",
|
"c9b9ae7a61444ab7": "Previous page",
|
||||||
"c9d146d006993cc1": "Cache statistics policy: default ({rate})",
|
"c9d146d006993cc1": "Cache statistics policy: default ({rate})",
|
||||||
"cb2f1709f983d2f4": "Model name",
|
"cb2f1709f983d2f4": "Model name",
|
||||||
|
"cb99f0138b032687": "Initialization downloads and installs the plugin runtime.",
|
||||||
"cea1aafe9416de7b": "Request headers",
|
"cea1aafe9416de7b": "Request headers",
|
||||||
"cfae1a14d2120c57": "Detailed mode",
|
"cfae1a14d2120c57": "Detailed mode",
|
||||||
"cfe085015632e9c8": "The local management service port used by the desktop frontend. Enter 0 to select a random port at startup.",
|
"cfe085015632e9c8": "The local management service port used by the desktop frontend. Enter 0 to select a random port at startup.",
|
||||||
@@ -271,6 +293,8 @@
|
|||||||
"d58c88688e1a949d": "Presets",
|
"d58c88688e1a949d": "Presets",
|
||||||
"d60669bb26a22f5d": "Leave blank to use the default",
|
"d60669bb26a22f5d": "Leave blank to use the default",
|
||||||
"d6b1f203680f5496": "Leave blank to use adaptive thinking",
|
"d6b1f203680f5496": "Leave blank to use adaptive thinking",
|
||||||
|
"d6e61888b07853ea": "Advanced",
|
||||||
|
"d766536c18e8e990": "Plugin runtime {version} is installed and ready to use.",
|
||||||
"d86fa42c3848c680": "Use system proxy",
|
"d86fa42c3848c680": "Use system proxy",
|
||||||
"d8c47e9776cf1082": "Main menu",
|
"d8c47e9776cf1082": "Main menu",
|
||||||
"da521d1c1cbd36af": "Authorization is required to install the certificate",
|
"da521d1c1cbd36af": "Authorization is required to install the certificate",
|
||||||
@@ -299,6 +323,7 @@
|
|||||||
"e59ae97924d62f01": "First page",
|
"e59ae97924d62f01": "First page",
|
||||||
"e5b9961a0d5242e3": "Port settings saved. Restart the app to apply them.",
|
"e5b9961a0d5242e3": "Port settings saved. Restart the app to apply them.",
|
||||||
"e77e3d58b0dcffaa": "Duration",
|
"e77e3d58b0dcffaa": "Duration",
|
||||||
|
"e7cef7b834f301e7": "Plugin runtime version",
|
||||||
"e825a2a42c22380e": "Model type",
|
"e825a2a42c22380e": "Model type",
|
||||||
"e828bd3a0151edc2": "The local CA must be trusted by the system",
|
"e828bd3a0151edc2": "The local CA must be trusted by the system",
|
||||||
"e8b1268c1e3610f2": "Existing",
|
"e8b1268c1e3610f2": "Existing",
|
||||||
@@ -313,6 +338,7 @@
|
|||||||
"f2bdc88464c51c2e": "Show API Key",
|
"f2bdc88464c51c2e": "Show API Key",
|
||||||
"f4694c46b1e19602": "Final request type",
|
"f4694c46b1e19602": "Final request type",
|
||||||
"f4dcb6a3ceb32247": "Page {page} of {count}",
|
"f4dcb6a3ceb32247": "Page {page} of {count}",
|
||||||
|
"f4f4c81a4d719711": "Plugin runtime",
|
||||||
"f4fa9f31ea2ae58d": "Token usage calendar for the past year",
|
"f4fa9f31ea2ae58d": "Token usage calendar for the past year",
|
||||||
"f50276449943286c": "End time",
|
"f50276449943286c": "End time",
|
||||||
"f69273dbbebfb3a1": "Format",
|
"f69273dbbebfb3a1": "Format",
|
||||||
@@ -323,8 +349,10 @@
|
|||||||
"f9aa11dbb15ce647": "Saturday",
|
"f9aa11dbb15ce647": "Saturday",
|
||||||
"f9b55ca75425161b": "Response content was not recorded. Enable detailed records and try again.",
|
"f9b55ca75425161b": "Response content was not recorded. Enable detailed records and try again.",
|
||||||
"fa5b4b8a751c7d1b": "The local proxy port used by Cursor. Enter 0 to select a random port at startup.",
|
"fa5b4b8a751c7d1b": "The local proxy port used by Cursor. Enter 0 to select a random port at startup.",
|
||||||
|
"fad86bf65f72c747": "Download progress",
|
||||||
"fb11aa6f29827095": "Checking…",
|
"fb11aa6f29827095": "Checking…",
|
||||||
"fbe8778fa8b9bab5": "Initialize the local CA first",
|
"fbe8778fa8b9bab5": "Initialize the local CA first",
|
||||||
|
"fc22d1ab9ac73c6f": "Checking the plugin runtime",
|
||||||
"fc3947ebe6b2177b": "Default {defaultRate} / include creation {reuseRate}",
|
"fc3947ebe6b2177b": "Default {defaultRate} / include creation {reuseRate}",
|
||||||
"fcd311fd8ad42462": "Open model list",
|
"fcd311fd8ad42462": "Open model list",
|
||||||
"fd415f8e0097c832": "Cache reads and writes are included in prompt-side statistics.",
|
"fd415f8e0097c832": "Cache reads and writes are included in prompt-side statistics.",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"051836569928a9f9": "编辑",
|
"051836569928a9f9": "编辑",
|
||||||
"05468af47054d488": "模型 {model} 连通性测试成功({duration} ms)",
|
"05468af47054d488": "模型 {model} 连通性测试成功({duration} ms)",
|
||||||
"0580e0a99a6f1afc": "工件数",
|
"0580e0a99a6f1afc": "工件数",
|
||||||
|
"06619f339fa0ab46": "正在准备插件运行时",
|
||||||
"076832c1b2de22c3": "缓存写入:{tokens}",
|
"076832c1b2de22c3": "缓存写入:{tokens}",
|
||||||
"07879e064ae16542": "输出推算:{tokens}",
|
"07879e064ae16542": "输出推算:{tokens}",
|
||||||
"07c657ed4747126e": "Anthropic 额外参数",
|
"07c657ed4747126e": "Anthropic 额外参数",
|
||||||
@@ -50,6 +51,7 @@
|
|||||||
"22d7895ea5fca72e": "按供应商",
|
"22d7895ea5fca72e": "按供应商",
|
||||||
"23ae7a90b1b9816d": "清理范围",
|
"23ae7a90b1b9816d": "清理范围",
|
||||||
"23e49479e15e6770": "发现新版本 {version}",
|
"23e49479e15e6770": "发现新版本 {version}",
|
||||||
|
"2400fbd0aeab9e13": "已下载 {downloaded}",
|
||||||
"24a0a24864454575": "已存在,跳过",
|
"24a0a24864454575": "已存在,跳过",
|
||||||
"2555d6c7fbb7e070": "可以直接输入模型标识,也可以读取接口返回的模型列表。",
|
"2555d6c7fbb7e070": "可以直接输入模型标识,也可以读取接口返回的模型列表。",
|
||||||
"29585d7193539200": "当前版本 {version}",
|
"29585d7193539200": "当前版本 {version}",
|
||||||
@@ -68,6 +70,7 @@
|
|||||||
"2f7dec3be28d7597": "已选择 {count} 个",
|
"2f7dec3be28d7597": "已选择 {count} 个",
|
||||||
"2f9daa828907b93f": "删除",
|
"2f9daa828907b93f": "删除",
|
||||||
"346ff60e6c7c5181": "读取中…",
|
"346ff60e6c7c5181": "读取中…",
|
||||||
|
"35fcbd57d58a9394": "插件管理",
|
||||||
"36f33adaf0942634": "确认",
|
"36f33adaf0942634": "确认",
|
||||||
"37125ef2e1d707cb": "服务器地址或完整请求 URL、API Key、模型名称、显示名称和备注不能为空",
|
"37125ef2e1d707cb": "服务器地址或完整请求 URL、API Key、模型名称、显示名称和备注不能为空",
|
||||||
"378bb0eec39fa8a2": "最后一页",
|
"378bb0eec39fa8a2": "最后一页",
|
||||||
@@ -85,9 +88,11 @@
|
|||||||
"3cfae5728b92b334": "Token 用量:{tokens}",
|
"3cfae5728b92b334": "Token 用量:{tokens}",
|
||||||
"3d13868593ae4eeb": "界面语言",
|
"3d13868593ae4eeb": "界面语言",
|
||||||
"3da0bf1610ff5db5": "推荐内容",
|
"3da0bf1610ff5db5": "推荐内容",
|
||||||
|
"3e0b34ddc2121f7d": "运行平台",
|
||||||
"3f6c25aa329163a4": "原接口路径会追加到此服务地址。",
|
"3f6c25aa329163a4": "原接口路径会追加到此服务地址。",
|
||||||
"3fd118e2ffe0b2b6": "取消全部测试",
|
"3fd118e2ffe0b2b6": "取消全部测试",
|
||||||
"3fd47edce45b3603": "关闭",
|
"3fd47edce45b3603": "关闭",
|
||||||
|
"402495402ce333b1": "重新初始化插件",
|
||||||
"40a08e7cf320ae07": "确定要清理详细记录吗?",
|
"40a08e7cf320ae07": "确定要清理详细记录吗?",
|
||||||
"4125fc7ba333524c": "默认亮色",
|
"4125fc7ba333524c": "默认亮色",
|
||||||
"42655ed8e4108ae2": "输入(非缓存)",
|
"42655ed8e4108ae2": "输入(非缓存)",
|
||||||
@@ -104,7 +109,9 @@
|
|||||||
"497c85690c4cc0fc": "暂无数据",
|
"497c85690c4cc0fc": "暂无数据",
|
||||||
"499c729eb09aa2a6": "上下文窗口 Token",
|
"499c729eb09aa2a6": "上下文窗口 Token",
|
||||||
"49be72e6045c007d": "取消测试",
|
"49be72e6045c007d": "取消测试",
|
||||||
|
"4a861200ad513a3c": "初始化插件运行时",
|
||||||
"4a8d6841b4023edf": "确认导入",
|
"4a8d6841b4023edf": "确认导入",
|
||||||
|
"4aca6a31090fe2b8": "初始化中…",
|
||||||
"4b458e6e147221d7": "系统会根据请求协议自动追加标准端点路径。",
|
"4b458e6e147221d7": "系统会根据请求协议自动追加标准端点路径。",
|
||||||
"4d0680f9efaef147": "未读",
|
"4d0680f9efaef147": "未读",
|
||||||
"4e30d7c9ed2b0eee": "不设置",
|
"4e30d7c9ed2b0eee": "不设置",
|
||||||
@@ -127,6 +134,7 @@
|
|||||||
"5b17f59d33bde39e": "错误:{error}",
|
"5b17f59d33bde39e": "错误:{error}",
|
||||||
"5ba65a74c4e792c5": "按类型",
|
"5ba65a74c4e792c5": "按类型",
|
||||||
"5c55a67935af8f45": "全部",
|
"5c55a67935af8f45": "全部",
|
||||||
|
"5c62e36c152dfc7c": "插件运行时初始化完成",
|
||||||
"5d59857bf039cac9": "Cursor 助手 v{version}",
|
"5d59857bf039cac9": "Cursor 助手 v{version}",
|
||||||
"5f8d556a9c47da3c": "已关闭开机启动",
|
"5f8d556a9c47da3c": "已关闭开机启动",
|
||||||
"5f9acfb945229062": "你确认不想再看到此广告吗?",
|
"5f9acfb945229062": "你确认不想再看到此广告吗?",
|
||||||
@@ -163,6 +171,7 @@
|
|||||||
"7a2229f6a6d330a5": "请在桌面应用中打开终端安装 CA",
|
"7a2229f6a6d330a5": "请在桌面应用中打开终端安装 CA",
|
||||||
"7a3cec4ca715de80": "调用统计",
|
"7a3cec4ca715de80": "调用统计",
|
||||||
"7ba2d6728fe2531b": "确认清理",
|
"7ba2d6728fe2531b": "确认清理",
|
||||||
|
"7c10d97162c96dbd": "正在验证插件运行时",
|
||||||
"7cea2f3c46565d29": "OpenAI 额外参数",
|
"7cea2f3c46565d29": "OpenAI 额外参数",
|
||||||
"7d9f043f8f7ab45c": "发现新版本 {version},可在设置中安装",
|
"7d9f043f8f7ab45c": "发现新版本 {version},可在设置中安装",
|
||||||
"7e0891860c9e6374": "TAB 服务地址不能为空",
|
"7e0891860c9e6374": "TAB 服务地址不能为空",
|
||||||
@@ -170,7 +179,9 @@
|
|||||||
"7f3c8312816fe26a": "刷新中…",
|
"7f3c8312816fe26a": "刷新中…",
|
||||||
"7f68ebad19ba6bcd": "检查更新",
|
"7f68ebad19ba6bcd": "检查更新",
|
||||||
"811a3b22a5a7f2d5": "无法连接本地管理服务",
|
"811a3b22a5a7f2d5": "无法连接本地管理服务",
|
||||||
|
"8213941f12320ce1": "当前操作系统或 CPU 架构暂不受支持",
|
||||||
"83c4efccd9a6bf69": "连通性测试已取消:成功 {successful},失败 {failed}",
|
"83c4efccd9a6bf69": "连通性测试已取消:成功 {successful},失败 {failed}",
|
||||||
|
"83e8d0b7aff2b394": "已下载 {downloaded} / {total}",
|
||||||
"83fcfb4c1f2c1641": "获取模型",
|
"83fcfb4c1f2c1641": "获取模型",
|
||||||
"842b9f11cdd96bda": "开机启动",
|
"842b9f11cdd96bda": "开机启动",
|
||||||
"843ac7e15a5047a7": "确认导入旧版模型配置",
|
"843ac7e15a5047a7": "确认导入旧版模型配置",
|
||||||
@@ -178,6 +189,7 @@
|
|||||||
"86b7355ec3bd55ef": "隐藏 API Key",
|
"86b7355ec3bd55ef": "隐藏 API Key",
|
||||||
"8716e1344b0daddb": "Cursor 官方",
|
"8716e1344b0daddb": "Cursor 官方",
|
||||||
"878a8ab176429a86": "查看说明",
|
"878a8ab176429a86": "查看说明",
|
||||||
|
"8911e4f1407d58cb": "正在下载插件运行时",
|
||||||
"89a101b809be7cfc": "系统会原样使用此地址,不追加或修改请求路径。",
|
"89a101b809be7cfc": "系统会原样使用此地址,不追加或修改请求路径。",
|
||||||
"8a8542f6964852dc": "下一页",
|
"8a8542f6964852dc": "下一页",
|
||||||
"8b6ff498515bcc2f": "时间",
|
"8b6ff498515bcc2f": "时间",
|
||||||
@@ -192,14 +204,17 @@
|
|||||||
"91aaf184cfc17ffd": "数据概览",
|
"91aaf184cfc17ffd": "数据概览",
|
||||||
"92156a483d4ba248": "仅删除请求、响应和追踪附件等详细内容,保留调用汇总、统计指标和配置。",
|
"92156a483d4ba248": "仅删除请求、响应和追踪附件等详细内容,保留调用汇总、统计指标和配置。",
|
||||||
"940a168911ade998": "每页条数",
|
"940a168911ade998": "每页条数",
|
||||||
|
"945fb1c67eca8493": "正在安装插件运行时",
|
||||||
"946b3ffc02f026c0": "确定删除这个模型吗?",
|
"946b3ffc02f026c0": "确定删除这个模型吗?",
|
||||||
"966498853d801a52": "TAB 选择",
|
"966498853d801a52": "TAB 选择",
|
||||||
"9850ed41a5bfbb0c": "已选 {count} 项",
|
"9850ed41a5bfbb0c": "已选 {count} 项",
|
||||||
"997ec8201c2adeda": "打开终端安装 CA",
|
"997ec8201c2adeda": "打开终端安装 CA",
|
||||||
"9b1b7ed518ee401d": "将在系统浏览器中打开使用教程,是否继续?",
|
"9b1b7ed518ee401d": "将在系统浏览器中打开使用教程,是否继续?",
|
||||||
"9c41b3a9e12ac994": "思考强度",
|
"9c41b3a9e12ac994": "思考强度",
|
||||||
|
"9db205c6055bacc4": "插件运行时初始化失败",
|
||||||
"9e356080c56877f8": "已关闭静默启动",
|
"9e356080c56877f8": "已关闭静默启动",
|
||||||
"9e46da6923836182": "如:2026-08-23 09:00、1小时前",
|
"9e46da6923836182": "如:2026-08-23 09:00、1小时前",
|
||||||
|
"9ed11266ead88f5b": "正在验证插件运行时下载文件",
|
||||||
"9f6fee1aba17a565": "语言",
|
"9f6fee1aba17a565": "语言",
|
||||||
"9fb48101d237ff96": "近一周",
|
"9fb48101d237ff96": "近一周",
|
||||||
"a026f37e613cf48b": "输出 Token",
|
"a026f37e613cf48b": "输出 Token",
|
||||||
@@ -220,6 +235,7 @@
|
|||||||
"a7617f42f898b2bf": "使用完整请求地址",
|
"a7617f42f898b2bf": "使用完整请求地址",
|
||||||
"a8036485f9227f2c": "拖动排序",
|
"a8036485f9227f2c": "拖动排序",
|
||||||
"a98585871c5313ff": "显示名称",
|
"a98585871c5313ff": "显示名称",
|
||||||
|
"ab27f80d046f3d7f": "已就绪",
|
||||||
"ab9084a640fbb864": "全不选",
|
"ab9084a640fbb864": "全不选",
|
||||||
"abecab6701177721": "已开启开机启动",
|
"abecab6701177721": "已开启开机启动",
|
||||||
"ac58d0f9a3f8d389": "请输入模型备注",
|
"ac58d0f9a3f8d389": "请输入模型备注",
|
||||||
@@ -230,7 +246,9 @@
|
|||||||
"aee88743413144a2": "刷新",
|
"aee88743413144a2": "刷新",
|
||||||
"b06325c5660f0c29": "直连",
|
"b06325c5660f0c29": "直连",
|
||||||
"b16c3b2ecedd6fe1": "Cursor 接管已生效;添加模型配置后即可使用 BYOK 模型。",
|
"b16c3b2ecedd6fe1": "Cursor 接管已生效;添加模型配置后即可使用 BYOK 模型。",
|
||||||
|
"b254ff315d861346": "请重试初始化",
|
||||||
"b4411558b932266f": "上游类型",
|
"b4411558b932266f": "上游类型",
|
||||||
|
"b4c9e08870d41aa2": "需要先初始化插件运行时",
|
||||||
"b502b1d414664337": "提示词:{tokens}",
|
"b502b1d414664337": "提示词:{tokens}",
|
||||||
"b5141d3d19e9a048": "是",
|
"b5141d3d19e9a048": "是",
|
||||||
"b6725f218ebaef26": "已显示 Dock 栏图标",
|
"b6725f218ebaef26": "已显示 Dock 栏图标",
|
||||||
@@ -247,10 +265,13 @@
|
|||||||
"bb2b7736433ae867": "Cursor 追踪",
|
"bb2b7736433ae867": "Cursor 追踪",
|
||||||
"bb7efdcb6af6e805": "默认暗色",
|
"bb7efdcb6af6e805": "默认暗色",
|
||||||
"bda62ce1d5e4ace9": "可以告诉我们原因",
|
"bda62ce1d5e4ace9": "可以告诉我们原因",
|
||||||
|
"bda74b5674b6a57d": "初始化插件",
|
||||||
"bf57afd709694b55": "概览时间范围",
|
"bf57afd709694b55": "概览时间范围",
|
||||||
"bfc01caf9fe0c841": "缓存命中率 {rate}",
|
"bfc01caf9fe0c841": "缓存命中率 {rate}",
|
||||||
|
"c0b3fbff51ccc40b": "完成",
|
||||||
"c1e98892a77f7a19": "{count} 条/页",
|
"c1e98892a77f7a19": "{count} 条/页",
|
||||||
"c3760858cdb6d9f4": "请求体",
|
"c3760858cdb6d9f4": "请求体",
|
||||||
|
"c54863655e879b36": "当前系统不支持插件运行时",
|
||||||
"c7ea2c9bc43134bd": "编辑模型",
|
"c7ea2c9bc43134bd": "编辑模型",
|
||||||
"c8c14507b2d37395": "推理强度",
|
"c8c14507b2d37395": "推理强度",
|
||||||
"c8df3c14a003bfcd": "无法加载调用详情",
|
"c8df3c14a003bfcd": "无法加载调用详情",
|
||||||
@@ -258,6 +279,7 @@
|
|||||||
"c9b9ae7a61444ab7": "上一页",
|
"c9b9ae7a61444ab7": "上一页",
|
||||||
"c9d146d006993cc1": "缓存统计策略:默认口径({rate})",
|
"c9d146d006993cc1": "缓存统计策略:默认口径({rate})",
|
||||||
"cb2f1709f983d2f4": "模型名称",
|
"cb2f1709f983d2f4": "模型名称",
|
||||||
|
"cb99f0138b032687": "初始化将下载并安装插件运行时。",
|
||||||
"cea1aafe9416de7b": "请求头",
|
"cea1aafe9416de7b": "请求头",
|
||||||
"cfae1a14d2120c57": "详细模式",
|
"cfae1a14d2120c57": "详细模式",
|
||||||
"cfe085015632e9c8": "桌面前端连接的本地管理服务端口;填写 0 时启动时随机选择。",
|
"cfe085015632e9c8": "桌面前端连接的本地管理服务端口;填写 0 时启动时随机选择。",
|
||||||
@@ -271,6 +293,8 @@
|
|||||||
"d58c88688e1a949d": "常用预设",
|
"d58c88688e1a949d": "常用预设",
|
||||||
"d60669bb26a22f5d": "留空使用默认值",
|
"d60669bb26a22f5d": "留空使用默认值",
|
||||||
"d6b1f203680f5496": "留空使用 adaptive thinking",
|
"d6b1f203680f5496": "留空使用 adaptive thinking",
|
||||||
|
"d6e61888b07853ea": "高级",
|
||||||
|
"d766536c18e8e990": "插件运行时 {version} 已安装,可以开始使用插件。",
|
||||||
"d86fa42c3848c680": "使用系统代理",
|
"d86fa42c3848c680": "使用系统代理",
|
||||||
"d8c47e9776cf1082": "主菜单",
|
"d8c47e9776cf1082": "主菜单",
|
||||||
"da521d1c1cbd36af": "需要授权安装证书",
|
"da521d1c1cbd36af": "需要授权安装证书",
|
||||||
@@ -299,6 +323,7 @@
|
|||||||
"e59ae97924d62f01": "第一页",
|
"e59ae97924d62f01": "第一页",
|
||||||
"e5b9961a0d5242e3": "端口设置已保存,重启软件后生效",
|
"e5b9961a0d5242e3": "端口设置已保存,重启软件后生效",
|
||||||
"e77e3d58b0dcffaa": "耗时",
|
"e77e3d58b0dcffaa": "耗时",
|
||||||
|
"e7cef7b834f301e7": "插件运行时版本",
|
||||||
"e825a2a42c22380e": "模型类型",
|
"e825a2a42c22380e": "模型类型",
|
||||||
"e828bd3a0151edc2": "需要在系统中信任本地 CA",
|
"e828bd3a0151edc2": "需要在系统中信任本地 CA",
|
||||||
"e8b1268c1e3610f2": "已存在",
|
"e8b1268c1e3610f2": "已存在",
|
||||||
@@ -313,6 +338,7 @@
|
|||||||
"f2bdc88464c51c2e": "显示 API Key",
|
"f2bdc88464c51c2e": "显示 API Key",
|
||||||
"f4694c46b1e19602": "最终请求类型",
|
"f4694c46b1e19602": "最终请求类型",
|
||||||
"f4dcb6a3ceb32247": "第 {page} / {count} 页",
|
"f4dcb6a3ceb32247": "第 {page} / {count} 页",
|
||||||
|
"f4f4c81a4d719711": "插件运行时",
|
||||||
"f4fa9f31ea2ae58d": "过去一年的 Token 用量日历",
|
"f4fa9f31ea2ae58d": "过去一年的 Token 用量日历",
|
||||||
"f50276449943286c": "结束时间",
|
"f50276449943286c": "结束时间",
|
||||||
"f69273dbbebfb3a1": "格式化",
|
"f69273dbbebfb3a1": "格式化",
|
||||||
@@ -323,8 +349,10 @@
|
|||||||
"f9aa11dbb15ce647": "周六",
|
"f9aa11dbb15ce647": "周六",
|
||||||
"f9b55ca75425161b": "未记录响应内容,请开启详细记录后重试。",
|
"f9b55ca75425161b": "未记录响应内容,请开启详细记录后重试。",
|
||||||
"fa5b4b8a751c7d1b": "Cursor 使用的本地代理端口;填写 0 时启动时随机选择。",
|
"fa5b4b8a751c7d1b": "Cursor 使用的本地代理端口;填写 0 时启动时随机选择。",
|
||||||
|
"fad86bf65f72c747": "下载进度",
|
||||||
"fb11aa6f29827095": "检查中…",
|
"fb11aa6f29827095": "检查中…",
|
||||||
"fbe8778fa8b9bab5": "需要先初始化本地 CA",
|
"fbe8778fa8b9bab5": "需要先初始化本地 CA",
|
||||||
|
"fc22d1ab9ac73c6f": "正在检查插件运行时",
|
||||||
"fc3947ebe6b2177b": "默认 {defaultRate} / 计入创建 {reuseRate}",
|
"fc3947ebe6b2177b": "默认 {defaultRate} / 计入创建 {reuseRate}",
|
||||||
"fcd311fd8ad42462": "打开模型列表",
|
"fcd311fd8ad42462": "打开模型列表",
|
||||||
"fd415f8e0097c832": "缓存读写已计入提示词侧统计。",
|
"fd415f8e0097c832": "缓存读写已计入提示词侧统计。",
|
||||||
|
|||||||
@@ -148,6 +148,19 @@ export interface DesktopSettings {
|
|||||||
show_dock_icon: boolean;
|
show_dock_icon: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PluginRuntimeState = "uninitialized" | "initializing" | "ready" | "failed" | "unsupported";
|
||||||
|
export type PluginRuntimePhase = "checking" | "downloading" | "verifying" | "installing" | "validating";
|
||||||
|
|
||||||
|
export interface PluginRuntimeStatus {
|
||||||
|
state: PluginRuntimeState;
|
||||||
|
version: string;
|
||||||
|
target: string | null;
|
||||||
|
phase: PluginRuntimePhase | null;
|
||||||
|
downloaded_bytes: number;
|
||||||
|
total_bytes: number | null;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface OverviewMetrics {
|
export interface OverviewMetrics {
|
||||||
llm_calls: number;
|
llm_calls: number;
|
||||||
successful_calls: number;
|
successful_calls: number;
|
||||||
@@ -309,6 +322,9 @@ export const api = {
|
|||||||
},
|
},
|
||||||
cursorHarness: () => request<CursorHarnessStatus>("/harness/cursor/status"),
|
cursorHarness: () => request<CursorHarnessStatus>("/harness/cursor/status"),
|
||||||
initializeCursorCa: () => request<CursorHarnessStatus>("/harness/cursor/ca/initialize", { method: "POST" }),
|
initializeCursorCa: () => request<CursorHarnessStatus>("/harness/cursor/ca/initialize", { method: "POST" }),
|
||||||
|
pluginRuntime: () => request<PluginRuntimeStatus>("/plugins/runtime"),
|
||||||
|
initializePluginRuntime: () => request<PluginRuntimeStatus>("/plugins/runtime", { method: "POST" }),
|
||||||
|
cancelPluginRuntimeInitialization: () => request<PluginRuntimeStatus>("/plugins/runtime", { method: "DELETE" }),
|
||||||
openCursorCaInstallTerminal: async (command: string) => {
|
openCursorCaInstallTerminal: async (command: string) => {
|
||||||
if (!packagedDesktop) throw new Error(t("请在桌面应用中打开终端安装 CA"));
|
if (!packagedDesktop) throw new Error(t("请在桌面应用中打开终端安装 CA"));
|
||||||
const { invoke } = await import("@tauri-apps/api/core");
|
const { invoke } = await import("@tauri-apps/api/core");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
import { api, type CursorHarnessStatus, type LlmCall, type Model, type ModelInput, type Overview, type PortSettings } from "../api";
|
import { api, type CursorHarnessStatus, type LlmCall, type Model, type ModelInput, type Overview, type PluginRuntimeStatus, type PortSettings } from "../api";
|
||||||
import { applyTheme, isThemeId, type ThemeId } from "../theme/theme";
|
import { applyTheme, isThemeId, type ThemeId } from "../theme/theme";
|
||||||
|
|
||||||
export type AppSnapshot = {
|
export type AppSnapshot = {
|
||||||
@@ -13,6 +13,7 @@ export type AppSnapshot = {
|
|||||||
theme: ThemeId;
|
theme: ThemeId;
|
||||||
cursorHarness: CursorHarnessStatus | null;
|
cursorHarness: CursorHarnessStatus | null;
|
||||||
cursorBusy: boolean;
|
cursorBusy: boolean;
|
||||||
|
pluginRuntime: PluginRuntimeStatus | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedTheme = (): ThemeId => {
|
const savedTheme = (): ThemeId => {
|
||||||
@@ -45,6 +46,7 @@ let snapshot: AppSnapshot = {
|
|||||||
theme: savedTheme(),
|
theme: savedTheme(),
|
||||||
cursorHarness: null,
|
cursorHarness: null,
|
||||||
cursorBusy: false,
|
cursorBusy: false,
|
||||||
|
pluginRuntime: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const listeners = new Set<() => void>();
|
const listeners = new Set<() => void>();
|
||||||
@@ -73,15 +75,16 @@ export const appStore = {
|
|||||||
async refresh() {
|
async refresh() {
|
||||||
update({ busy: true, error: null });
|
update({ busy: true, error: null });
|
||||||
try {
|
try {
|
||||||
const [models, calls, overview, settings, ports, cursorHarness] = await Promise.all([
|
const [models, calls, overview, settings, ports, cursorHarness, pluginRuntime] = await Promise.all([
|
||||||
api.models(),
|
api.models(),
|
||||||
api.calls(),
|
api.calls(),
|
||||||
api.overview(),
|
api.overview(),
|
||||||
api.observability(),
|
api.observability(),
|
||||||
api.ports(),
|
api.ports(),
|
||||||
api.cursorHarness(),
|
api.cursorHarness(),
|
||||||
|
api.pluginRuntime(),
|
||||||
]);
|
]);
|
||||||
update({ models, calls, overview, detailed: settings.detailed, ports, cursorHarness });
|
update({ models, calls, overview, detailed: settings.detailed, ports, cursorHarness, pluginRuntime });
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
update({ error: cause instanceof Error ? cause.message : String(cause) });
|
update({ error: cause instanceof Error ? cause.message : String(cause) });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -107,6 +110,37 @@ export const appStore = {
|
|||||||
return null;
|
return null;
|
||||||
} finally { update({ cursorBusy: false }); }
|
} finally { update({ cursorBusy: false }); }
|
||||||
},
|
},
|
||||||
|
async initializePluginRuntime() {
|
||||||
|
update({ error: null });
|
||||||
|
try {
|
||||||
|
const pluginRuntime = await api.initializePluginRuntime();
|
||||||
|
update({ pluginRuntime });
|
||||||
|
return pluginRuntime;
|
||||||
|
} catch (cause) {
|
||||||
|
update({ error: cause instanceof Error ? cause.message : String(cause) });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async refreshPluginRuntime() {
|
||||||
|
try {
|
||||||
|
const pluginRuntime = await api.pluginRuntime();
|
||||||
|
update({ pluginRuntime });
|
||||||
|
return pluginRuntime;
|
||||||
|
} catch (cause) {
|
||||||
|
update({ error: cause instanceof Error ? cause.message : String(cause) });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async cancelPluginRuntimeInitialization() {
|
||||||
|
try {
|
||||||
|
const pluginRuntime = await api.cancelPluginRuntimeInitialization();
|
||||||
|
update({ pluginRuntime });
|
||||||
|
return pluginRuntime;
|
||||||
|
} catch (cause) {
|
||||||
|
update({ error: cause instanceof Error ? cause.message : String(cause) });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
async setCursorEnabled(enabled: boolean) {
|
async setCursorEnabled(enabled: boolean) {
|
||||||
update({ cursorBusy: true, error: null });
|
update({ cursorBusy: true, error: null });
|
||||||
try { update({ cursorHarness: await api.setCursorEnabled(enabled) }); }
|
try { update({ cursorHarness: await api.setCursorEnabled(enabled) }); }
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export const flatColorComboChartIcon = icon('<path fill="#00BCD4" d="M37 18h6v24
|
|||||||
export const flatColorDataConfigurationIcon = icon('<path fill="#D1C4E9" d="M38 7H10c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h28c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2m0 12H10c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h28c1.1 0 2-.9 2-2v-6c0-1.1-.9-2-2-2m0 12H10c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h28c1.1 0 2-.9 2-2v-6c0-1.1-.9-2-2-2"/><path fill="#607D8B" d="M45.2 38.1c.1-.4.1-.8.1-1.1s0-.8-.1-1.1l2.3-1.7c.2-.2.3-.5.2-.7l-2.3-3.9c-.1-.2-.4-.3-.7-.2l-2.6 1.2c-.6-.5-1.3-.9-2-1.2l-.3-2.9c0-.3-.3-.5-.5-.5h-4.5c-.3 0-.5.2-.5.5l-.3 2.9c-.7.3-1.4.7-2 1.2l-2.6-1.2c-.3-.1-.6 0-.7.2l-2.3 3.9c-.1.2-.1.6.2.7l2.3 1.7c-.1.4-.1.8-.1 1.1s0 .8.1 1.1l-2.3 1.7c-.2.2-.3.5-.2.7l2.3 3.9c.1.2.4.3.7.2l2.6-1.2c.6.5 1.3.9 2 1.2l.3 2.9c0 .3.3.5.5.5h4.5c.3 0 .5-.2.5-.5l.3-2.9c.7-.3 1.4-.7 2-1.2l2.6 1.2c.3.1.6 0 .7-.2l2.3-3.9c.1-.2.1-.6-.2-.7zM37 42.2c-2.9 0-5.2-2.3-5.2-5.2s2.3-5.2 5.2-5.2s5.2 2.3 5.2 5.2s-2.3 5.2-5.2 5.2"/><path fill="#455A64" d="M37 31c-3.3 0-6 2.7-6 6s2.7 6 6 6s6-2.7 6-6s-2.7-6-6-6m0 9c-1.7 0-3-1.3-3-3s1.3-3 3-3s3 1.3 3 3s-1.3 3-3 3"/>', 48, 48);
|
export const flatColorDataConfigurationIcon = icon('<path fill="#D1C4E9" d="M38 7H10c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h28c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2m0 12H10c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h28c1.1 0 2-.9 2-2v-6c0-1.1-.9-2-2-2m0 12H10c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h28c1.1 0 2-.9 2-2v-6c0-1.1-.9-2-2-2"/><path fill="#607D8B" d="M45.2 38.1c.1-.4.1-.8.1-1.1s0-.8-.1-1.1l2.3-1.7c.2-.2.3-.5.2-.7l-2.3-3.9c-.1-.2-.4-.3-.7-.2l-2.6 1.2c-.6-.5-1.3-.9-2-1.2l-.3-2.9c0-.3-.3-.5-.5-.5h-4.5c-.3 0-.5.2-.5.5l-.3 2.9c-.7.3-1.4.7-2 1.2l-2.6-1.2c-.3-.1-.6 0-.7.2l-2.3 3.9c-.1.2-.1.6.2.7l2.3 1.7c-.1.4-.1.8-.1 1.1s0 .8.1 1.1l-2.3 1.7c-.2.2-.3.5-.2.7l2.3 3.9c.1.2.4.3.7.2l2.6-1.2c.6.5 1.3.9 2 1.2l.3 2.9c0 .3.3.5.5.5h4.5c.3 0 .5-.2.5-.5l.3-2.9c.7-.3 1.4-.7 2-1.2l2.6 1.2c.3.1.6 0 .7-.2l2.3-3.9c.1-.2.1-.6-.2-.7zM37 42.2c-2.9 0-5.2-2.3-5.2-5.2s2.3-5.2 5.2-5.2s5.2 2.3 5.2 5.2s-2.3 5.2-5.2 5.2"/><path fill="#455A64" d="M37 31c-3.3 0-6 2.7-6 6s2.7 6 6 6s6-2.7 6-6s-2.7-6-6-6m0 9c-1.7 0-3-1.3-3-3s1.3-3 3-3s3 1.3 3 3s-1.3 3-3 3"/>', 48, 48);
|
||||||
export const flatColorSettingsIcon = icon('<path fill="#607D8B" d="M39.6 27.2c.1-.7.2-1.4.2-2.2s-.1-1.5-.2-2.2l4.5-3.2c.4-.3.6-.9.3-1.4L40 10.8c-.3-.5-.8-.7-1.3-.4l-5 2.3c-1.2-.9-2.4-1.6-3.8-2.2L29.4 5c-.1-.5-.5-.9-1-.9h-8.6c-.5 0-1 .4-1 .9l-.5 5.5c-1.4.6-2.7 1.3-3.8 2.2l-5-2.3c-.5-.2-1.1 0-1.3.4l-4.3 7.4c-.3.5-.1 1.1.3 1.4l4.5 3.2c-.1.7-.2 1.4-.2 2.2s.1 1.5.2 2.2L4 30.4c-.4.3-.6.9-.3 1.4L8 39.2c.3.5.8.7 1.3.4l5-2.3c1.2.9 2.4 1.6 3.8 2.2l.5 5.5c.1.5.5.9 1 .9h8.6c.5 0 1-.4 1-.9l.5-5.5c1.4-.6 2.7-1.3 3.8-2.2l5 2.3c.5.2 1.1 0 1.3-.4l4.3-7.4c.3-.5.1-1.1-.3-1.4zM24 35c-5.5 0-10-4.5-10-10s4.5-10 10-10s10 4.5 10 10s-4.5 10-10 10"/><path fill="#455A64" d="M24 13c-6.6 0-12 5.4-12 12s5.4 12 12 12s12-5.4 12-12s-5.4-12-12-12m0 17c-2.8 0-5-2.2-5-5s2.2-5 5-5s5 2.2 5 5s-2.2 5-5 5"/>', 48, 48);
|
export const flatColorSettingsIcon = icon('<path fill="#607D8B" d="M39.6 27.2c.1-.7.2-1.4.2-2.2s-.1-1.5-.2-2.2l4.5-3.2c.4-.3.6-.9.3-1.4L40 10.8c-.3-.5-.8-.7-1.3-.4l-5 2.3c-1.2-.9-2.4-1.6-3.8-2.2L29.4 5c-.1-.5-.5-.9-1-.9h-8.6c-.5 0-1 .4-1 .9l-.5 5.5c-1.4.6-2.7 1.3-3.8 2.2l-5-2.3c-.5-.2-1.1 0-1.3.4l-4.3 7.4c-.3.5-.1 1.1.3 1.4l4.5 3.2c-.1.7-.2 1.4-.2 2.2s.1 1.5.2 2.2L4 30.4c-.4.3-.6.9-.3 1.4L8 39.2c.3.5.8.7 1.3.4l5-2.3c1.2.9 2.4 1.6 3.8 2.2l.5 5.5c.1.5.5.9 1 .9h8.6c.5 0 1-.4 1-.9l.5-5.5c1.4-.6 2.7-1.3 3.8-2.2l5 2.3c.5.2 1.1 0 1.3-.4l4.3-7.4c.3-.5.1-1.1-.3-1.4zM24 35c-5.5 0-10-4.5-10-10s4.5-10 10-10s10 4.5 10 10s-4.5 10-10 10"/><path fill="#455A64" d="M24 13c-6.6 0-12 5.4-12 12s5.4 12 12 12s12-5.4 12-12s-5.4-12-12-12m0 17c-2.8 0-5-2.2-5-5s2.2-5 5-5s5 2.2 5 5s-2.2 5-5 5"/>', 48, 48);
|
||||||
|
|
||||||
|
export const flatColorBriefcaseIcon = icon('<path fill="#424242" d="M27 7h-6c-1.7 0-3 1.3-3 3v3h2v-3c0-.6.4-1 1-1h6c.6 0 1 .4 1 1v3h2v-3c0-1.7-1.3-3-3-3"/><path fill="#E65100" d="M40 43H8c-2.2 0-4-1.8-4-4V15c0-2.2 1.8-4 4-4h32c2.2 0 4 1.8 4 4v24c0 2.2-1.8 4-4 4"/><path fill="#FF6E40" d="M40 28H8c-2.2 0-4-1.8-4-4v-9c0-2.2 1.8-4 4-4h32c2.2 0 4 1.8 4 4v9c0 2.2-1.8 4-4 4"/><path fill="#FFF3E0" d="M26 26h-4c-.6 0-1-.4-1-1v-2c0-.6.4-1 1-1h4c.6 0 1 .4 1 1v2c0 .6-.4 1-1 1"/>', 48, 48); // flat-color-icons:briefcase
|
||||||
|
|
||||||
export const claudeIcon = icon('<path fill="#d97757" d="m50.228 170.321l50.357-28.257l.843-2.463l-.843-1.361h-2.462l-8.426-.518l-28.775-.778l-24.952-1.037l-24.175-1.296l-6.092-1.297L0 125.796l.583-3.759l5.12-3.434l7.324.648l16.202 1.101l24.304 1.685l17.629 1.037l26.118 2.722h4.148l.583-1.685l-1.426-1.037l-1.101-1.037l-25.147-17.045l-27.22-18.017l-14.258-10.37l-7.713-5.25l-3.888-4.925l-1.685-10.758l7-7.713l9.397.649l2.398.648l9.527 7.323l20.35 15.75L94.817 91.9l3.889 3.24l1.555-1.102l.195-.777l-1.75-2.917l-14.453-26.118l-15.425-26.572l-6.87-11.018l-1.814-6.61c-.648-2.723-1.102-4.991-1.102-7.778l7.972-10.823L71.42 0l10.63 1.426l4.472 3.888l6.61 15.101l10.694 23.786l16.591 32.34l4.861 9.592l2.592 8.879l.973 2.722h1.685v-1.556l1.36-18.211l2.528-22.36l2.463-28.776l.843-8.1l4.018-9.722l7.971-5.25l6.222 2.981l5.12 7.324l-.713 4.73l-3.046 19.768l-5.962 30.98l-3.889 20.739h2.268l2.593-2.593l10.499-13.934l17.628-22.036l7.778-8.749l9.073-9.657l5.833-4.601h11.018l8.1 12.055l-3.628 12.443l-11.342 14.388l-9.398 12.184l-13.48 18.147l-8.426 14.518l.778 1.166l2.01-.194l30.46-6.481l16.462-2.982l19.637-3.37l8.88 4.148l.971 4.213l-3.5 8.62l-20.998 5.184l-24.628 4.926l-36.682 8.685l-.454.324l.519.648l16.526 1.555l7.065.389h17.304l32.21 2.398l8.426 5.574l5.055 6.805l-.843 5.184l-12.962 6.611l-17.498-4.148l-40.83-9.721l-14-3.5h-1.944v1.167l11.666 11.406l21.387 19.314l26.767 24.887l1.36 6.157l-3.434 4.86l-3.63-.518l-23.526-17.693l-9.073-7.972l-20.545-17.304h-1.36v1.814l4.73 6.935l25.017 37.59l1.296 11.536l-1.814 3.76l-6.481 2.268l-7.13-1.297l-14.647-20.544l-15.1-23.138l-12.185-20.739l-1.49.843l-7.194 77.448l-3.37 3.953l-7.778 2.981l-6.48-4.925l-3.436-7.972l3.435-15.749l4.148-20.544l3.37-16.333l3.046-20.285l1.815-6.74l-.13-.454l-1.49.194l-15.295 20.999l-23.267 31.433l-18.406 19.702l-4.407 1.75l-7.648-3.954l.713-7.064l4.277-6.286l25.47-32.405l15.36-20.092l9.917-11.6l-.065-1.686h-.583L44.07 198.125l-12.055 1.555l-5.185-4.86l.648-7.972l2.463-2.593l20.35-13.999z"/>', 256, 257);
|
export const claudeIcon = icon('<path fill="#d97757" d="m50.228 170.321l50.357-28.257l.843-2.463l-.843-1.361h-2.462l-8.426-.518l-28.775-.778l-24.952-1.037l-24.175-1.296l-6.092-1.297L0 125.796l.583-3.759l5.12-3.434l7.324.648l16.202 1.101l24.304 1.685l17.629 1.037l26.118 2.722h4.148l.583-1.685l-1.426-1.037l-1.101-1.037l-25.147-17.045l-27.22-18.017l-14.258-10.37l-7.713-5.25l-3.888-4.925l-1.685-10.758l7-7.713l9.397.649l2.398.648l9.527 7.323l20.35 15.75L94.817 91.9l3.889 3.24l1.555-1.102l.195-.777l-1.75-2.917l-14.453-26.118l-15.425-26.572l-6.87-11.018l-1.814-6.61c-.648-2.723-1.102-4.991-1.102-7.778l7.972-10.823L71.42 0l10.63 1.426l4.472 3.888l6.61 15.101l10.694 23.786l16.591 32.34l4.861 9.592l2.592 8.879l.973 2.722h1.685v-1.556l1.36-18.211l2.528-22.36l2.463-28.776l.843-8.1l4.018-9.722l7.971-5.25l6.222 2.981l5.12 7.324l-.713 4.73l-3.046 19.768l-5.962 30.98l-3.889 20.739h2.268l2.593-2.593l10.499-13.934l17.628-22.036l7.778-8.749l9.073-9.657l5.833-4.601h11.018l8.1 12.055l-3.628 12.443l-11.342 14.388l-9.398 12.184l-13.48 18.147l-8.426 14.518l.778 1.166l2.01-.194l30.46-6.481l16.462-2.982l19.637-3.37l8.88 4.148l.971 4.213l-3.5 8.62l-20.998 5.184l-24.628 4.926l-36.682 8.685l-.454.324l.519.648l16.526 1.555l7.065.389h17.304l32.21 2.398l8.426 5.574l5.055 6.805l-.843 5.184l-12.962 6.611l-17.498-4.148l-40.83-9.721l-14-3.5h-1.944v1.167l11.666 11.406l21.387 19.314l26.767 24.887l1.36 6.157l-3.434 4.86l-3.63-.518l-23.526-17.693l-9.073-7.972l-20.545-17.304h-1.36v1.814l4.73 6.935l25.017 37.59l1.296 11.536l-1.814 3.76l-6.481 2.268l-7.13-1.297l-14.647-20.544l-15.1-23.138l-12.185-20.739l-1.49.843l-7.194 77.448l-3.37 3.953l-7.778 2.981l-6.48-4.925l-3.436-7.972l3.435-15.749l4.148-20.544l3.37-16.333l3.046-20.285l1.815-6.74l-.13-.454l-1.49.194l-15.295 20.999l-23.267 31.433l-18.406 19.702l-4.407 1.75l-7.648-3.954l.713-7.064l4.277-6.286l25.47-32.405l15.36-20.092l9.917-11.6l-.065-1.686h-.583L44.07 198.125l-12.055 1.555l-5.185-4.86l.648-7.972l2.463-2.593l20.35-13.999z"/>', 256, 257);
|
||||||
export const openAiIcon = icon('<path fill="currentColor" d="M20.57 10.18c.45-1.36.3-2.85-.43-4.09a5.03 5.03 0 0 0-5.42-2.42a5.05 5.05 0 0 0-7.14-.38c-.66.59-1.15 1.35-1.43 2.19A4.98 4.98 0 0 0 2.82 7.9c-1.1 1.9-.85 4.29.62 5.91c-.45 1.36-.3 2.85.43 4.09a5.045 5.045 0 0 0 5.43 2.42A5.03 5.03 0 0 0 13.06 22c2.19 0 4.14-1.41 4.81-3.5a4.98 4.98 0 0 0 3.33-2.42a5.04 5.04 0 0 0-.62-5.89Zm-7.52 10.51c-.88 0-1.72-.31-2.4-.87l.12-.07l3.98-2.3c.2-.12.33-.33.33-.57v-5.61l1.68.97s.03.02.03.04v4.65c0 2.07-1.68 3.74-3.75 3.75ZM5 17.25c-.44-.76-.6-1.65-.45-2.51l.12.07l3.99 2.3c.2.12.45.12.65 0l4.87-2.81v1.94s-.01.04-.03.05l-4.03 2.33A3.756 3.756 0 0 1 5 17.25M3.95 8.58a3.7 3.7 0 0 1 1.97-1.64v4.73c0 .23.12.45.32.56l4.85 2.8l-1.68.97h-.06l-4.03-2.32a3.754 3.754 0 0 1-1.37-5.12zm13.83 3.21l-4.86-2.82L14.6 8h.06l4.03 2.33a3.743 3.743 0 0 1 1.37 5.12a3.8 3.8 0 0 1-1.94 1.64v-4.73a.67.67 0 0 0-.34-.56Zm1.68-2.52l-.12-.07l-3.98-2.32a.63.63 0 0 0-.65 0L9.84 9.69V7.75s0-.04.02-.05l4.03-2.32a3.75 3.75 0 0 1 5.12 1.38c.44.76.59 1.64.45 2.51v.02ZM8.93 12.72l-1.68-.97s-.03-.03-.03-.05V7.06c0-2.07 1.68-3.75 3.75-3.74c.87 0 1.72.31 2.39.87l-.12.07l-3.98 2.3c-.2.12-.33.33-.33.57v5.6Zm.91-1.97l2.17-1.25l2.17 1.25v2.5l-2.16 1.25l-2.17-1.25v-2.5Z"/>');
|
export const openAiIcon = icon('<path fill="currentColor" d="M20.57 10.18c.45-1.36.3-2.85-.43-4.09a5.03 5.03 0 0 0-5.42-2.42a5.05 5.05 0 0 0-7.14-.38c-.66.59-1.15 1.35-1.43 2.19A4.98 4.98 0 0 0 2.82 7.9c-1.1 1.9-.85 4.29.62 5.91c-.45 1.36-.3 2.85.43 4.09a5.045 5.045 0 0 0 5.43 2.42A5.03 5.03 0 0 0 13.06 22c2.19 0 4.14-1.41 4.81-3.5a4.98 4.98 0 0 0 3.33-2.42a5.04 5.04 0 0 0-.62-5.89Zm-7.52 10.51c-.88 0-1.72-.31-2.4-.87l.12-.07l3.98-2.3c.2-.12.33-.33.33-.57v-5.61l1.68.97s.03.02.03.04v4.65c0 2.07-1.68 3.74-3.75 3.75ZM5 17.25c-.44-.76-.6-1.65-.45-2.51l.12.07l3.99 2.3c.2.12.45.12.65 0l4.87-2.81v1.94s-.01.04-.03.05l-4.03 2.33A3.756 3.756 0 0 1 5 17.25M3.95 8.58a3.7 3.7 0 0 1 1.97-1.64v4.73c0 .23.12.45.32.56l4.85 2.8l-1.68.97h-.06l-4.03-2.32a3.754 3.754 0 0 1-1.37-5.12zm13.83 3.21l-4.86-2.82L14.6 8h.06l4.03 2.33a3.743 3.743 0 0 1 1.37 5.12a3.8 3.8 0 0 1-1.94 1.64v-4.73a.67.67 0 0 0-.34-.56Zm1.68-2.52l-.12-.07l-3.98-2.32a.63.63 0 0 0-.65 0L9.84 9.69V7.75s0-.04.02-.05l4.03-2.32a3.75 3.75 0 0 1 5.12 1.38c.44.76.59 1.64.45 2.51v.02ZM8.93 12.72l-1.68-.97s-.03-.03-.03-.05V7.06c0-2.07 1.68-3.75 3.75-3.74c.87 0 1.72.31 2.39.87l-.12.07l-3.98 2.3c-.2.12-.33.33-.33.57v5.6Zm.91-1.97l2.17-1.25l2.17 1.25v2.5l-2.16 1.25l-2.17-1.25v-2.5Z"/>');
|
||||||
// Menu icons intentionally use filled glyphs from different collections so they
|
// Menu icons intentionally use filled glyphs from different collections so they
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { ConfirmDialog } from "../shared/ui/ConfirmDialog";
|
|||||||
import controls from "../shared/ui/Controls.module.scss";
|
import controls from "../shared/ui/Controls.module.scss";
|
||||||
import { Icon } from "../shared/ui/Icon";
|
import { Icon } from "../shared/ui/Icon";
|
||||||
import { TooltipTrigger } from "../shared/ui/TooltipTrigger";
|
import { TooltipTrigger } from "../shared/ui/TooltipTrigger";
|
||||||
import { flatColorAboutIcon, flatColorAreaChartIcon, flatColorSalesPerformanceIcon, flatColorSettingsIcon, refreshIcon } from "../shared/ui/icons";
|
import { flatColorAboutIcon, flatColorAreaChartIcon, flatColorBriefcaseIcon, flatColorSalesPerformanceIcon, flatColorSettingsIcon, refreshIcon } from "../shared/ui/icons";
|
||||||
import { useMessage } from "../shared/ui/message";
|
import { useMessage } from "../shared/ui/message";
|
||||||
import { VirtualList } from "../shared/virtual/VirtualList";
|
import { VirtualList } from "../shared/virtual/VirtualList";
|
||||||
import { useI18n } from "../i18n/store";
|
import { useI18n } from "../i18n/store";
|
||||||
@@ -27,7 +27,7 @@ type MenuItem =
|
|||||||
| { kind: "external"; id: string; label: string; icon: IconifyIcon | string }
|
| { kind: "external"; id: string; label: string; icon: IconifyIcon | string }
|
||||||
| { kind: "group"; label: string };
|
| { kind: "group"; label: string };
|
||||||
|
|
||||||
const keptAlivePages = ["/", "/calls", "/settings", "/harness/cursor"];
|
const keptAlivePages = ["/", "/calls", "/settings", "/harness/cursor", "/plugins"];
|
||||||
const readAdStorageKey = "cursor-byok:read-ad-ids";
|
const readAdStorageKey = "cursor-byok:read-ad-ids";
|
||||||
const dismissedAdStorageKey = "cursor-byok:dismissed-ad-ids";
|
const dismissedAdStorageKey = "cursor-byok:dismissed-ad-ids";
|
||||||
const tutorialReadStorageKey = "cursor-byok:tutorial-read";
|
const tutorialReadStorageKey = "cursor-byok:tutorial-read";
|
||||||
@@ -76,6 +76,8 @@ export function AppLayout() {
|
|||||||
{ kind: "page", path: "/harness/cursor", label: t("Cursor 配置"), icon: cursorIconUrl },
|
{ kind: "page", path: "/harness/cursor", label: t("Cursor 配置"), icon: cursorIconUrl },
|
||||||
{ kind: "page", path: "/settings", label: t("系统设置"), icon: flatColorSettingsIcon },
|
{ kind: "page", path: "/settings", label: t("系统设置"), icon: flatColorSettingsIcon },
|
||||||
{ kind: "external", id: "tutorial", label: t("使用教程"), icon: flatColorAboutIcon },
|
{ kind: "external", id: "tutorial", label: t("使用教程"), icon: flatColorAboutIcon },
|
||||||
|
{ kind: "group", label: t("高级") },
|
||||||
|
{ kind: "page", path: "/plugins", label: t("插件管理"), icon: flatColorBriefcaseIcon },
|
||||||
];
|
];
|
||||||
|
|
||||||
const openTutorial = useCallback(() => {
|
const openTutorial = useCallback(() => {
|
||||||
|
|||||||
+2
-1
@@ -48,7 +48,7 @@ similar = "2"
|
|||||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal", "sync", "time", "net"] }
|
tokio = { version = "1", features = ["fs", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync", "time", "net"] }
|
||||||
tokio-stream = { version = "0.1", features = ["sync"] }
|
tokio-stream = { version = "0.1", features = ["sync"] }
|
||||||
tokio-util = "0.7"
|
tokio-util = "0.7"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
@@ -57,6 +57,7 @@ tower-http = { version = "0.6", features = ["cors", "decompression-gzip", "fs"]
|
|||||||
url = "2"
|
url = "2"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
x509-parser = "0.18"
|
x509-parser = "0.18"
|
||||||
|
zip = { version = "4", default-features = false, features = ["deflate"] }
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.13"
|
prost-build = "0.13"
|
||||||
protoc-bin-vendored = "3"
|
protoc-bin-vendored = "3"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod calls;
|
|||||||
mod harness;
|
mod harness;
|
||||||
mod models;
|
mod models;
|
||||||
mod overview;
|
mod overview;
|
||||||
|
mod plugins;
|
||||||
mod service;
|
mod service;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
@@ -136,6 +137,12 @@ pub fn api_router(service: ControlService) -> Router {
|
|||||||
)
|
)
|
||||||
.route("/__byok-api__/api/llm-calls", get(calls::list))
|
.route("/__byok-api__/api/llm-calls", get(calls::list))
|
||||||
.route("/__byok-api__/api/llm-calls/{call_id}", get(calls::detail))
|
.route("/__byok-api__/api/llm-calls/{call_id}", get(calls::detail))
|
||||||
|
.route(
|
||||||
|
"/__byok-api__/api/plugins/runtime",
|
||||||
|
get(plugins::runtime_status)
|
||||||
|
.post(plugins::initialize_runtime)
|
||||||
|
.delete(plugins::cancel_runtime_initialization),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/__byok-api__/api/settings/observability",
|
"/__byok-api__/api/settings/observability",
|
||||||
get(settings::get).put(settings::update),
|
get(settings::get).put(settings::update),
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
//! Exposes plugin runtime initialization and status endpoints.
|
||||||
|
use axum::{extract::State, Json};
|
||||||
|
|
||||||
|
use crate::{plugin::PluginRuntimeStatus, Result};
|
||||||
|
|
||||||
|
use super::ControlService;
|
||||||
|
|
||||||
|
pub async fn runtime_status(
|
||||||
|
State(service): State<ControlService>,
|
||||||
|
) -> Result<Json<PluginRuntimeStatus>> {
|
||||||
|
Ok(Json(service.plugin_runtime_status()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn initialize_runtime(
|
||||||
|
State(service): State<ControlService>,
|
||||||
|
) -> Result<Json<PluginRuntimeStatus>> {
|
||||||
|
Ok(Json(service.initialize_plugin_runtime()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cancel_runtime_initialization(
|
||||||
|
State(service): State<ControlService>,
|
||||||
|
) -> Result<Json<PluginRuntimeStatus>> {
|
||||||
|
Ok(Json(service.cancel_plugin_runtime_initialization()))
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ use crate::{
|
|||||||
ModelRequest, ModelSpec, ModelType, Overview, ProjectedContent, ProjectedMessage,
|
ModelRequest, ModelSpec, ModelType, Overview, ProjectedContent, ProjectedMessage,
|
||||||
PromptSpec, ProviderType, Role,
|
PromptSpec, ProviderType, Role,
|
||||||
},
|
},
|
||||||
|
plugin::{PluginRuntime, PluginRuntimeStatus},
|
||||||
provider::{is_valid_response_event, ModelEvent, Provider},
|
provider::{is_valid_response_event, ModelEvent, Provider},
|
||||||
store::{
|
store::{
|
||||||
DesktopSettings, PortSettings, ProxySettings, ProxySettingsInput, StatisticsStorage, Store,
|
DesktopSettings, PortSettings, ProxySettings, ProxySettingsInput, StatisticsStorage, Store,
|
||||||
@@ -38,6 +39,7 @@ pub struct ControlService {
|
|||||||
store: Store,
|
store: Store,
|
||||||
cursor_harness: CursorHarness,
|
cursor_harness: CursorHarness,
|
||||||
provider: Arc<dyn Provider>,
|
provider: Arc<dyn Provider>,
|
||||||
|
plugin_runtime: PluginRuntime,
|
||||||
model_tests: Arc<Mutex<BTreeMap<String, CancellationToken>>>,
|
model_tests: Arc<Mutex<BTreeMap<String, CancellationToken>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +150,7 @@ impl ControlService {
|
|||||||
cursor_harness: CursorHarness::new(store.clone())?,
|
cursor_harness: CursorHarness::new(store.clone())?,
|
||||||
store,
|
store,
|
||||||
provider,
|
provider,
|
||||||
|
plugin_runtime: PluginRuntime::managed()?,
|
||||||
model_tests: Arc::new(Mutex::new(BTreeMap::new())),
|
model_tests: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -156,6 +159,18 @@ impl ControlService {
|
|||||||
&self.cursor_harness
|
&self.cursor_harness
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn plugin_runtime_status(&self) -> PluginRuntimeStatus {
|
||||||
|
self.plugin_runtime.status()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize_plugin_runtime(&self) -> PluginRuntimeStatus {
|
||||||
|
self.plugin_runtime.initialize(self.store.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_plugin_runtime_initialization(&self) -> PluginRuntimeStatus {
|
||||||
|
self.plugin_runtime.cancel_initialization()
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) async fn ads(
|
pub(super) async fn ads(
|
||||||
&self,
|
&self,
|
||||||
disabled_ad_ids: Option<&str>,
|
disabled_ad_ids: Option<&str>,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub mod error;
|
|||||||
pub mod local_app;
|
pub mod local_app;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
pub mod plugin;
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
//! Maps supported desktop platforms to pinned Deno release assets.
|
||||||
|
pub(super) const DENO_VERSION: &str = "2.9.6";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(super) struct RuntimeAsset {
|
||||||
|
pub target: &'static str,
|
||||||
|
pub sha256: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimeAsset {
|
||||||
|
pub fn current() -> Option<Self> {
|
||||||
|
Self::for_platform(std::env::consts::OS, std::env::consts::ARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn for_platform(os: &str, arch: &str) -> Option<Self> {
|
||||||
|
let (target, sha256) = match (os, arch) {
|
||||||
|
("macos", "aarch64") => (
|
||||||
|
"aarch64-apple-darwin",
|
||||||
|
"213a2f304f04d3c9cb5220669afad138f60a5aab1fe80962abdeb8f35807a472",
|
||||||
|
),
|
||||||
|
("macos", "x86_64") => (
|
||||||
|
"x86_64-apple-darwin",
|
||||||
|
"7d4524b82bcc557fe020a1a5b56956ed42b992ae5b28026e8ad5d17329533f5f",
|
||||||
|
),
|
||||||
|
("windows", "aarch64") => (
|
||||||
|
"aarch64-pc-windows-msvc",
|
||||||
|
"acb014afe2299847764e232b4993e162e3946cdeec36603e3f1a0b548cd1ea55",
|
||||||
|
),
|
||||||
|
("windows", "x86_64") => (
|
||||||
|
"x86_64-pc-windows-msvc",
|
||||||
|
"15e5300b0ba3c3695a7621d90160a746ec9e710228cee639afa9d580f6e3cd11",
|
||||||
|
),
|
||||||
|
("linux", "aarch64") => (
|
||||||
|
"aarch64-unknown-linux-gnu",
|
||||||
|
"9a46afc6c392c7cd2ff71a31558935545b46408d0e87f7a86908c712721c046e",
|
||||||
|
),
|
||||||
|
("linux", "x86_64") => (
|
||||||
|
"x86_64-unknown-linux-gnu",
|
||||||
|
"394f07f4da2bebe6ce6f1e7ce0fa16429b29b08c35e3fac3fe25972676dff4b2",
|
||||||
|
),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(Self { target, sha256 })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn archive_name(self) -> String {
|
||||||
|
format!("deno-{}.zip", self.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn download_url(self) -> String {
|
||||||
|
format!(
|
||||||
|
"https://github.com/denoland/deno/releases/download/v{DENO_VERSION}/{}",
|
||||||
|
self.archive_name()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn executable_name(self) -> &'static str {
|
||||||
|
if self.target.contains("windows") {
|
||||||
|
"deno.exe"
|
||||||
|
} else {
|
||||||
|
"deno"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn maps_every_supported_desktop_target() {
|
||||||
|
let cases = [
|
||||||
|
("macos", "aarch64", "aarch64-apple-darwin"),
|
||||||
|
("macos", "x86_64", "x86_64-apple-darwin"),
|
||||||
|
("windows", "aarch64", "aarch64-pc-windows-msvc"),
|
||||||
|
("windows", "x86_64", "x86_64-pc-windows-msvc"),
|
||||||
|
("linux", "aarch64", "aarch64-unknown-linux-gnu"),
|
||||||
|
("linux", "x86_64", "x86_64-unknown-linux-gnu"),
|
||||||
|
];
|
||||||
|
for (os, arch, expected) in cases {
|
||||||
|
assert_eq!(
|
||||||
|
RuntimeAsset::for_platform(os, arch).unwrap().target,
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert!(RuntimeAsset::for_platform("linux", "x86").is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
//! Downloads, verifies, extracts, and validates a pinned Deno runtime.
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Stdio,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
asset::{RuntimeAsset, DENO_VERSION},
|
||||||
|
runtime::PluginRuntimePhase,
|
||||||
|
};
|
||||||
|
use crate::{network, store::Store, Error, Result};
|
||||||
|
|
||||||
|
const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(10 * 60);
|
||||||
|
const VALIDATION_TIMEOUT: Duration = Duration::from_secs(15);
|
||||||
|
const MAX_ARCHIVE_BYTES: u64 = 128 * 1024 * 1024;
|
||||||
|
|
||||||
|
pub(super) async fn install(
|
||||||
|
root: &Path,
|
||||||
|
store: &Store,
|
||||||
|
asset: RuntimeAsset,
|
||||||
|
cancellation: CancellationToken,
|
||||||
|
on_progress: impl Fn(PluginRuntimePhase, u64, Option<u64>),
|
||||||
|
) -> Result<()> {
|
||||||
|
let paths = RuntimePaths::new(root, asset);
|
||||||
|
tokio::fs::create_dir_all(&paths.download_dir).await?;
|
||||||
|
tokio::fs::create_dir_all(&paths.install_dir).await?;
|
||||||
|
remove_if_exists(&paths.archive).await?;
|
||||||
|
remove_if_exists(&paths.executable_staging).await?;
|
||||||
|
remove_if_exists(&paths.ready_marker).await?;
|
||||||
|
|
||||||
|
let result = download_and_install(store, asset, &paths, &cancellation, &on_progress).await;
|
||||||
|
if result.is_err() {
|
||||||
|
let _ = remove_if_exists(&paths.archive).await;
|
||||||
|
let _ = remove_if_exists(&paths.executable_staging).await;
|
||||||
|
let _ = remove_if_exists(&paths.executable).await;
|
||||||
|
let _ = remove_if_exists(&paths.ready_marker).await;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn runtime_complete(root: &Path, asset: RuntimeAsset) -> bool {
|
||||||
|
let paths = RuntimePaths::new(root, asset);
|
||||||
|
paths.executable.is_file() && paths.ready_marker.is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn download_and_install(
|
||||||
|
store: &Store,
|
||||||
|
asset: RuntimeAsset,
|
||||||
|
paths: &RuntimePaths,
|
||||||
|
cancellation: &CancellationToken,
|
||||||
|
on_progress: &impl Fn(PluginRuntimePhase, u64, Option<u64>),
|
||||||
|
) -> Result<()> {
|
||||||
|
ensure_not_cancelled(cancellation)?;
|
||||||
|
let client = network::client(store).await?;
|
||||||
|
let response = tokio::select! {
|
||||||
|
_ = cancellation.cancelled() => return Err(Error::Cancelled),
|
||||||
|
response = client
|
||||||
|
.get(asset.download_url())
|
||||||
|
.timeout(DOWNLOAD_TIMEOUT)
|
||||||
|
.send() => response?,
|
||||||
|
}
|
||||||
|
.error_for_status()?;
|
||||||
|
let total_bytes = response.content_length();
|
||||||
|
if total_bytes.is_some_and(|size| size > MAX_ARCHIVE_BYTES) {
|
||||||
|
return Err(Error::Config(
|
||||||
|
"Deno runtime archive is larger than allowed".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
on_progress(PluginRuntimePhase::Downloading, 0, total_bytes);
|
||||||
|
let mut archive = tokio::fs::File::create(&paths.archive).await?;
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
let mut downloaded_bytes = 0_u64;
|
||||||
|
let mut stream = response.bytes_stream();
|
||||||
|
loop {
|
||||||
|
let next = tokio::select! {
|
||||||
|
_ = cancellation.cancelled() => return Err(Error::Cancelled),
|
||||||
|
next = stream.next() => next,
|
||||||
|
};
|
||||||
|
let Some(chunk) = next else { break };
|
||||||
|
let chunk = chunk?;
|
||||||
|
downloaded_bytes = downloaded_bytes.saturating_add(chunk.len() as u64);
|
||||||
|
if downloaded_bytes > MAX_ARCHIVE_BYTES {
|
||||||
|
return Err(Error::Config(
|
||||||
|
"Deno runtime archive is larger than allowed".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
archive.write_all(&chunk).await?;
|
||||||
|
hasher.update(&chunk);
|
||||||
|
on_progress(
|
||||||
|
PluginRuntimePhase::Downloading,
|
||||||
|
downloaded_bytes,
|
||||||
|
total_bytes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
archive.flush().await?;
|
||||||
|
archive.sync_all().await?;
|
||||||
|
drop(archive);
|
||||||
|
ensure_not_cancelled(cancellation)?;
|
||||||
|
|
||||||
|
on_progress(PluginRuntimePhase::Verifying, downloaded_bytes, total_bytes);
|
||||||
|
let actual_hash = hex::encode(hasher.finalize());
|
||||||
|
if actual_hash != asset.sha256 {
|
||||||
|
return Err(Error::Config(format!(
|
||||||
|
"Deno runtime checksum mismatch: expected {}, received {actual_hash}",
|
||||||
|
asset.sha256
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
on_progress(
|
||||||
|
PluginRuntimePhase::Installing,
|
||||||
|
downloaded_bytes,
|
||||||
|
total_bytes,
|
||||||
|
);
|
||||||
|
let archive_path = paths.archive.clone();
|
||||||
|
let staging_path = paths.executable_staging.clone();
|
||||||
|
let executable_name = asset.executable_name();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
extract_runtime_archive(&archive_path, &staging_path, executable_name)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|error| Error::Config(format!("Deno extraction task failed: {error}")))??;
|
||||||
|
ensure_not_cancelled(cancellation)?;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
tokio::fs::set_permissions(
|
||||||
|
&paths.executable_staging,
|
||||||
|
std::fs::Permissions::from_mode(0o700),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
remove_if_exists(&paths.executable).await?;
|
||||||
|
tokio::fs::rename(&paths.executable_staging, &paths.executable).await?;
|
||||||
|
ensure_not_cancelled(cancellation)?;
|
||||||
|
|
||||||
|
on_progress(
|
||||||
|
PluginRuntimePhase::Validating,
|
||||||
|
downloaded_bytes,
|
||||||
|
total_bytes,
|
||||||
|
);
|
||||||
|
validate_runtime(&paths.executable, cancellation).await?;
|
||||||
|
ensure_not_cancelled(cancellation)?;
|
||||||
|
tokio::fs::write(&paths.ready_marker, format!("deno {DENO_VERSION}\n")).await?;
|
||||||
|
remove_if_exists(&paths.archive).await?;
|
||||||
|
tracing::info!(
|
||||||
|
version = DENO_VERSION,
|
||||||
|
target = asset.target,
|
||||||
|
path = %paths.executable.display(),
|
||||||
|
"plugin runtime initialized"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RuntimePaths {
|
||||||
|
download_dir: PathBuf,
|
||||||
|
install_dir: PathBuf,
|
||||||
|
archive: PathBuf,
|
||||||
|
executable: PathBuf,
|
||||||
|
executable_staging: PathBuf,
|
||||||
|
ready_marker: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimePaths {
|
||||||
|
fn new(root: &Path, asset: RuntimeAsset) -> Self {
|
||||||
|
let download_dir = root.join(".downloads");
|
||||||
|
let install_dir = root
|
||||||
|
.join("deno")
|
||||||
|
.join(format!("v{DENO_VERSION}"))
|
||||||
|
.join(asset.target);
|
||||||
|
let executable = install_dir.join(asset.executable_name());
|
||||||
|
Self {
|
||||||
|
archive: download_dir.join(format!("{}.part", asset.archive_name())),
|
||||||
|
executable_staging: install_dir.join(format!("{}.part", asset.executable_name())),
|
||||||
|
ready_marker: install_dir.join(".ready"),
|
||||||
|
download_dir,
|
||||||
|
install_dir,
|
||||||
|
executable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_runtime_archive(archive: &Path, output: &Path, executable_name: &str) -> Result<()> {
|
||||||
|
let file = std::fs::File::open(archive)?;
|
||||||
|
let mut archive = zip::ZipArchive::new(file)
|
||||||
|
.map_err(|error| Error::Config(format!("invalid Deno runtime archive: {error}")))?;
|
||||||
|
let mut executable = archive
|
||||||
|
.by_name(executable_name)
|
||||||
|
.map_err(|error| Error::Config(format!("Deno executable missing from archive: {error}")))?;
|
||||||
|
let mut destination = std::fs::File::create(output)?;
|
||||||
|
io::copy(&mut executable, &mut destination)?;
|
||||||
|
destination.sync_all()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn validate_runtime(executable: &Path, cancellation: &CancellationToken) -> Result<()> {
|
||||||
|
let mut command = tokio::process::Command::new(executable);
|
||||||
|
command
|
||||||
|
.arg("--version")
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.kill_on_drop(true);
|
||||||
|
let output = tokio::select! {
|
||||||
|
_ = cancellation.cancelled() => return Err(Error::Cancelled),
|
||||||
|
result = tokio::time::timeout(VALIDATION_TIMEOUT, command.output()) => {
|
||||||
|
result.map_err(|_| Error::Config("Deno runtime validation timed out".into()))??
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(Error::Config(format!(
|
||||||
|
"Deno runtime validation failed: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr).trim()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let expected = format!("deno {DENO_VERSION}");
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let version_line = stdout.lines().next().unwrap_or_default().trim();
|
||||||
|
if version_line != expected && !version_line.starts_with(&format!("{expected} ")) {
|
||||||
|
return Err(Error::Config(format!(
|
||||||
|
"unexpected Deno runtime version: {version_line}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_not_cancelled(cancellation: &CancellationToken) -> Result<()> {
|
||||||
|
if cancellation.is_cancelled() {
|
||||||
|
Err(Error::Cancelled)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_if_exists(path: &Path) -> Result<()> {
|
||||||
|
match tokio::fs::remove_file(path).await {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
|
||||||
|
Err(error) => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uses_versioned_runtime_directory() {
|
||||||
|
let root = PathBuf::from("/tmp/plugin-runtime");
|
||||||
|
let asset = super::super::asset::RuntimeAsset::for_platform("macos", "aarch64").unwrap();
|
||||||
|
let paths = RuntimePaths::new(&root, asset);
|
||||||
|
assert_eq!(
|
||||||
|
paths.executable,
|
||||||
|
root.join("deno")
|
||||||
|
.join(format!("v{DENO_VERSION}"))
|
||||||
|
.join(asset.target)
|
||||||
|
.join("deno")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
//! Owns plugin runtime installation and lifecycle infrastructure.
|
||||||
|
mod asset;
|
||||||
|
mod installation;
|
||||||
|
mod runtime;
|
||||||
|
|
||||||
|
pub use runtime::{PluginRuntime, PluginRuntimePhase, PluginRuntimeState, PluginRuntimeStatus};
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
//! Tracks Deno runtime readiness and coordinates one initialization task.
|
||||||
|
use std::{
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use parking_lot::{Mutex, RwLock};
|
||||||
|
use serde::Serialize;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
asset::{RuntimeAsset, DENO_VERSION},
|
||||||
|
installation,
|
||||||
|
};
|
||||||
|
use crate::{config, store::Store, Error, Result};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PluginRuntime {
|
||||||
|
inner: Arc<PluginRuntimeInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PluginRuntimeInner {
|
||||||
|
root: PathBuf,
|
||||||
|
asset: Option<RuntimeAsset>,
|
||||||
|
status: RwLock<PluginRuntimeStatus>,
|
||||||
|
initializing: AtomicBool,
|
||||||
|
cancellation: Mutex<Option<CancellationToken>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum PluginRuntimeState {
|
||||||
|
Uninitialized,
|
||||||
|
Initializing,
|
||||||
|
Ready,
|
||||||
|
Failed,
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum PluginRuntimePhase {
|
||||||
|
Checking,
|
||||||
|
Downloading,
|
||||||
|
Verifying,
|
||||||
|
Installing,
|
||||||
|
Validating,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
|
||||||
|
pub struct PluginRuntimeStatus {
|
||||||
|
pub state: PluginRuntimeState,
|
||||||
|
pub version: String,
|
||||||
|
pub target: Option<String>,
|
||||||
|
pub phase: Option<PluginRuntimePhase>,
|
||||||
|
pub downloaded_bytes: u64,
|
||||||
|
pub total_bytes: Option<u64>,
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginRuntimeStatus {
|
||||||
|
fn uninitialized(asset: RuntimeAsset) -> Self {
|
||||||
|
Self::new(PluginRuntimeState::Uninitialized, Some(asset))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ready(asset: RuntimeAsset) -> Self {
|
||||||
|
Self::new(PluginRuntimeState::Ready, Some(asset))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsupported() -> Self {
|
||||||
|
Self {
|
||||||
|
state: PluginRuntimeState::Unsupported,
|
||||||
|
version: DENO_VERSION.into(),
|
||||||
|
target: None,
|
||||||
|
phase: None,
|
||||||
|
downloaded_bytes: 0,
|
||||||
|
total_bytes: None,
|
||||||
|
error: Some(format!(
|
||||||
|
"unsupported platform: {}/{}",
|
||||||
|
std::env::consts::OS,
|
||||||
|
std::env::consts::ARCH
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(state: PluginRuntimeState, asset: Option<RuntimeAsset>) -> Self {
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
version: DENO_VERSION.into(),
|
||||||
|
target: asset.map(|value| value.target.into()),
|
||||||
|
phase: None,
|
||||||
|
downloaded_bytes: 0,
|
||||||
|
total_bytes: None,
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginRuntime {
|
||||||
|
pub fn managed() -> Result<Self> {
|
||||||
|
Self::new(config::managed_data_dir()?.join("plugins").join("runtime"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(root: PathBuf) -> Result<Self> {
|
||||||
|
std::fs::create_dir_all(&root)?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700))?;
|
||||||
|
}
|
||||||
|
let asset = RuntimeAsset::current();
|
||||||
|
let status = match asset {
|
||||||
|
Some(asset) if installation::runtime_complete(&root, asset) => {
|
||||||
|
PluginRuntimeStatus::ready(asset)
|
||||||
|
}
|
||||||
|
Some(asset) => PluginRuntimeStatus::uninitialized(asset),
|
||||||
|
None => PluginRuntimeStatus::unsupported(),
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
inner: Arc::new(PluginRuntimeInner {
|
||||||
|
root,
|
||||||
|
asset,
|
||||||
|
status: RwLock::new(status),
|
||||||
|
initializing: AtomicBool::new(false),
|
||||||
|
cancellation: Mutex::new(None),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(&self) -> PluginRuntimeStatus {
|
||||||
|
let mut status = self.inner.status.write();
|
||||||
|
if status.state == PluginRuntimeState::Ready {
|
||||||
|
if let Some(asset) = self.inner.asset {
|
||||||
|
if !installation::runtime_complete(&self.inner.root, asset) {
|
||||||
|
*status = PluginRuntimeStatus::uninitialized(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize(&self, store: Store) -> PluginRuntimeStatus {
|
||||||
|
let Some(asset) = self.inner.asset else {
|
||||||
|
return self.status();
|
||||||
|
};
|
||||||
|
if installation::runtime_complete(&self.inner.root, asset) {
|
||||||
|
let ready = PluginRuntimeStatus::ready(asset);
|
||||||
|
*self.inner.status.write() = ready.clone();
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
if self
|
||||||
|
.inner
|
||||||
|
.initializing
|
||||||
|
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return self.status();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut initializing =
|
||||||
|
PluginRuntimeStatus::new(PluginRuntimeState::Initializing, Some(asset));
|
||||||
|
initializing.phase = Some(PluginRuntimePhase::Checking);
|
||||||
|
*self.inner.status.write() = initializing.clone();
|
||||||
|
|
||||||
|
let cancellation = CancellationToken::new();
|
||||||
|
*self.inner.cancellation.lock() = Some(cancellation.clone());
|
||||||
|
let runtime = self.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let result = installation::install(
|
||||||
|
&runtime.inner.root,
|
||||||
|
&store,
|
||||||
|
asset,
|
||||||
|
cancellation,
|
||||||
|
|phase, downloaded, total| {
|
||||||
|
runtime.update_progress(phase, downloaded, total);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let status = match result {
|
||||||
|
Ok(()) => PluginRuntimeStatus::ready(asset),
|
||||||
|
Err(Error::Cancelled) => PluginRuntimeStatus::uninitialized(asset),
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(%error, target = asset.target, "plugin runtime initialization failed");
|
||||||
|
let mut failed =
|
||||||
|
PluginRuntimeStatus::new(PluginRuntimeState::Failed, Some(asset));
|
||||||
|
failed.error = Some("plugin runtime initialization failed".into());
|
||||||
|
failed
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*runtime.inner.status.write() = status;
|
||||||
|
runtime.inner.cancellation.lock().take();
|
||||||
|
runtime.inner.initializing.store(false, Ordering::Release);
|
||||||
|
});
|
||||||
|
|
||||||
|
initializing
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_initialization(&self) -> PluginRuntimeStatus {
|
||||||
|
if let Some(cancellation) = self.inner.cancellation.lock().as_ref() {
|
||||||
|
cancellation.cancel();
|
||||||
|
}
|
||||||
|
self.status()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_progress(
|
||||||
|
&self,
|
||||||
|
phase: PluginRuntimePhase,
|
||||||
|
downloaded_bytes: u64,
|
||||||
|
total_bytes: Option<u64>,
|
||||||
|
) {
|
||||||
|
let mut status = self.inner.status.write();
|
||||||
|
status.state = PluginRuntimeState::Initializing;
|
||||||
|
status.phase = Some(phase);
|
||||||
|
status.downloaded_bytes = downloaded_bytes;
|
||||||
|
status.total_bytes = total_bytes;
|
||||||
|
status.error = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user