源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Creator App Tests / PR Spam Gate (push) Skipped
Frontend Tests / PR Spam Gate (push) Skipped
NPM Format (website & console) / PR Spam Gate (push) Skipped
Pre-commit Checks / PR Spam Gate (push) Skipped
QwenPaw Creator Plugin Release / check-version (push) Canceled after 0s
QwenPaw Creator Plugin Release / upload-oss (push) Canceled after 0s
Tests / PR Spam Gate (push) Skipped
CodeQL / CodeQL (python) (push) Canceled after 0s
CodeQL / CodeQL (typescript) (push) Canceled after 0s
Creator App Tests / Creator Backend Tests (push) Canceled after 0s
Creator App Tests / Creator UI Build & Tests (push) Canceled after 0s
E2E Smoke Tests / Playwright UI Smoke (push) Canceled after 0s
Frontend Tests / Detect console changes (push) Canceled after 0s
NPM Format (website & console) / website format check (push) Canceled after 0s
NPM Format (website & console) / console format check (push) Canceled after 0s
Pre-commit Checks / Pre-commit (push) Canceled after 0s
QwenPaw Data Plugin Release / check-version (push) Canceled after 0s
Tests / Detect code changes (push) Canceled after 0s
Frontend Tests / Vitest Unit Tests (push) Canceled after 0s
Frontend Tests / Frontend Summary (push) Canceled after 0s
QwenPaw Data Plugin Release / upload-oss (push) Canceled after 0s
Tests / Maintainer Approval (push) Canceled after 0s
Tests / Unit Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - fallback (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p2 (push) Canceled after 0s
Tests / Coverage Report (push) Canceled after 0s
Tests / Test Summary (push) Canceled after 0s
Creator App Tests / PR Spam Gate (push) Skipped
Frontend Tests / PR Spam Gate (push) Skipped
NPM Format (website & console) / PR Spam Gate (push) Skipped
Pre-commit Checks / PR Spam Gate (push) Skipped
QwenPaw Creator Plugin Release / check-version (push) Canceled after 0s
QwenPaw Creator Plugin Release / upload-oss (push) Canceled after 0s
Tests / PR Spam Gate (push) Skipped
CodeQL / CodeQL (python) (push) Canceled after 0s
CodeQL / CodeQL (typescript) (push) Canceled after 0s
Creator App Tests / Creator Backend Tests (push) Canceled after 0s
Creator App Tests / Creator UI Build & Tests (push) Canceled after 0s
E2E Smoke Tests / Playwright UI Smoke (push) Canceled after 0s
Frontend Tests / Detect console changes (push) Canceled after 0s
NPM Format (website & console) / website format check (push) Canceled after 0s
NPM Format (website & console) / console format check (push) Canceled after 0s
Pre-commit Checks / Pre-commit (push) Canceled after 0s
QwenPaw Data Plugin Release / check-version (push) Canceled after 0s
Tests / Detect code changes (push) Canceled after 0s
Frontend Tests / Vitest Unit Tests (push) Canceled after 0s
Frontend Tests / Frontend Summary (push) Canceled after 0s
QwenPaw Data Plugin Release / upload-oss (push) Canceled after 0s
Tests / Maintainer Approval (push) Canceled after 0s
Tests / Unit Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - fallback (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p2 (push) Canceled after 0s
Tests / Coverage Report (push) Canceled after 0s
Tests / Test Summary (push) Canceled after 0s
This commit is contained in:
+1689
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "computer-use-plugin-ui",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"format": "tsc --noEmit && prettier --write --cache .",
|
||||
"format:check": "tsc --noEmit && prettier --check ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.3",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"prettier": "3.0.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
import type * as ReactNS from "react";
|
||||
|
||||
import { resolveLocale, t, type ComputerUseLocale } from "./locale";
|
||||
import manifest from "../../plugin.json";
|
||||
|
||||
const host = window.QwenPaw.host;
|
||||
const React: typeof ReactNS = host.React;
|
||||
const {
|
||||
Badge,
|
||||
Button,
|
||||
Empty,
|
||||
Popconfirm,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
Typography,
|
||||
message,
|
||||
} = host.antd;
|
||||
const {
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
DeleteOutlined,
|
||||
FolderOpenOutlined,
|
||||
ReloadOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
} = host.antdIcons;
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
type RuntimeStatus = { runtime_available: boolean; enabled: boolean };
|
||||
|
||||
type PersistentAccess = {
|
||||
canonical_app_id: string;
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
type Approval = {
|
||||
requestId: string;
|
||||
sessionId: string;
|
||||
toolParams: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type ApprovalDecision = "deny" | "session" | "always";
|
||||
|
||||
type ApprovalCardProps = {
|
||||
approval: Approval;
|
||||
onResolved: () => void;
|
||||
};
|
||||
|
||||
function storedLocale() {
|
||||
try {
|
||||
return resolveLocale(localStorage.getItem("language"));
|
||||
} catch {
|
||||
return resolveLocale(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
async function requestJson(path: string, init?: RequestInit): Promise<unknown> {
|
||||
const response = host.fetch
|
||||
? await host.fetch(path, init)
|
||||
: await fetch(host.getApiUrl(path), {
|
||||
...init,
|
||||
headers: {
|
||||
...(init?.headers || {}),
|
||||
...(host.getApiToken()
|
||||
? { Authorization: `Bearer ${host.getApiToken()}` }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
const content = await response.text();
|
||||
let payload: unknown = null;
|
||||
try {
|
||||
payload = content ? JSON.parse(content) : null;
|
||||
} catch {
|
||||
payload = null;
|
||||
}
|
||||
if (!response.ok) {
|
||||
const detail =
|
||||
payload && typeof payload === "object" && "detail" in payload
|
||||
? (payload as { detail?: unknown }).detail
|
||||
: undefined;
|
||||
throw new Error(
|
||||
typeof detail === "string" ? detail : `HTTP ${response.status}`,
|
||||
);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function applicationName(
|
||||
approval: Approval,
|
||||
selectedLocale: ComputerUseLocale,
|
||||
) {
|
||||
const displayName = approval.toolParams.display_name;
|
||||
if (typeof displayName === "string" && displayName.trim()) {
|
||||
return displayName;
|
||||
}
|
||||
const appId = approval.toolParams.canonical_app_id;
|
||||
return typeof appId === "string" && appId.trim()
|
||||
? appId
|
||||
: t(selectedLocale, "unknownApplication");
|
||||
}
|
||||
|
||||
// Surface the executable's on-disk location so the user can tell exactly
|
||||
// which application is asking for access. Identifiers arrive as
|
||||
// ``process:<path>``; strip only the scheme and keep the drive-qualified
|
||||
// path intact (the drive letter's own colon must be preserved).
|
||||
function applicationPath(approval: Approval) {
|
||||
const appId = approval.toolParams.canonical_app_id;
|
||||
if (typeof appId !== "string" || !appId.trim()) {
|
||||
return "";
|
||||
}
|
||||
const separator = appId.indexOf(":");
|
||||
if (separator !== -1 && appId.slice(0, separator) === "process") {
|
||||
return appId.slice(separator + 1);
|
||||
}
|
||||
return appId;
|
||||
}
|
||||
|
||||
function ComputerUseApprovalCard({ approval, onResolved }: ApprovalCardProps) {
|
||||
const selectedLocale = resolveLocale(host.useLocale?.());
|
||||
const [pending, setPending] = React.useState<ApprovalDecision | null>(null);
|
||||
const risk =
|
||||
typeof approval.toolParams.risk === "string"
|
||||
? approval.toolParams.risk
|
||||
: "";
|
||||
const warning =
|
||||
typeof approval.toolParams.warning === "string"
|
||||
? approval.toolParams.warning
|
||||
: "";
|
||||
const appName = applicationName(approval, selectedLocale);
|
||||
const appPath = applicationPath(approval);
|
||||
|
||||
const decide = async (decision: ApprovalDecision) => {
|
||||
setPending(decision);
|
||||
try {
|
||||
await requestJson("/computer-use/session/pending/decision", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
session_id: approval.sessionId,
|
||||
request_id: approval.requestId,
|
||||
decision,
|
||||
}),
|
||||
});
|
||||
message.success(t(selectedLocale, `decision.${decision}`));
|
||||
onResolved();
|
||||
} catch (error) {
|
||||
message.error(
|
||||
error instanceof Error ? error.message : t(selectedLocale, "failed"),
|
||||
);
|
||||
} finally {
|
||||
setPending(null);
|
||||
}
|
||||
};
|
||||
|
||||
return React.createElement(
|
||||
host.antd.Card,
|
||||
{ size: "small", bordered: true, style: { borderRadius: 8 } },
|
||||
React.createElement(
|
||||
"div",
|
||||
{ style: { display: "grid", gap: 14 } },
|
||||
React.createElement(
|
||||
"div",
|
||||
{ style: { display: "grid", gap: 4 } },
|
||||
React.createElement(
|
||||
Space,
|
||||
{ size: 8 },
|
||||
React.createElement(SafetyCertificateOutlined),
|
||||
React.createElement(
|
||||
Text,
|
||||
{ strong: true },
|
||||
t(selectedLocale, "approvalTitle"),
|
||||
),
|
||||
),
|
||||
React.createElement(Text, { strong: true }, appName),
|
||||
appPath && appPath !== appName
|
||||
? React.createElement(
|
||||
"div",
|
||||
{
|
||||
style: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
minWidth: 0,
|
||||
maxWidth: "100%",
|
||||
padding: "2px 8px",
|
||||
borderRadius: 6,
|
||||
background: "rgba(140, 140, 140, 0.1)",
|
||||
},
|
||||
},
|
||||
React.createElement(FolderOpenOutlined, {
|
||||
style: { color: "#8c8c8c", flexShrink: 0, fontSize: 12 },
|
||||
}),
|
||||
React.createElement(
|
||||
Text,
|
||||
{
|
||||
type: "secondary",
|
||||
copyable: { text: appPath },
|
||||
ellipsis: { tooltip: appPath },
|
||||
style: {
|
||||
fontSize: 12,
|
||||
minWidth: 0,
|
||||
fontFamily:
|
||||
"'SFMono-Regular', Consolas, 'Liberation Mono', " +
|
||||
"Menlo, monospace",
|
||||
},
|
||||
},
|
||||
appPath,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
risk
|
||||
? React.createElement(
|
||||
Text,
|
||||
{ type: "secondary" },
|
||||
`${t(selectedLocale, "risk")}: ${risk}`,
|
||||
)
|
||||
: null,
|
||||
warning
|
||||
? React.createElement(Text, { type: "warning" }, warning)
|
||||
: null,
|
||||
),
|
||||
React.createElement(
|
||||
Space,
|
||||
{ size: 8, wrap: true },
|
||||
React.createElement(
|
||||
Button,
|
||||
{
|
||||
danger: true,
|
||||
icon: React.createElement(CloseOutlined),
|
||||
loading: pending === "deny",
|
||||
disabled: pending !== null,
|
||||
onClick: () => void decide("deny"),
|
||||
},
|
||||
t(selectedLocale, "deny"),
|
||||
),
|
||||
React.createElement(
|
||||
Button,
|
||||
{
|
||||
icon: React.createElement(CheckOutlined),
|
||||
loading: pending === "session",
|
||||
disabled: pending !== null,
|
||||
onClick: () => void decide("session"),
|
||||
},
|
||||
t(selectedLocale, "allowSession"),
|
||||
),
|
||||
React.createElement(
|
||||
Button,
|
||||
{
|
||||
type: "primary",
|
||||
icon: React.createElement(CheckOutlined),
|
||||
loading: pending === "always",
|
||||
disabled: pending !== null,
|
||||
onClick: () => void decide("always"),
|
||||
},
|
||||
t(selectedLocale, "allowAlways"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function ComputerUsePage() {
|
||||
const selectedLocale = resolveLocale(host.useLocale?.());
|
||||
const currentSession = host.useCurrentSession?.();
|
||||
const sessionId = currentSession?.id ?? host.getCurrentSessionId?.() ?? null;
|
||||
const [runtime, setRuntime] = React.useState<RuntimeStatus | null>(null);
|
||||
const [access, setAccess] = React.useState<PersistentAccess[]>([]);
|
||||
const [enabled, setEnabled] = React.useState(true);
|
||||
const [toggling, setToggling] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [action, setAction] = React.useState<string | null>(null);
|
||||
|
||||
const refresh = React.useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [runtimePayload, accessPayload] = await Promise.all([
|
||||
requestJson("/computer-use/status"),
|
||||
requestJson("/computer-use/access"),
|
||||
]);
|
||||
setRuntime(runtimePayload as RuntimeStatus);
|
||||
setAccess((accessPayload as { access: PersistentAccess[] }).access || []);
|
||||
setEnabled((runtimePayload as RuntimeStatus).enabled !== false);
|
||||
} catch (error) {
|
||||
message.error(
|
||||
error instanceof Error ? error.message : t(selectedLocale, "failed"),
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [selectedLocale]);
|
||||
|
||||
React.useEffect(() => {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const revoke = async (item: PersistentAccess) => {
|
||||
setAction(`revoke:${item.canonical_app_id}`);
|
||||
try {
|
||||
await requestJson("/computer-use/access", {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ canonical_app_id: item.canonical_app_id }),
|
||||
});
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
message.error(
|
||||
error instanceof Error ? error.message : t(selectedLocale, "failed"),
|
||||
);
|
||||
} finally {
|
||||
setAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleFeature = async (next: boolean) => {
|
||||
setToggling(true);
|
||||
try {
|
||||
await requestJson("/computer-use/feature", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ enabled: next, session_id: sessionId }),
|
||||
});
|
||||
setEnabled(next);
|
||||
message.success(t(selectedLocale, next ? "enabledMsg" : "disabledMsg"));
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
message.error(
|
||||
error instanceof Error ? error.message : t(selectedLocale, "failed"),
|
||||
);
|
||||
} finally {
|
||||
setToggling(false);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t(selectedLocale, "application"),
|
||||
dataIndex: "display_name",
|
||||
key: "display_name",
|
||||
render: (value: string) =>
|
||||
React.createElement(Text, { strong: true }, value),
|
||||
},
|
||||
{
|
||||
title: t(selectedLocale, "applicationId"),
|
||||
dataIndex: "canonical_app_id",
|
||||
key: "canonical_app_id",
|
||||
render: (value: string) =>
|
||||
React.createElement(Text, { code: true }, value),
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
width: 56,
|
||||
render: (_: unknown, item: PersistentAccess) =>
|
||||
React.createElement(
|
||||
Popconfirm,
|
||||
{
|
||||
title: t(selectedLocale, "revokeConfirm"),
|
||||
onConfirm: () => void revoke(item),
|
||||
},
|
||||
React.createElement(
|
||||
Tooltip,
|
||||
{ title: t(selectedLocale, "revoke") },
|
||||
React.createElement(Button, {
|
||||
type: "text",
|
||||
danger: true,
|
||||
shape: "circle",
|
||||
icon: React.createElement(DeleteOutlined),
|
||||
loading: action === `revoke:${item.canonical_app_id}`,
|
||||
"aria-label": t(selectedLocale, "revoke"),
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
];
|
||||
const runtimeReady = runtime?.runtime_available === true;
|
||||
|
||||
return React.createElement(
|
||||
"main",
|
||||
{
|
||||
style: {
|
||||
maxWidth: 1080,
|
||||
margin: "24px auto",
|
||||
padding: "0 24px 40px",
|
||||
display: "grid",
|
||||
gap: 28,
|
||||
},
|
||||
},
|
||||
React.createElement(
|
||||
"header",
|
||||
{
|
||||
style: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 16,
|
||||
paddingBottom: 16,
|
||||
borderBottom: "1px solid rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
},
|
||||
React.createElement(
|
||||
"div",
|
||||
{ style: { display: "grid", gap: 6 } },
|
||||
React.createElement(
|
||||
Space,
|
||||
{ align: "baseline", size: 8 },
|
||||
React.createElement(
|
||||
Title,
|
||||
{ level: 3, style: { margin: 0 } },
|
||||
t(selectedLocale, "title"),
|
||||
),
|
||||
React.createElement(
|
||||
Text,
|
||||
{ type: "secondary", style: { fontSize: 12 } },
|
||||
`${t(selectedLocale, "version")} ${manifest.version}`,
|
||||
),
|
||||
),
|
||||
React.createElement(Badge, {
|
||||
status: runtimeReady ? "success" : "error",
|
||||
text: t(selectedLocale, runtimeReady ? "ready" : "unavailable"),
|
||||
}),
|
||||
),
|
||||
React.createElement(
|
||||
Space,
|
||||
{ size: 8 },
|
||||
React.createElement(
|
||||
Tooltip,
|
||||
{ title: t(selectedLocale, "refresh") },
|
||||
React.createElement(Button, {
|
||||
type: "text",
|
||||
shape: "circle",
|
||||
icon: React.createElement(ReloadOutlined),
|
||||
loading,
|
||||
onClick: () => void refresh(),
|
||||
"aria-label": t(selectedLocale, "refresh"),
|
||||
}),
|
||||
),
|
||||
React.createElement(
|
||||
Space,
|
||||
{ size: 8, align: "center" },
|
||||
React.createElement(
|
||||
Text,
|
||||
{ type: "secondary", style: { fontSize: 13 } },
|
||||
t(selectedLocale, "feature"),
|
||||
),
|
||||
React.createElement(Switch, {
|
||||
checked: enabled,
|
||||
loading: toggling,
|
||||
disabled: !runtimeReady,
|
||||
onChange: (next: boolean) => void toggleFeature(next),
|
||||
"aria-label": t(selectedLocale, "feature"),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
React.createElement(Tabs, {
|
||||
defaultActiveKey: "access",
|
||||
items: [
|
||||
{
|
||||
key: "access",
|
||||
label: t(selectedLocale, "accessManagement"),
|
||||
children: React.createElement(Table, {
|
||||
rowKey: "canonical_app_id",
|
||||
columns,
|
||||
dataSource: access,
|
||||
pagination: false,
|
||||
size: "middle",
|
||||
locale: {
|
||||
emptyText: React.createElement(Empty, {
|
||||
image: Empty.PRESENTED_IMAGE_SIMPLE,
|
||||
description: t(selectedLocale, "empty"),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
window.QwenPaw.chat?.approval.render(
|
||||
"computer-use",
|
||||
"computer_use_app_access",
|
||||
ComputerUseApprovalCard,
|
||||
);
|
||||
|
||||
window.QwenPaw.registerRoutes?.("computer-use", [
|
||||
{
|
||||
path: "/plugin/computer-use",
|
||||
component: ComputerUsePage,
|
||||
label: t(storedLocale(), "routeLabel"),
|
||||
icon: "🖥️",
|
||||
priority: 43,
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,86 @@
|
||||
export type ComputerUseLocale = "en" | "zh";
|
||||
|
||||
const messages = {
|
||||
en: {
|
||||
routeLabel: "Computer Use",
|
||||
title: "Computer Use",
|
||||
ready: "Runtime ready",
|
||||
unavailable: "Runtime unavailable",
|
||||
refresh: "Refresh",
|
||||
stop: "Stop automation",
|
||||
feature: "Enable Computer Use",
|
||||
enabledMsg: "Computer Use enabled",
|
||||
disabledMsg: "Computer Use disabled; running automation stopped",
|
||||
application: "Application",
|
||||
applicationId: "Application ID",
|
||||
revoke: "Revoke access",
|
||||
revokeConfirm: "Revoke this application access?",
|
||||
empty: "No applications are always allowed.",
|
||||
approvalTitle: "Application access",
|
||||
unknownApplication: "Unknown application",
|
||||
risk: "Risk",
|
||||
deny: "Deny",
|
||||
allowSession: "Allow for this session",
|
||||
allowAlways: "Always allow",
|
||||
failed: "Action failed.",
|
||||
accessManagement: "Access management",
|
||||
version: "Version",
|
||||
decision: {
|
||||
deny: "Access denied.",
|
||||
session: "Access allowed for this session.",
|
||||
always: "Application always allowed.",
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
routeLabel: "电脑操作",
|
||||
title: "电脑操作",
|
||||
ready: "运行环境已就绪",
|
||||
unavailable: "运行环境不可用",
|
||||
refresh: "刷新",
|
||||
stop: "停止自动化",
|
||||
feature: "启用电脑操作",
|
||||
enabledMsg: "已启用电脑操作",
|
||||
disabledMsg: "已关闭电脑操作,正在进行的自动化已停止",
|
||||
application: "应用",
|
||||
applicationId: "应用标识",
|
||||
revoke: "撤销授权",
|
||||
revokeConfirm: "撤销这个应用的授权?",
|
||||
empty: "暂未允许任何应用长期访问。",
|
||||
approvalTitle: "应用访问请求",
|
||||
unknownApplication: "未知应用",
|
||||
risk: "风险",
|
||||
deny: "拒绝",
|
||||
allowSession: "仅本次会话允许",
|
||||
allowAlways: "始终允许",
|
||||
failed: "操作失败。",
|
||||
accessManagement: "授权管理",
|
||||
version: "版本",
|
||||
decision: {
|
||||
deny: "已拒绝访问。",
|
||||
session: "已允许本次会话访问。",
|
||||
always: "已始终允许该应用。",
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type MessageKey = keyof typeof messages.en;
|
||||
export type DecisionKey = `decision.${keyof typeof messages.en.decision}`;
|
||||
|
||||
export function resolveLocale(
|
||||
value: string | null | undefined,
|
||||
): ComputerUseLocale {
|
||||
return value?.toLowerCase().startsWith("zh") ? "zh" : "en";
|
||||
}
|
||||
|
||||
export function t(
|
||||
selectedLocale: ComputerUseLocale,
|
||||
key: MessageKey | DecisionKey,
|
||||
): string {
|
||||
if (key.startsWith("decision.")) {
|
||||
const decision = key.slice(
|
||||
"decision.".length,
|
||||
) as keyof typeof messages.en.decision;
|
||||
return messages[selectedLocale].decision[decision];
|
||||
}
|
||||
return messages[selectedLocale][key as MessageKey] as string;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type * as ReactNS from "react";
|
||||
|
||||
declare global {
|
||||
interface QwenPawHost {
|
||||
React: typeof ReactNS;
|
||||
antd: any;
|
||||
antdIcons: any;
|
||||
getApiUrl: (path: string) => string;
|
||||
getApiToken: () => string;
|
||||
useLocale?: () => string;
|
||||
useCurrentSession?: () => { id: string } | null;
|
||||
getCurrentSessionId?: () => string | null;
|
||||
fetch?: (path: string, init?: RequestInit) => Promise<Response>;
|
||||
}
|
||||
|
||||
interface QwenPawRoute {
|
||||
path: string;
|
||||
component: unknown;
|
||||
label?: string;
|
||||
icon?: string;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
interface QwenPawGlobal {
|
||||
host: QwenPawHost;
|
||||
chat?: {
|
||||
approval: {
|
||||
render: (
|
||||
pluginId: string,
|
||||
sourceType: string,
|
||||
component: unknown,
|
||||
) => unknown;
|
||||
};
|
||||
toolRender?: (
|
||||
pluginId: string,
|
||||
toolName: string,
|
||||
component: unknown,
|
||||
) => unknown;
|
||||
};
|
||||
registerRoutes?: (pluginId: string, routes: QwenPawRoute[]) => void;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
QwenPaw: QwenPawGlobal;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"types": []
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { resolve } from "path";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react({ jsxRuntime: "classic" })],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, "src/index.tsx"),
|
||||
formats: ["es"],
|
||||
fileName: () => "index.js",
|
||||
},
|
||||
outDir: resolve(__dirname, "../dist"),
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
external: ["react", "react-dom"],
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user