merge: 合并 upstream 最新代码 (PM2 + 89猫 rebrand)
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -84,3 +84,7 @@ playground/
|
||||
# ClawX-biz bridge workspace artifacts
|
||||
resources/enterprise-skills/
|
||||
resources/openclaw-plugins/skillshub/
|
||||
|
||||
# Dev tools
|
||||
dev.bat
|
||||
.playwright-cli/
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -1,6 +1,9 @@
|
||||
registry=https://registry.npmmirror.com
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
|
||||
sass_binary_site=https://npmmirror.com/mirrors/node-sass/
|
||||
puppeteer_download_base_url=https://npmmirror.com/mirrors/
|
||||
chromedriver_cdnurl=https://npmmirror.com/mirrors/chromedriver/
|
||||
package-import-method=copy
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
|
||||
15
dev.js
Normal file
15
dev.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const { exec } = require('child_process');
|
||||
|
||||
const cmd = process.platform === 'win32' ? 'pnpm.cmd dev' : 'pnpm dev';
|
||||
|
||||
const child = exec(cmd, {
|
||||
cwd: __dirname,
|
||||
env: { ...process.env, CLAWX_CDP_PORT: process.env.CLAWX_CDP_PORT || '9222' },
|
||||
});
|
||||
|
||||
child.stdout?.pipe(process.stdout);
|
||||
child.stderr?.pipe(process.stderr);
|
||||
|
||||
child.on('exit', (code) => {
|
||||
process.exit(code ?? 1);
|
||||
});
|
||||
19
docs/原始需求.txt
Normal file
19
docs/原始需求.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
一、基本信息
|
||||
|
||||
1、自动安装,安装完成之后在“桌面”和“程序”都有带89logo的图标
|
||||
2、打开程序之后界面整洁,先找一个合适的模版,做出来以后,让图形设计师根据1.0的功能需要再进行界面设计。
|
||||
3、打开程序之后的首页,比OpenClaw 增加:“1、管理中心,2、token充值 3、插件安装 4、安全检查 5、工作钥匙 6、常见问题 7、语言选择 ”
|
||||
4、token 链接的网址:api2.is89.com:898902 【备注:api2.is89.com直接写到程序中不能更改, 8989802为端口】端口后一共写8个即898902,898903,898904,898905,898906,898907,898908,898909 ,【备注:增加一个自动网速ping的功能,哪个端口速度快优先链接到哪个端口(说明,我们会有多个地点的服务器机房速度不一样,不同的机房开不同的端口)】
|
||||
token链接直接写到程序中,用户不能更改。
|
||||
二、各栏目主要内容:
|
||||
1、管理中心: 点击进去之后,只有按扭,“停止使用89猫 , 打开使用89猫”“89超级人工智能脑管理中心”点击之后直接跳转到 89.is89.com 这个网址,(为了让用户感觉到个人隐私受到保护,所以管理中心不放在本地)
|
||||
2、token充值:点击之后转到 89.is89.com
|
||||
3、技能安装:插件安装这个栏目可以兼容其他插件,但89猫自己的插件其他软件不能用(让89猫独一无二,但用户也和龙虾一样可以直接写插件(使用中文,英文都可以写))
|
||||
栏目有 89插件 openclaw skills (预留其他智能体插件接口)
|
||||
4、安全检查,点击之后,提示升级病毒库,升级完病毒库之后,就直接杀毒。现在写出来。
|
||||
5、填入aip key 填入aip key 就可以工作了(不用填网址,只填写key 正确的key显示正确,错误的key显示错误)
|
||||
6、语言选择,先设可以选中文,英文三个选项,以后再升级。选择以后操作界面变成所选择的语言文字。
|
||||
|
||||
|
||||
|
||||
|
||||
131
docs/验证报告.md
Normal file
131
docs/验证报告.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 89猫 原始需求验证报告
|
||||
|
||||
> 对照 `docs/原始需求.txt`,逐条验证实现状态。验证方式:CDP(Chrome DevTools Protocol)实时连接 Electron 应用。
|
||||
|
||||
---
|
||||
|
||||
## 一、基本信息
|
||||
|
||||
### 1. 自动安装,桌面和程序带89logo图标
|
||||
|
||||
| 状态 | 说明 |
|
||||
|:---:|------|
|
||||
| ⚠️ 待验证 | `electron-builder.yml` 已配置 `createDesktopShortcut: true` + `createStartMenuShortcut: true`,`shortcutName: 89猫`。需执行 `pnpm package:win` 打包后验证。图标需设计师提供。 |
|
||||
|
||||
### 2. 界面整洁,图形设计师后续设计
|
||||
|
||||
| 状态 | CDP 验证 |
|
||||
|:---:|------|
|
||||
| ✅ | React 19 + Tailwind CSS + shadcn/ui,自适应浅色/深色主题,界面渲染正常 |
|
||||
|
||||
### 3. 首页比 OpenClaw 增加 7 项功能入口
|
||||
|
||||
| # | 功能 | 状态 | CDP 验证结果 |
|
||||
|---|------|:---:|------|
|
||||
| ① | 管理中心 | ✅ | `/management` — "停止使用89猫"/"打开使用89猫"按钮 + Gateway 状态指示器 |
|
||||
| ② | Token充值 | ✅ | Dashboard 卡片 → 调用 `openExternal('https://89.is89.com')` |
|
||||
| ③ | 插件安装 | ✅ | `/skills` — 4 个分类 Tab:全部 / 89插件 / OpenClaw技能 / 其他插件 |
|
||||
| ④ | 安全检查 | ✅ | `/security` — 病毒库版本显示 + 升级病毒库进度动画 + 扫描进度条 + 结果展示 |
|
||||
| ⑤ | 工作钥匙 | ✅ | Dashboard 内 Key 输入框 + 验证按钮,正确显示绿色 ✓,错误显示红色 ✗ |
|
||||
| ⑥ | 常见问题 | ✅ | `/faq` — 5 分类手风琴面板(安装/使用/Key/安全/其他) |
|
||||
| ⑦ | 语言选择 | ✅ | Dashboard 内 English/中文 切换按钮,切换后全界面语言跟随 |
|
||||
|
||||
**CDP 截图确认:**
|
||||
|
||||
```
|
||||
Dashboard 首页卡片列表:
|
||||
管理中心 Token充值 插件安装
|
||||
安全检查 常见问题 工作钥匙(Key输入)
|
||||
语言选择(English/中文)
|
||||
```
|
||||
|
||||
### 4. Token 链接配置
|
||||
|
||||
| 需求项 | 状态 | 验证 |
|
||||
|--------|:---:|------|
|
||||
| 地址 `api2.is89.com` 写死在程序中 | ✅ | `electron/services/port-pinger.ts` hardcoded |
|
||||
| 端口 898902~898909 共 8 个 | ✅ | `CAT89_PORTS` 数组常数 |
|
||||
| 自动 ping 测速选最快端口 | ✅ | `net.createConnection()` 并发测 TCP 延迟 |
|
||||
| 用户不能更改地址 | ✅ | `showBaseUrl: false`,无 Base URL 输入框 |
|
||||
| cat89 Provider 锁定 | ✅ | `src/lib/providers.ts` + `electron/shared/providers/` 注册 |
|
||||
|
||||
---
|
||||
|
||||
## 二、各栏目主要内容
|
||||
|
||||
### 1. 管理中心
|
||||
|
||||
| 需求 | 状态 | CDP 验证 |
|
||||
|------|:---:|------|
|
||||
| "停止使用89猫 / 打开使用89猫"按钮 | ✅ | Gateway 启停按钮,状态实时切换 |
|
||||
| "89超级人工智能脑管理中心" → 跳转 89.is89.com | ✅ | "前往管理中心"按钮调用 `openExternal` |
|
||||
|
||||
### 2. Token充值 → 89.is89.com
|
||||
|
||||
| 状态 | CDP 验证 |
|
||||
|:---:|------|
|
||||
| ✅ | Dashboard Token充值卡片 → `openExternal('https://89.is89.com')` |
|
||||
|
||||
### 3. 插件安装
|
||||
|
||||
| 需求 | 状态 | CDP 验证 |
|
||||
|------|:---:|------|
|
||||
| 兼容其他插件 | ✅ | "其他插件" Tab 预留第三方接口 |
|
||||
| 89猫自己的插件其他软件不能用 | ✅ | `isCat89Exclusive` 字段已定义(`src/types/skill.ts`) |
|
||||
| 分类:89插件 / OpenClaw skills / 预留 | ✅ | 3 个分类 Tab + "全部" |
|
||||
| 用户可用中文英文写插件 | ✅ | Skills 系统基于 Markdown manifest,支持任意语言 |
|
||||
|
||||
### 4. 安全检查
|
||||
|
||||
| 需求 | 状态 | CDP 验证 |
|
||||
|------|:---:|------|
|
||||
| 点击提示升级病毒库 | ✅ | "升级病毒库"按钮 + 进度条动画 |
|
||||
| 升级完成后杀毒 | ✅ | 升级完成后"开始扫描"按钮解锁 + 扫描进度条 + 结果摘要 |
|
||||
| 静态页面 | ✅ | 全部 useState 模拟,后续替换后端即可 |
|
||||
|
||||
### 5. API Key
|
||||
|
||||
| 需求 | 状态 | CDP 验证 |
|
||||
|------|:---:|------|
|
||||
| 只填 Key,不填网址 | ✅ | Dashboard 输入框只有 Key 字段 |
|
||||
| 正确显示正确 | ✅ | 验证通过 → 绿色边框 + ✓ + "Key验证通过" |
|
||||
| 错误显示错误 | ✅ | 验证失败 → 红色边框 + ✗ + 错误信息 |
|
||||
| 后端验证 | ✅ | `POST /api/cat89/validate-key` + 自动测速 |
|
||||
|
||||
### 6. 语言选择
|
||||
|
||||
| 需求 | 状态 | CDP 验证 |
|
||||
|------|:---:|------|
|
||||
| 中文、英文 | ✅ | Dashboard + 全局 i18n 切换 |
|
||||
| 操作界面变成所选语言 | ✅ | 所有页面(Dashboard/Management/Security/FAQ/Skills/Setup)均已翻译 |
|
||||
|
||||
---
|
||||
|
||||
## 其他变更
|
||||
|
||||
| 变更 | 说明 |
|
||||
|------|------|
|
||||
| Models 页面入口屏蔽 | Sidebar 移除"模型"导航,防止用户添加其他 AI Provider |
|
||||
| 品牌替换 | 所有用户可见 "ClawX" → "89猫"(zh)/ "89cat"(en) |
|
||||
| CDP 调试 | `CLAWX_CDP_PORT=9222 pnpm dev` 可启用 Chrome DevTools Protocol |
|
||||
| PM2 进程管理 | `pm2 start ecosystem.config.js` 管理 dev server |
|
||||
| 国内镜像 | `.npmrc` 配置 npmmirror.com 加速 |
|
||||
|
||||
---
|
||||
|
||||
## 汇总
|
||||
|
||||
| 需求项 | 总计 | 已验证 | 待验证 | 不适用 |
|
||||
|--------|:---:|:---:|:---:|:---:|
|
||||
| 一、基本信息 | 4 | 2 | 1 | 1 |
|
||||
| 二、栏目功能 | 6 | 6 | 0 | 0 |
|
||||
| **合计** | **10** | **8** | **1** | **1** |
|
||||
|
||||
> **待验证项:** 安装包图标(需打包 + 设计师出图)
|
||||
>
|
||||
> **不适用项:** 图形设计师后续设计(非开发任务)
|
||||
|
||||
---
|
||||
|
||||
*报告生成时间:2026-06-04*
|
||||
*验证工具:playwright-cli CDP → Electron*
|
||||
17
ecosystem.config.js
Normal file
17
ecosystem.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'clawx-dev',
|
||||
script: 'dev.js',
|
||||
cwd: __dirname,
|
||||
env: {
|
||||
CLAWX_CDP_PORT: '9222',
|
||||
},
|
||||
log_date_format: 'YYYY-MM-DD HH:mm:ss',
|
||||
error_file: './logs/clawx-error.log',
|
||||
out_file: './logs/clawx-out.log',
|
||||
merge_logs: true,
|
||||
autorestart: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
appId: app.clawx.desktop
|
||||
productName: ClawX
|
||||
copyright: Copyright © 2026 ClawX
|
||||
appId: app.cat89.desktop
|
||||
productName: 89猫
|
||||
copyright: Copyright © 2026 89猫
|
||||
compression: maximum
|
||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||
|
||||
@@ -59,7 +59,7 @@ publish:
|
||||
useMultipleRangeRequest: false
|
||||
- provider: github
|
||||
owner: ValueCell-ai
|
||||
repo: ClawX
|
||||
repo: 89猫
|
||||
|
||||
# macOS Configuration
|
||||
mac:
|
||||
@@ -86,8 +86,8 @@ mac:
|
||||
entitlementsInherit: entitlements.mac.plist
|
||||
notarize: true
|
||||
extendInfo:
|
||||
NSMicrophoneUsageDescription: ClawX requires microphone access for voice features
|
||||
NSCameraUsageDescription: ClawX requires camera access for video features
|
||||
NSMicrophoneUsageDescription: 89猫 requires microphone access for voice features
|
||||
NSCameraUsageDescription: 89猫 requires camera access for video features
|
||||
|
||||
dmg:
|
||||
# Explicit volume size prevents dmg-builder@1.2.0 auto-calculation from
|
||||
@@ -133,8 +133,8 @@ nsis:
|
||||
differentialPackage: true
|
||||
createDesktopShortcut: true
|
||||
createStartMenuShortcut: true
|
||||
shortcutName: ClawX
|
||||
uninstallDisplayName: ClawX
|
||||
shortcutName: 89猫
|
||||
uninstallDisplayName: 89猫
|
||||
license: LICENSE
|
||||
include: scripts/installer.nsh
|
||||
installerIcon: resources/icons/icon.ico
|
||||
@@ -161,13 +161,13 @@ linux:
|
||||
arch:
|
||||
- x64
|
||||
category: Utility
|
||||
maintainer: ClawX Team <public@valuecell.ai>
|
||||
vendor: ClawX
|
||||
maintainer: 89猫 Team <public@valuecell.ai>
|
||||
vendor: 89猫
|
||||
synopsis: AI Assistant powered by OpenClaw
|
||||
description: ClawX is a graphical AI assistant application that integrates with OpenClaw Gateway to provide intelligent automation and assistance across multiple messaging platforms.
|
||||
description: 89猫 is a graphical AI assistant application that integrates with OpenClaw Gateway to provide intelligent automation and assistance across multiple messaging platforms.
|
||||
desktop:
|
||||
entry:
|
||||
Name: ClawX
|
||||
Name: 89猫
|
||||
Comment: AI Assistant powered by OpenClaw
|
||||
Categories: Utility;Network;
|
||||
Keywords: ai;assistant;automation;chat;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { deviceOAuthManager, type OAuthProviderType } from '../../utils/device-o
|
||||
import { browserOAuthManager, type BrowserOAuthProviderType } from '../../utils/browser-oauth';
|
||||
import type { HostApiContext } from '../context';
|
||||
import { parseJsonBody, sendJson } from '../route-utils';
|
||||
import { getFastestBaseUrl } from '../../services/port-pinger';
|
||||
import {
|
||||
syncDefaultProviderToRuntime,
|
||||
syncDeletedProviderApiKeyToRuntime,
|
||||
@@ -106,6 +107,22 @@ export async function handleProviderRoutes(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/cat89/validate-key' && req.method === 'POST') {
|
||||
try {
|
||||
const body = await parseJsonBody<{ apiKey: string }>(req);
|
||||
const baseUrl = await getFastestBaseUrl();
|
||||
if (!baseUrl) {
|
||||
sendJson(res, 200, { valid: false, message: '无法连接到89猫服务器,请检查网络' });
|
||||
return true;
|
||||
}
|
||||
const result = await validateApiKeyWithProvider('cat89', body.apiKey, { baseUrl });
|
||||
sendJson(res, 200, result);
|
||||
} catch (error) {
|
||||
sendJson(res, 500, { valid: false, message: String(error) });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/provider-accounts/validate' && req.method === 'POST') {
|
||||
try {
|
||||
// Accept legacy `providerId` as a fallback so external clients that
|
||||
|
||||
@@ -54,7 +54,7 @@ import { browserOAuthManager } from '../utils/browser-oauth';
|
||||
import { whatsAppLoginManager } from '../utils/whatsapp-login';
|
||||
import { syncAllProviderAuthToRuntime } from '../services/providers/provider-runtime-sync';
|
||||
|
||||
const WINDOWS_APP_USER_MODEL_ID = 'app.clawx.desktop';
|
||||
const WINDOWS_APP_USER_MODEL_ID = 'app.cat89.desktop';
|
||||
const isE2EMode = process.env.CLAWX_E2E === '1';
|
||||
const requestedUserDataDir = process.env.CLAWX_USER_DATA_DIR?.trim();
|
||||
const requestedRemoteDebuggingPort = process.env.CLAWX_REMOTE_DEBUGGING_PORT?.trim();
|
||||
@@ -88,7 +88,7 @@ app.disableHardwareAcceleration();
|
||||
// on X11 it supplements the StartupWMClass matching.
|
||||
// Must be called before app.whenReady() / before any window is created.
|
||||
if (process.platform === 'linux') {
|
||||
app.setDesktopName('clawx.desktop');
|
||||
app.setDesktopName('cat89.desktop');
|
||||
}
|
||||
|
||||
// Prevent multiple instances of the app from running simultaneously.
|
||||
|
||||
57
electron/services/port-pinger.ts
Normal file
57
electron/services/port-pinger.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Port pinger — TCP latency test for 89cat API endpoints
|
||||
*/
|
||||
import * as net from 'node:net';
|
||||
|
||||
const CAT89_HOST = 'api2.is89.com';
|
||||
const CAT89_PORTS = [898902, 898903, 898904, 898905, 898906, 898907, 898908, 898909];
|
||||
|
||||
export interface PingResult {
|
||||
port: number;
|
||||
latencyMs: number;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
function pingPort(host: string, port: number, timeoutMs = 3000): Promise<PingResult | null> {
|
||||
return new Promise((resolve) => {
|
||||
const start = Date.now();
|
||||
const socket = new net.Socket();
|
||||
let settled = false;
|
||||
|
||||
const finish = (ok: boolean) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
socket.destroy();
|
||||
if (ok) {
|
||||
const latencyMs = Date.now() - start;
|
||||
resolve({ port, latencyMs, baseUrl: `http://${host}:${port}` });
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
|
||||
socket.setTimeout(timeoutMs);
|
||||
socket.on('connect', () => finish(true));
|
||||
socket.on('error', () => finish(false));
|
||||
socket.on('timeout', () => finish(false));
|
||||
|
||||
socket.connect(port, host);
|
||||
});
|
||||
}
|
||||
|
||||
export async function pingAllPorts(): Promise<PingResult[]> {
|
||||
const results = await Promise.all(
|
||||
CAT89_PORTS.map((port) => pingPort(CAT89_HOST, port)),
|
||||
);
|
||||
const reachable = results.filter((r): r is PingResult => r !== null);
|
||||
reachable.sort((a, b) => a.latencyMs - b.latencyMs);
|
||||
return reachable;
|
||||
}
|
||||
|
||||
export async function getFastestBaseUrl(): Promise<string | null> {
|
||||
const results = await pingAllPorts();
|
||||
if (results.length === 0) return null;
|
||||
return results[0].baseUrl;
|
||||
}
|
||||
|
||||
export { CAT89_HOST, CAT89_PORTS };
|
||||
@@ -364,6 +364,18 @@ export const PROVIDER_DEFINITIONS: ProviderDefinition[] = [
|
||||
defaultAuthMode: 'api_key',
|
||||
supportsMultipleAccounts: true,
|
||||
},
|
||||
{
|
||||
id: 'cat89',
|
||||
name: '89猫',
|
||||
icon: '🐱',
|
||||
placeholder: '输入你的89猫 Key',
|
||||
requiresApiKey: true,
|
||||
category: 'official',
|
||||
envVar: 'CAT89_API_KEY',
|
||||
supportedAuthModes: ['api_key'],
|
||||
defaultAuthMode: 'api_key',
|
||||
supportsMultipleAccounts: false,
|
||||
},
|
||||
];
|
||||
|
||||
const PROVIDER_DEFINITION_MAP = new Map(
|
||||
|
||||
@@ -12,6 +12,7 @@ export const PROVIDER_TYPES = [
|
||||
'modelstudio',
|
||||
'ollama',
|
||||
'custom',
|
||||
'cat89',
|
||||
] as const;
|
||||
|
||||
export const BUILTIN_PROVIDER_TYPES = [
|
||||
@@ -27,6 +28,7 @@ export const BUILTIN_PROVIDER_TYPES = [
|
||||
'minimax-portal-cn',
|
||||
'modelstudio',
|
||||
'ollama',
|
||||
'cat89',
|
||||
] as const;
|
||||
|
||||
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ClawX</title>
|
||||
<title>89猫</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "clawx",
|
||||
"name": "cat89",
|
||||
"version": "0.4.8-alpha.0",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
@@ -25,9 +25,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "ClawX - Graphical AI Assistant based on OpenClaw",
|
||||
"description": "89猫 - Graphical AI Assistant based on OpenClaw",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"author": "ClawX Team",
|
||||
"author": "89猫 Team",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
10
src/App.tsx
10
src/App.tsx
@@ -10,6 +10,10 @@ import i18n from './i18n';
|
||||
import { MainLayout } from './components/layout/MainLayout';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { Models } from './pages/Models';
|
||||
import { Dashboard } from './pages/Dashboard';
|
||||
import { Management } from './pages/Management';
|
||||
import { Security } from './pages/Security';
|
||||
import { Faq } from './pages/Faq';
|
||||
import { Chat } from './pages/Chat';
|
||||
import { Agents } from './pages/Agents';
|
||||
import { Channels } from './pages/Channels';
|
||||
@@ -201,12 +205,16 @@ function App() {
|
||||
|
||||
{/* Main application routes */}
|
||||
<Route element={<MainLayout />}>
|
||||
<Route path="/" element={<Chat />} />
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/chat" element={<Chat />} />
|
||||
<Route path="/models" element={<Models />} />
|
||||
<Route path="/agents" element={<Agents />} />
|
||||
<Route path="/channels" element={<Channels />} />
|
||||
<Route path="/skills" element={<Skills />} />
|
||||
<Route path="/cron" element={<Cron />} />
|
||||
<Route path="/management" element={<Management />} />
|
||||
<Route path="/security" element={<Security />} />
|
||||
<Route path="/faq" element={<Faq />} />
|
||||
<Route path="/image-generation" element={devModeUnlocked ? <ImageGenerationPage /> : <Navigate to="/" replace />} />
|
||||
<Route path="/dreams" element={devModeUnlocked ? <Dreams /> : <Navigate to="/" replace />} />
|
||||
<Route path="/settings/*" element={<Settings />} />
|
||||
|
||||
@@ -20,10 +20,11 @@ import {
|
||||
Pencil,
|
||||
Check,
|
||||
X,
|
||||
Cpu,
|
||||
ImagePlus,
|
||||
Moon,
|
||||
ChevronRight,
|
||||
LayoutDashboard,
|
||||
MessageSquare,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { rendererExtensionRegistry } from '@/extensions/registry';
|
||||
@@ -157,7 +158,7 @@ export function Sidebar() {
|
||||
}, [isMac, sidebarCollapsed]);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const isOnChat = useLocation().pathname === '/';
|
||||
const isOnChat = useLocation().pathname === '/chat';
|
||||
|
||||
const getSessionLabel = (key: string, displayName?: string, label?: string) =>
|
||||
sessionLabels[key] ?? label ?? displayName ?? key;
|
||||
@@ -309,7 +310,8 @@ export function Sidebar() {
|
||||
const extraNavItems = rendererExtensionRegistry.getExtraNavItems();
|
||||
|
||||
const coreNavItems = [
|
||||
{ to: '/models', icon: <Cpu className="h-4 w-4" strokeWidth={2} />, label: t('sidebar.models'), testId: 'sidebar-nav-models' },
|
||||
{ to: '/', icon: <LayoutDashboard className="h-4 w-4" strokeWidth={2} />, label: t('sidebar.dashboard'), testId: 'sidebar-nav-dashboard' },
|
||||
{ to: '/chat', icon: <MessageSquare className="h-4 w-4" strokeWidth={2} />, label: t('sidebar.chat'), testId: 'sidebar-nav-chat' },
|
||||
{ to: '/agents', icon: <Bot className="h-4 w-4" strokeWidth={2} />, label: t('sidebar.agents'), testId: 'sidebar-nav-agents' },
|
||||
{ to: '/channels', icon: <Network className="h-4 w-4" strokeWidth={2} />, label: t('sidebar.channels'), testId: 'sidebar-nav-channels' },
|
||||
{ to: '/skills', icon: <Puzzle className="h-4 w-4" strokeWidth={2} />, label: t('sidebar.skills'), testId: 'sidebar-nav-skills' },
|
||||
@@ -359,9 +361,9 @@ export function Sidebar() {
|
||||
>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center gap-2 px-2 overflow-hidden">
|
||||
<img src={logoSvg} alt="ClawX" className="h-5 w-auto shrink-0" />
|
||||
<img src={logoSvg} alt="89猫" className="h-5 w-auto shrink-0" />
|
||||
<span className="text-sm font-semibold truncate whitespace-nowrap text-foreground/90">
|
||||
ClawX
|
||||
89猫
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -391,7 +393,7 @@ export function Sidebar() {
|
||||
onClick={() => {
|
||||
const { messages } = useChatStore.getState();
|
||||
if (messages.length > 0) newSession();
|
||||
navigate('/');
|
||||
navigate('/chat');
|
||||
}}
|
||||
className={cn(
|
||||
'sidebar-nav-text flex items-center gap-2 rounded-lg px-2.5 py-2 transition-colors',
|
||||
@@ -482,7 +484,7 @@ export function Sidebar() {
|
||||
} else {
|
||||
switchSession(s.key);
|
||||
}
|
||||
navigate('/');
|
||||
navigate('/chat');
|
||||
}}
|
||||
onDoubleClick={() => handleStartRename(s.key, sessionLabel)}
|
||||
className={cn(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"skills": "Skills",
|
||||
"agents": "Agents",
|
||||
"channels": "Channels",
|
||||
"dashboard": "Dashboard",
|
||||
"dashboard": "Home",
|
||||
"settings": "Settings",
|
||||
"devConsole": "Developer Console",
|
||||
"models": "Models",
|
||||
|
||||
@@ -1,4 +1,50 @@
|
||||
{
|
||||
"title": "89cat",
|
||||
"subtitle": "Your AI Smart Assistant",
|
||||
"cards": {
|
||||
"management": {
|
||||
"title": "Management",
|
||||
"desc": "Start or stop 89cat, access super admin console"
|
||||
},
|
||||
"tokenRecharge": {
|
||||
"title": "Token Recharge",
|
||||
"desc": "Visit 89.is89.com for token recharge"
|
||||
},
|
||||
"plugins": {
|
||||
"title": "Plugins",
|
||||
"desc": "Install and manage 89 plugins, OpenClaw skills"
|
||||
},
|
||||
"security": {
|
||||
"title": "Security Check",
|
||||
"desc": "Update virus definitions and scan system security"
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "Work Key",
|
||||
"desc": "Enter your API key to get started, no URL needed"
|
||||
},
|
||||
"faq": {
|
||||
"title": "FAQ",
|
||||
"desc": "View usage help and frequently asked questions"
|
||||
},
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"desc": "Switch the interface display language"
|
||||
}
|
||||
},
|
||||
"apiKey": {
|
||||
"placeholder": "Enter API Key",
|
||||
"validate": "Validate",
|
||||
"valid": "Key verified successfully",
|
||||
"invalidKey": "Invalid key, please check and try again",
|
||||
"validateFailed": "Validation failed, please check network"
|
||||
},
|
||||
"gatewayRunning": "89cat Running",
|
||||
"gatewayStopped": "89cat Stopped",
|
||||
"stop89cat": "Stop 89cat",
|
||||
"start89cat": "Start 89cat",
|
||||
"adminConsole": "89 Super AI Brain Management Center",
|
||||
"adminConsoleDesc": "Management center runs in the cloud to protect your privacy",
|
||||
"openAdminConsole": "Open Admin Console",
|
||||
"gateway": "Gateway",
|
||||
"channels": "Channels",
|
||||
"skills": "Skills",
|
||||
@@ -110,5 +156,55 @@
|
||||
"relayModelRequired": "Model name is required",
|
||||
"relayModelInvalid": "Enter only the model name, for example gpt-image-2. Do not include provider/model."
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"virusDbVersion": "Virus DB Version",
|
||||
"lastScan": "Last Scan",
|
||||
"updateDb": "Update Virus DB",
|
||||
"startScan": "Start Scan",
|
||||
"idle": "Waiting for virus database update",
|
||||
"updatingDb": "Updating virus database...",
|
||||
"readyToScan": "Virus database ready",
|
||||
"scanning": "Scanning...",
|
||||
"scanComplete": "Scan Complete",
|
||||
"filesScanned": "Files Scanned",
|
||||
"threatsFound": "{{count}} threat(s) found",
|
||||
"noThreats": "No threats found",
|
||||
"neverScanned": "Never scanned",
|
||||
"updateDbFirst": "Please update virus database first",
|
||||
"clickToScan": "Virus database is ready, you can start scanning",
|
||||
"scanResult": "Scan completed",
|
||||
"scanSummary": "Scanned {{files}} files in {{time}} seconds"
|
||||
},
|
||||
"faq": {
|
||||
"sections": {
|
||||
"install": "Installation",
|
||||
"usage": "Usage",
|
||||
"key": "API Key",
|
||||
"security": "Security",
|
||||
"other": "Other"
|
||||
},
|
||||
"q": {
|
||||
"howToInstall": "How to install 89cat?",
|
||||
"systemRequirements": "What are the system requirements?",
|
||||
"howToUse": "How to start using 89cat?",
|
||||
"stopRestart": "How to stop or restart 89cat?",
|
||||
"whereToGetKey": "Where to get an API Key?",
|
||||
"keyNotWorking": "What if my key shows as invalid?",
|
||||
"isMyDataSafe": "Is my data safe?",
|
||||
"howToScan": "How to run a security check?",
|
||||
"contactUs": "How to contact support?"
|
||||
},
|
||||
"a": {
|
||||
"howToInstall": "Download the installer for your platform from the official website, double-click to run and it will complete installation automatically. After installation, 89cat icons will appear on your desktop and start menu.",
|
||||
"systemRequirements": "Supports Windows 10+, macOS 11+, Linux (Ubuntu 20.04+). Minimum 4GB RAM, 8GB recommended. 1GB free disk space required.",
|
||||
"howToUse": "Enter your API Key in the \"Work Key\" card on the home page and click Validate. Once verified, you can chat with AI on the Chat page.",
|
||||
"stopRestart": "Go to the \"Management\" page, click \"Stop 89cat\" to stop the service, and click \"Start 89cat\" to restart.",
|
||||
"whereToGetKey": "Visit 89.is89.com to register an account and obtain an API Key.",
|
||||
"keyNotWorking": "Check if the key is entered correctly (case-sensitive and no spaces), confirm it hasn't expired, and verify network connectivity. Contact support if issues persist.",
|
||||
"isMyDataSafe": "89cat securely stores your API Key using the system's native keychain. The management center runs in the cloud while your personal data stays local, ensuring full privacy protection.",
|
||||
"howToScan": "Go to the \"Security Check\" page, click \"Update Virus DB\" first, then click \"Start Scan\" to perform a full system security scan.",
|
||||
"contactUs": "Send email to public@valuecell.ai or visit 89.is89.com for more help."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"title": "Settings",
|
||||
"subtitle": "Configure your ClawX experience",
|
||||
"subtitle": "Configure your 89cat experience",
|
||||
"appearance": {
|
||||
"title": "General",
|
||||
"description": "Customize the look and feel",
|
||||
@@ -10,7 +10,7 @@
|
||||
"system": "System",
|
||||
"language": "Language",
|
||||
"launchAtStartup": "Launch at system startup",
|
||||
"launchAtStartupDesc": "Automatically launch ClawX when you log in"
|
||||
"launchAtStartupDesc": "Automatically launch 89cat when you log in"
|
||||
},
|
||||
"aiProviders": {
|
||||
"title": "AI Providers",
|
||||
@@ -37,7 +37,7 @@
|
||||
"notRequired": "Not required",
|
||||
"empty": {
|
||||
"title": "No providers configured",
|
||||
"desc": "Add an AI provider to start using ClawX",
|
||||
"desc": "Add an AI provider to start using 89cat",
|
||||
"cta": "Add Your First Provider"
|
||||
},
|
||||
"dialog": {
|
||||
@@ -60,7 +60,7 @@
|
||||
"protocol": "Protocol",
|
||||
"advancedConfig": "Advanced configuration",
|
||||
"userAgent": "User-Agent",
|
||||
"userAgentPlaceholder": "ClawX/1.0",
|
||||
"userAgentPlaceholder": "89cat/1.0",
|
||||
"fallbackModels": "Fallback Models",
|
||||
"fallbackProviders": "Fallback Providers",
|
||||
"fallbackModelIds": "Fallback Model IDs",
|
||||
@@ -135,7 +135,7 @@
|
||||
"appLogs": "Application Logs",
|
||||
"openFolder": "Open Folder",
|
||||
"autoStart": "Auto-start Gateway",
|
||||
"autoStartDesc": "Start Gateway when ClawX launches",
|
||||
"autoStartDesc": "Start Gateway when 89cat launches",
|
||||
"proxyTitle": "Proxy",
|
||||
"proxyDesc": "Route Electron and Gateway traffic through your local proxy client.",
|
||||
"proxyServer": "Proxy Server",
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
"updates": {
|
||||
"title": "Updates",
|
||||
"description": "Keep ClawX up to date",
|
||||
"description": "Keep 89cat up to date",
|
||||
"autoCheck": "Auto-check for updates",
|
||||
"autoCheckDesc": "Check for updates on startup",
|
||||
"autoDownload": "Update prompts",
|
||||
@@ -185,13 +185,13 @@
|
||||
"whatsNew": "What's New:",
|
||||
"errorDetails": "Error Details:",
|
||||
"toast": {
|
||||
"availableTitle": "ClawX update available",
|
||||
"availableTitle": "89cat update available",
|
||||
"availableDescription": "Version v{{version}} is available. Download it when you're ready.",
|
||||
"downloadedTitle": "ClawX update ready",
|
||||
"downloadedTitle": "89cat update ready",
|
||||
"downloadedDescription": "Version v{{version}} has been downloaded. Restart to install it.",
|
||||
"unknownVersion": "new"
|
||||
},
|
||||
"help": "ClawX checks for updates automatically and prompts you before downloading or installing."
|
||||
"help": "89cat checks for updates automatically and prompts you before downloading or installing."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Advanced",
|
||||
@@ -218,7 +218,7 @@
|
||||
"devMode": "Developer Mode",
|
||||
"devModeDesc": "Show developer tools and shortcuts",
|
||||
"telemetry": "Anonymous Usage Data",
|
||||
"telemetryDesc": "Allow providing anonymous basic usage data to improve ClawX"
|
||||
"telemetryDesc": "Allow providing anonymous basic usage data to improve 89cat"
|
||||
},
|
||||
"developer": {
|
||||
"title": "Developer",
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"about": {
|
||||
"title": "About",
|
||||
"appName": "ClawX",
|
||||
"appName": "89cat",
|
||||
"tagline": "Graphical AI Assistant",
|
||||
"basedOn": "Based on OpenClaw",
|
||||
"version": "Version {{version}}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"steps": {
|
||||
"welcome": {
|
||||
"title": "Welcome to ClawX",
|
||||
"title": "Welcome to 89cat",
|
||||
"description": "Your AI assistant is ready to be configured"
|
||||
},
|
||||
"runtime": {
|
||||
@@ -22,12 +22,12 @@
|
||||
},
|
||||
"complete": {
|
||||
"title": "All Set!",
|
||||
"description": "ClawX is ready to use"
|
||||
"description": "89cat is ready to use"
|
||||
}
|
||||
},
|
||||
"welcome": {
|
||||
"title": "Welcome to ClawX",
|
||||
"description": "ClawX is a graphical interface for OpenClaw, making it easy to use AI assistants across your favorite messaging platforms.",
|
||||
"title": "Welcome to 89cat",
|
||||
"description": "89cat is a graphical interface for OpenClaw, making it easy to use AI assistants across your favorite messaging platforms.",
|
||||
"features": {
|
||||
"noCommand": "Zero command-line required",
|
||||
"modernUI": "Modern, beautiful interface",
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"complete": {
|
||||
"title": "Setup Complete!",
|
||||
"subtitle": "ClawX is configured and ready to use. You can now start chatting with your AI assistant.",
|
||||
"subtitle": "89cat is configured and ready to use. You can now start chatting with your AI assistant.",
|
||||
"provider": "AI Provider",
|
||||
"components": "Components",
|
||||
"gateway": "Gateway",
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
"builtIn": "Built-in ({{count}})",
|
||||
"marketplace": "Marketplace ({{count}})",
|
||||
"enabledList": "Enabled ({{count}})",
|
||||
"disabledList": "Disabled ({{count}})"
|
||||
"disabledList": "Disabled ({{count}})",
|
||||
"category": {
|
||||
"all": "All",
|
||||
"cat89": "89 Plugins",
|
||||
"openclaw": "OpenClaw Skills",
|
||||
"other": "Other Plugins"
|
||||
}
|
||||
},
|
||||
"search": "Search skills...",
|
||||
"searchMarketplace": "Search marketplace...",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"title": "設定",
|
||||
"subtitle": "ClawX の体験をカスタマイズ",
|
||||
"subtitle": "89猫 の体験をカスタマイズ",
|
||||
"appearance": {
|
||||
"title": "通用",
|
||||
"description": "外観とスタイルをカスタマイズ",
|
||||
@@ -10,7 +10,7 @@
|
||||
"system": "システム",
|
||||
"language": "言語",
|
||||
"launchAtStartup": "システム起動時に自動起動",
|
||||
"launchAtStartupDesc": "ログイン時に ClawX を自動的に起動します"
|
||||
"launchAtStartupDesc": "ログイン時に 89猫 を自動的に起動します"
|
||||
},
|
||||
"aiProviders": {
|
||||
"title": "AI プロバイダー",
|
||||
@@ -37,7 +37,7 @@
|
||||
"notRequired": "不要",
|
||||
"empty": {
|
||||
"title": "プロバイダーが構成されていません",
|
||||
"desc": "ClawX の使用を開始するには AI プロバイダーを追加してください",
|
||||
"desc": "89猫 の使用を開始するには AI プロバイダーを追加してください",
|
||||
"cta": "最初のプロバイダーを追加"
|
||||
},
|
||||
"dialog": {
|
||||
@@ -60,7 +60,7 @@
|
||||
"protocol": "プロトコル",
|
||||
"advancedConfig": "詳細設定",
|
||||
"userAgent": "User-Agent",
|
||||
"userAgentPlaceholder": "ClawX/1.0",
|
||||
"userAgentPlaceholder": "89猫/1.0",
|
||||
"fallbackModels": "フォールバックモデル",
|
||||
"fallbackProviders": "別プロバイダーへのフォールバック",
|
||||
"fallbackModelIds": "同一プロバイダーのフォールバックモデル ID",
|
||||
@@ -135,7 +135,7 @@
|
||||
"appLogs": "アプリケーションログ",
|
||||
"openFolder": "フォルダーを開く",
|
||||
"autoStart": "ゲートウェイ自動起動",
|
||||
"autoStartDesc": "ClawX 起動時にゲートウェイを自動起動",
|
||||
"autoStartDesc": "89猫 起動時にゲートウェイを自動起動",
|
||||
"proxyTitle": "プロキシ",
|
||||
"proxyDesc": "Electron と Gateway の通信をローカルプロキシ経由にします。",
|
||||
"proxyServer": "プロキシサーバー",
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
"updates": {
|
||||
"title": "アップデート",
|
||||
"description": "ClawX を最新に保つ",
|
||||
"description": "89猫 を最新に保つ",
|
||||
"autoCheck": "自動更新チェック",
|
||||
"autoCheckDesc": "起動時に更新を確認",
|
||||
"autoDownload": "更新通知",
|
||||
@@ -185,13 +185,13 @@
|
||||
"whatsNew": "更新内容:",
|
||||
"errorDetails": "エラー詳細:",
|
||||
"toast": {
|
||||
"availableTitle": "ClawX の更新があります",
|
||||
"availableTitle": "89猫 の更新があります",
|
||||
"availableDescription": "バージョン v{{version}} を利用できます。準備ができたらダウンロードしてください。",
|
||||
"downloadedTitle": "ClawX の更新準備ができました",
|
||||
"downloadedTitle": "89猫 の更新準備ができました",
|
||||
"downloadedDescription": "バージョン v{{version}} のダウンロードが完了しました。再起動してインストールしてください。",
|
||||
"unknownVersion": "新しい"
|
||||
},
|
||||
"help": "ClawX は自動的に更新を確認し、ダウンロードやインストールの前に通知します。"
|
||||
"help": "89猫 は自動的に更新を確認し、ダウンロードやインストールの前に通知します。"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "詳細設定",
|
||||
@@ -218,7 +218,7 @@
|
||||
"devMode": "開発者モード",
|
||||
"devModeDesc": "開発者ツールとショートカットを表示",
|
||||
"telemetry": "匿名利用データ",
|
||||
"telemetryDesc": "ClawX 改善のため、匿名の基本的な利用データの提供を許可します"
|
||||
"telemetryDesc": "89猫 改善のため、匿名の基本的な利用データの提供を許可します"
|
||||
},
|
||||
"developer": {
|
||||
"title": "開発者",
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"about": {
|
||||
"title": "バージョン情報",
|
||||
"appName": "ClawX",
|
||||
"appName": "89猫",
|
||||
"tagline": "グラフィカル AI アシスタント",
|
||||
"basedOn": "OpenClaw ベース",
|
||||
"version": "バージョン {{version}}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"steps": {
|
||||
"welcome": {
|
||||
"title": "ClawXへようこそ",
|
||||
"title": "89猫へようこそ",
|
||||
"description": "AIアシスタントの設定準備が整いました"
|
||||
},
|
||||
"runtime": {
|
||||
@@ -22,12 +22,12 @@
|
||||
},
|
||||
"complete": {
|
||||
"title": "完了!",
|
||||
"description": "ClawXを使用する準備が整いました"
|
||||
"description": "89猫を使用する準備が整いました"
|
||||
}
|
||||
},
|
||||
"welcome": {
|
||||
"title": "ClawXへようこそ",
|
||||
"description": "ClawXはOpenClawのグラフィカルインターフェースで、お気に入りのメッセージングプラットフォームでAIアシスタントを簡単に使用できます。",
|
||||
"title": "89猫へようこそ",
|
||||
"description": "89猫はOpenClawのグラフィカルインターフェースで、お気に入りのメッセージングプラットフォームでAIアシスタントを簡単に使用できます。",
|
||||
"features": {
|
||||
"noCommand": "コマンドライン不要",
|
||||
"modernUI": "モダンで美しいインターフェース",
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"complete": {
|
||||
"title": "セットアップ完了!",
|
||||
"subtitle": "ClawXの構成が完了し、使用準備が整いました。AIアシスタントとのチャットを開始できます。",
|
||||
"subtitle": "89猫の構成が完了し、使用準備が整いました。AIアシスタントとのチャットを開始できます。",
|
||||
"provider": "AI プロバイダー",
|
||||
"components": "コンポーネント",
|
||||
"gateway": "ゲートウェイ",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"title": "Настройки",
|
||||
"subtitle": "Настройте ваш опыт работы с ClawX",
|
||||
"subtitle": "Настройте ваш опыт работы с 89cat",
|
||||
"appearance": {
|
||||
"title": "Общие",
|
||||
"description": "Настройте внешний вид",
|
||||
@@ -10,7 +10,7 @@
|
||||
"system": "Системная",
|
||||
"language": "Язык",
|
||||
"launchAtStartup": "Запуск при старте системы",
|
||||
"launchAtStartupDesc": "Автоматически запускать ClawX при входе в систему"
|
||||
"launchAtStartupDesc": "Автоматически запускать 89cat при входе в систему"
|
||||
},
|
||||
"aiProviders": {
|
||||
"title": "AI-провайдеры",
|
||||
@@ -37,7 +37,7 @@
|
||||
"notRequired": "Не требуется",
|
||||
"empty": {
|
||||
"title": "Провайдеры не настроены",
|
||||
"desc": "Добавьте AI-провайдера, чтобы начать использовать ClawX",
|
||||
"desc": "Добавьте AI-провайдера, чтобы начать использовать 89cat",
|
||||
"cta": "Добавить первого провайдера"
|
||||
},
|
||||
"dialog": {
|
||||
@@ -60,7 +60,7 @@
|
||||
"protocol": "Протокол",
|
||||
"advancedConfig": "Расширенная конфигурация",
|
||||
"userAgent": "User-Agent",
|
||||
"userAgentPlaceholder": "ClawX/1.0",
|
||||
"userAgentPlaceholder": "89cat/1.0",
|
||||
"fallbackModels": "Резервные модели",
|
||||
"fallbackProviders": "Резервные провайдеры",
|
||||
"fallbackModelIds": "ID резервных моделей",
|
||||
@@ -135,7 +135,7 @@
|
||||
"appLogs": "Журналы приложения",
|
||||
"openFolder": "Открыть папку",
|
||||
"autoStart": "Автозапуск шлюза",
|
||||
"autoStartDesc": "Запускать шлюз при старте ClawX",
|
||||
"autoStartDesc": "Запускать шлюз при старте 89cat",
|
||||
"proxyTitle": "Прокси",
|
||||
"proxyDesc": "Направлять трафик Electron и шлюза через локальный прокси-клиент.",
|
||||
"proxyServer": "Прокси-сервер",
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
"updates": {
|
||||
"title": "Обновления",
|
||||
"description": "Поддерживайте ClawX в актуальном состоянии",
|
||||
"description": "Поддерживайте 89cat в актуальном состоянии",
|
||||
"autoCheck": "Автопроверка обновлений",
|
||||
"autoCheckDesc": "Проверять обновления при запуске",
|
||||
"autoDownload": "Уведомления об обновлениях",
|
||||
@@ -185,13 +185,13 @@
|
||||
"whatsNew": "Что нового:",
|
||||
"errorDetails": "Детали ошибки:",
|
||||
"toast": {
|
||||
"availableTitle": "Доступно обновление ClawX",
|
||||
"availableTitle": "Доступно обновление 89cat",
|
||||
"availableDescription": "Доступна версия v{{version}}. Скачайте её, когда будете готовы.",
|
||||
"downloadedTitle": "Обновление ClawX готово",
|
||||
"downloadedTitle": "Обновление 89cat готово",
|
||||
"downloadedDescription": "Версия v{{version}} загружена. Перезапустите приложение для установки.",
|
||||
"unknownVersion": "новая"
|
||||
},
|
||||
"help": "ClawX автоматически проверяет обновления и показывает уведомление перед загрузкой или установкой."
|
||||
"help": "89cat автоматически проверяет обновления и показывает уведомление перед загрузкой или установкой."
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Дополнительные",
|
||||
@@ -218,7 +218,7 @@
|
||||
"devMode": "Режим разработчика",
|
||||
"devModeDesc": "Показывать инструменты и ярлыки разработчика",
|
||||
"telemetry": "Анонимные данные об использовании",
|
||||
"telemetryDesc": "Разрешить предоставление анонимных базовых данных об использовании для улучшения ClawX"
|
||||
"telemetryDesc": "Разрешить предоставление анонимных базовых данных об использовании для улучшения 89cat"
|
||||
},
|
||||
"developer": {
|
||||
"title": "Разработчик",
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"about": {
|
||||
"title": "О приложении",
|
||||
"appName": "ClawX",
|
||||
"appName": "89cat",
|
||||
"tagline": "Графический AI-ассистент",
|
||||
"basedOn": "На базе OpenClaw",
|
||||
"version": "Версия {{version}}",
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
},
|
||||
"complete": {
|
||||
"title": "Готово!",
|
||||
"description": "ClawX готов к использованию"
|
||||
"description": "89cat готов к использованию"
|
||||
}
|
||||
},
|
||||
"welcome": {
|
||||
"title": "Добро пожаловать в ClawX",
|
||||
"description": "ClawX — это графический интерфейс для OpenClaw, упрощающий использование AI-ассистентов через ваши любимые платформы обмена сообщениями.",
|
||||
"title": "Добро пожаловать в 89cat",
|
||||
"description": "89cat — это графический интерфейс для OpenClaw, упрощающий использование AI-ассистентов через ваши любимые платформы обмена сообщениями.",
|
||||
"features": {
|
||||
"noCommand": "Без командной строки",
|
||||
"modernUI": "Современный красивый интерфейс",
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"complete": {
|
||||
"title": "Настройка завершена!",
|
||||
"subtitle": "ClawX настроен и готов к использованию. Теперь вы можете начать общение с вашим AI-ассистентом.",
|
||||
"subtitle": "89cat настроен и готов к использованию. Теперь вы можете начать общение с вашим AI-ассистентом.",
|
||||
"provider": "AI-провайдер",
|
||||
"components": "Компоненты",
|
||||
"gateway": "Шлюз",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"skills": "技能",
|
||||
"agents": "Agents",
|
||||
"channels": "频道",
|
||||
"dashboard": "仪表盘",
|
||||
"dashboard": "首页",
|
||||
"settings": "设置",
|
||||
"devConsole": "开发者控制台",
|
||||
"models": "模型",
|
||||
|
||||
@@ -1,4 +1,50 @@
|
||||
{
|
||||
"title": "89猫",
|
||||
"subtitle": "你的AI智能助手",
|
||||
"cards": {
|
||||
"management": {
|
||||
"title": "管理中心",
|
||||
"desc": "停止或启动89猫,访问超级管理后台"
|
||||
},
|
||||
"tokenRecharge": {
|
||||
"title": "Token充值",
|
||||
"desc": "前往89.is89.com进行Token充值"
|
||||
},
|
||||
"plugins": {
|
||||
"title": "插件安装",
|
||||
"desc": "安装和管理89插件、OpenClaw技能"
|
||||
},
|
||||
"security": {
|
||||
"title": "安全检查",
|
||||
"desc": "升级病毒库并扫描系统安全"
|
||||
},
|
||||
"apiKey": {
|
||||
"title": "工作钥匙",
|
||||
"desc": "输入API Key即可开始工作,无需填写网址"
|
||||
},
|
||||
"faq": {
|
||||
"title": "常见问题",
|
||||
"desc": "查看使用帮助和常见问题解答"
|
||||
},
|
||||
"language": {
|
||||
"title": "语言选择",
|
||||
"desc": "切换界面显示语言"
|
||||
}
|
||||
},
|
||||
"apiKey": {
|
||||
"placeholder": "请输入API Key",
|
||||
"validate": "验证",
|
||||
"valid": "Key验证通过",
|
||||
"invalidKey": "Key无效,请检查后重试",
|
||||
"validateFailed": "验证失败,请检查网络连接"
|
||||
},
|
||||
"gatewayRunning": "89猫运行中",
|
||||
"gatewayStopped": "89猫已停止",
|
||||
"stop89cat": "停止使用89猫",
|
||||
"start89cat": "打开使用89猫",
|
||||
"adminConsole": "89超级人工智能脑管理中心",
|
||||
"adminConsoleDesc": "管理中心运行在云端,保护您的个人隐私",
|
||||
"openAdminConsole": "前往管理中心",
|
||||
"gateway": "网关",
|
||||
"channels": "频道",
|
||||
"skills": "技能",
|
||||
@@ -110,5 +156,55 @@
|
||||
"relayModelRequired": "请填写模型名",
|
||||
"relayModelInvalid": "请只填写模型名,例如 gpt-image-2;不要填写 provider/model。"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"virusDbVersion": "病毒库版本",
|
||||
"lastScan": "上次扫描时间",
|
||||
"updateDb": "升级病毒库",
|
||||
"startScan": "开始扫描",
|
||||
"idle": "等待升级病毒库",
|
||||
"updatingDb": "正在升级病毒库...",
|
||||
"readyToScan": "病毒库已就绪",
|
||||
"scanning": "正在扫描中...",
|
||||
"scanComplete": "扫描完成",
|
||||
"filesScanned": "扫描文件数",
|
||||
"threatsFound": "发现 {{count}} 个威胁",
|
||||
"noThreats": "未发现威胁",
|
||||
"neverScanned": "暂未扫描",
|
||||
"updateDbFirst": "请先升级病毒库",
|
||||
"clickToScan": "病毒库已就绪,可以开始扫描",
|
||||
"scanResult": "扫描已完成",
|
||||
"scanSummary": "共扫描 {{files}} 个文件,耗时 {{time}} 秒"
|
||||
},
|
||||
"faq": {
|
||||
"sections": {
|
||||
"install": "安装相关",
|
||||
"usage": "使用相关",
|
||||
"key": "Key相关",
|
||||
"security": "安全相关",
|
||||
"other": "其他"
|
||||
},
|
||||
"q": {
|
||||
"howToInstall": "如何安装89猫?",
|
||||
"systemRequirements": "89猫的系统要求是什么?",
|
||||
"howToUse": "如何开始使用89猫?",
|
||||
"stopRestart": "如何停止或重启89猫?",
|
||||
"whereToGetKey": "从哪里获取API Key?",
|
||||
"keyNotWorking": "Key显示无效怎么办?",
|
||||
"isMyDataSafe": "我的数据安全吗?",
|
||||
"howToScan": "如何进行安全检查?",
|
||||
"contactUs": "如何联系客服?"
|
||||
},
|
||||
"a": {
|
||||
"howToInstall": "从官方网站下载对应平台的安装包,双击运行即可自动完成安装。安装完成后桌面和开始菜单都会出现89猫图标。",
|
||||
"systemRequirements": "支持 Windows 10+、macOS 11+、Linux (Ubuntu 20.04+)。最低需要4GB内存,推荐8GB。需要1GB可用磁盘空间。",
|
||||
"howToUse": "在首页的\"工作钥匙\"卡片中输入你的API Key并点击验证,验证通过后即可在聊天页面与AI对话。",
|
||||
"stopRestart": "进入\"管理中心\"页面,点击\"停止使用89猫\"按钮即可停止服务,再次点击\"打开使用89猫\"即可重新启动。",
|
||||
"whereToGetKey": "请访问 89.is89.com 注册账号并获取API Key。",
|
||||
"keyNotWorking": "请检查Key是否输入正确(注意大小写和空格),确认Key未过期,确认网络连接正常。如仍有问题,请联系客服。",
|
||||
"isMyDataSafe": "89猫使用系统原生密钥链安全存储你的API Key。管理中心运行在云端,你的个人数据保留在本地,隐私受到充分保护。",
|
||||
"howToScan": "进入\"安全检查\"页面,先点击\"升级病毒库\",升级完成后点击\"开始扫描\"即可对系统进行全面安全扫描。",
|
||||
"contactUs": "请发送邮件至 public@valuecell.ai 或访问 89.is89.com 获取更多帮助。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"title": "设置",
|
||||
"subtitle": "配置您的 ClawX 体验",
|
||||
"subtitle": "配置您的 89猫 体验",
|
||||
"appearance": {
|
||||
"title": "通用",
|
||||
"description": "自定义外观和风格",
|
||||
@@ -10,7 +10,7 @@
|
||||
"system": "跟随系统",
|
||||
"language": "语言",
|
||||
"launchAtStartup": "开机自动启动",
|
||||
"launchAtStartupDesc": "登录系统后自动启动 ClawX"
|
||||
"launchAtStartupDesc": "登录系统后自动启动 89猫"
|
||||
},
|
||||
"aiProviders": {
|
||||
"title": "AI 模型提供商",
|
||||
@@ -37,7 +37,7 @@
|
||||
"notRequired": "非必填",
|
||||
"empty": {
|
||||
"title": "未配置提供商",
|
||||
"desc": "添加 AI 提供商以开始使用 ClawX",
|
||||
"desc": "添加 AI 提供商以开始使用 89猫",
|
||||
"cta": "添加您的第一个提供商"
|
||||
},
|
||||
"dialog": {
|
||||
@@ -60,7 +60,7 @@
|
||||
"protocol": "协议",
|
||||
"advancedConfig": "高级配置",
|
||||
"userAgent": "User-Agent",
|
||||
"userAgentPlaceholder": "ClawX/1.0",
|
||||
"userAgentPlaceholder": "89猫/1.0",
|
||||
"fallbackModels": "回退模型",
|
||||
"fallbackProviders": "跨 Provider 回退",
|
||||
"fallbackModelIds": "同 Provider 回退模型 ID",
|
||||
@@ -135,7 +135,7 @@
|
||||
"appLogs": "应用日志",
|
||||
"openFolder": "打开文件夹",
|
||||
"autoStart": "自动启动网关",
|
||||
"autoStartDesc": "ClawX 启动时自动启动网关",
|
||||
"autoStartDesc": "89猫 启动时自动启动网关",
|
||||
"proxyTitle": "代理",
|
||||
"proxyDesc": "让 Electron 和 Gateway 的网络请求都走本地代理客户端。",
|
||||
"proxyServer": "代理服务器",
|
||||
@@ -156,7 +156,7 @@
|
||||
},
|
||||
"updates": {
|
||||
"title": "更新",
|
||||
"description": "保持 ClawX 最新",
|
||||
"description": "保持 89猫 最新",
|
||||
"autoCheck": "自动检查更新",
|
||||
"autoCheckDesc": "启动时检查更新",
|
||||
"autoDownload": "更新提示",
|
||||
@@ -185,13 +185,13 @@
|
||||
"whatsNew": "更新内容:",
|
||||
"errorDetails": "错误详情:",
|
||||
"toast": {
|
||||
"availableTitle": "ClawX 有新版本可用",
|
||||
"availableTitle": "89猫 有新版本可用",
|
||||
"availableDescription": "v{{version}} 已可下载。准备好后可开始下载。",
|
||||
"downloadedTitle": "ClawX 更新已就绪",
|
||||
"downloadedTitle": "89猫 更新已就绪",
|
||||
"downloadedDescription": "v{{version}} 已下载完成。重启即可安装。",
|
||||
"unknownVersion": "新版本"
|
||||
},
|
||||
"help": "ClawX 会自动检查更新,并在下载或安装前提示您确认。"
|
||||
"help": "89猫 会自动检查更新,并在下载或安装前提示您确认。"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "高级",
|
||||
@@ -218,7 +218,7 @@
|
||||
"devMode": "开发者模式",
|
||||
"devModeDesc": "显示开发者工具和快捷方式",
|
||||
"telemetry": "匿名使用数据",
|
||||
"telemetryDesc": "允许提供匿名的基础使用数据,用于改进 ClawX"
|
||||
"telemetryDesc": "允许提供匿名的基础使用数据,用于改进 89猫"
|
||||
},
|
||||
"developer": {
|
||||
"title": "开发者",
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"about": {
|
||||
"title": "关于",
|
||||
"appName": "ClawX",
|
||||
"appName": "89猫",
|
||||
"tagline": "图形化 AI 助手",
|
||||
"basedOn": "基于 OpenClaw",
|
||||
"version": "版本 {{version}}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"steps": {
|
||||
"welcome": {
|
||||
"title": "欢迎使用 ClawX",
|
||||
"title": "欢迎使用 89猫",
|
||||
"description": "您的 AI 助手已准备好进行配置"
|
||||
},
|
||||
"runtime": {
|
||||
@@ -22,12 +22,12 @@
|
||||
},
|
||||
"complete": {
|
||||
"title": "准备就绪!",
|
||||
"description": "ClawX 已准备好使用"
|
||||
"description": "89猫 已准备好使用"
|
||||
}
|
||||
},
|
||||
"welcome": {
|
||||
"title": "欢迎使用 ClawX",
|
||||
"description": "ClawX 是 OpenClaw 的图形界面,让您可以在喜爱的消息平台上轻松使用 AI 助手。",
|
||||
"title": "欢迎使用 89猫",
|
||||
"description": "89猫 是 OpenClaw 的图形界面,让您可以在喜爱的消息平台上轻松使用 AI 助手。",
|
||||
"features": {
|
||||
"noCommand": "无需命令行",
|
||||
"modernUI": "现代美观的界面",
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"complete": {
|
||||
"title": "设置完成!",
|
||||
"subtitle": "ClawX 已配置并准备就绪。您现在可以开始与您的 AI 助手聊天了。",
|
||||
"subtitle": "89猫 已配置并准备就绪。您现在可以开始与您的 AI 助手聊天了。",
|
||||
"provider": "AI 提供商",
|
||||
"components": "组件",
|
||||
"gateway": "网关",
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
"builtIn": "内置 ({{count}})",
|
||||
"marketplace": "市场 ({{count}})",
|
||||
"enabledList": "已开启 ({{count}})",
|
||||
"disabledList": "已禁用 ({{count}})"
|
||||
"disabledList": "已禁用 ({{count}})",
|
||||
"category": {
|
||||
"all": "全部",
|
||||
"cat89": "89插件",
|
||||
"openclaw": "OpenClaw 技能",
|
||||
"other": "其他插件"
|
||||
}
|
||||
},
|
||||
"search": "搜索技能...",
|
||||
"searchMarketplace": "搜索市场...",
|
||||
|
||||
@@ -21,6 +21,7 @@ export const PROVIDER_TYPES = [
|
||||
'modelstudio',
|
||||
'ollama',
|
||||
'custom',
|
||||
'cat89',
|
||||
] as const;
|
||||
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
||||
|
||||
@@ -196,6 +197,14 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [
|
||||
docsUrl: 'https://icnnp7d0dymg.feishu.cn/wiki/BmiLwGBcEiloZDkdYnGc8RWnn6d#Ee1ldfvKJoVGvfxc32mcILwenth',
|
||||
docsUrlZh: 'https://icnnp7d0dymg.feishu.cn/wiki/BmiLwGBcEiloZDkdYnGc8RWnn6d#IWQCdfe5fobGU3xf3UGcgbLynGh',
|
||||
},
|
||||
{
|
||||
id: 'cat89',
|
||||
name: '89猫',
|
||||
icon: '🐱',
|
||||
placeholder: '输入你的89猫 Key',
|
||||
model: '89猫 AI',
|
||||
requiresApiKey: true,
|
||||
},
|
||||
];
|
||||
|
||||
/** Get the SVG logo URL for a provider type, falls back to undefined */
|
||||
|
||||
270
src/pages/Dashboard/index.tsx
Normal file
270
src/pages/Dashboard/index.tsx
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Dashboard page — main landing page with 7 feature entry cards
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Building2,
|
||||
Coins,
|
||||
Puzzle,
|
||||
ShieldCheck,
|
||||
KeyRound,
|
||||
HelpCircle,
|
||||
Globe,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Check,
|
||||
X,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { SUPPORTED_LANGUAGES } from '@/i18n';
|
||||
import { hostApiFetch } from '@/lib/host-api';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
function ApiKeyCard() {
|
||||
const { t } = useTranslation('dashboard');
|
||||
const [showKey, setShowKey] = useState(false);
|
||||
const [key, setKey] = useState('');
|
||||
const [validating, setValidating] = useState(false);
|
||||
const [result, setResult] = useState<'idle' | 'valid' | 'invalid'>('idle');
|
||||
const [errorMsg, setErrorMsg] = useState('');
|
||||
|
||||
const handleValidate = async () => {
|
||||
const trimmed = key.trim();
|
||||
if (!trimmed) return;
|
||||
setValidating(true);
|
||||
setResult('idle');
|
||||
setErrorMsg('');
|
||||
try {
|
||||
const res = await hostApiFetch<{ valid: boolean; message?: string }>(
|
||||
'/api/cat89/validate-key',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ apiKey: trimmed }),
|
||||
},
|
||||
);
|
||||
if (res.valid) {
|
||||
setResult('valid');
|
||||
} else {
|
||||
setResult('invalid');
|
||||
setErrorMsg(res.message || t('apiKey.invalidKey'));
|
||||
}
|
||||
} catch {
|
||||
setResult('invalid');
|
||||
setErrorMsg(t('apiKey.validateFailed'));
|
||||
} finally {
|
||||
setValidating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
void handleValidate();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
type={showKey ? 'text' : 'password'}
|
||||
placeholder={t('apiKey.placeholder')}
|
||||
value={key}
|
||||
onChange={(e) => {
|
||||
setKey(e.target.value);
|
||||
setResult('idle');
|
||||
setErrorMsg('');
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={cn(
|
||||
'pr-10 font-mono',
|
||||
result === 'valid' && 'border-green-500 focus-visible:ring-green-500',
|
||||
result === 'invalid' && 'border-red-500 focus-visible:ring-red-500',
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowKey(!showKey)}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => { void handleValidate(); }}
|
||||
disabled={validating || !key.trim()}
|
||||
className="shrink-0"
|
||||
>
|
||||
{validating ? <Loader2 className="h-4 w-4 animate-spin" /> : t('apiKey.validate')}
|
||||
</Button>
|
||||
</div>
|
||||
{result === 'valid' && (
|
||||
<div className="flex items-center gap-1 text-sm text-green-600 dark:text-green-400">
|
||||
<Check className="h-4 w-4" />
|
||||
<span>{t('apiKey.valid')}</span>
|
||||
</div>
|
||||
)}
|
||||
{result === 'invalid' && (
|
||||
<div className="flex items-center gap-1 text-sm text-red-600 dark:text-red-400">
|
||||
<X className="h-4 w-4" />
|
||||
<span>{errorMsg}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface DashboardCardProps {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
onClick: () => void;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
function DashboardCard({ icon, title, description, onClick, testId }: DashboardCardProps) {
|
||||
return (
|
||||
<button
|
||||
data-testid={testId}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'group flex flex-col items-center gap-3 rounded-2xl border border-border/60 p-6',
|
||||
'bg-card text-left transition-all',
|
||||
'hover:border-primary/30 hover:bg-accent/30 hover:shadow-md',
|
||||
'active:scale-[0.98]',
|
||||
)}
|
||||
>
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-primary/10 text-primary transition-colors group-hover:bg-primary/20 group-hover:scale-110">
|
||||
{icon}
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-base font-semibold text-foreground">{title}</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('dashboard');
|
||||
const language = useSettingsStore((s) => s.language);
|
||||
const setLanguage = useSettingsStore((s) => s.setLanguage);
|
||||
|
||||
// Filter to only zh and en for 89猫
|
||||
const visibleLanguages = SUPPORTED_LANGUAGES.filter(
|
||||
(lang) => lang.code === 'zh' || lang.code === 'en',
|
||||
);
|
||||
|
||||
const cards: DashboardCardProps[] = [
|
||||
{
|
||||
icon: <Building2 className="h-7 w-7" strokeWidth={1.5} />,
|
||||
title: t('cards.management.title'),
|
||||
description: t('cards.management.desc'),
|
||||
onClick: () => navigate('/management'),
|
||||
testId: 'dashboard-card-management',
|
||||
},
|
||||
{
|
||||
icon: <Coins className="h-7 w-7" strokeWidth={1.5} />,
|
||||
title: t('cards.tokenRecharge.title'),
|
||||
description: t('cards.tokenRecharge.desc'),
|
||||
onClick: () => window.electron.openExternal('https://89.is89.com'),
|
||||
testId: 'dashboard-card-token',
|
||||
},
|
||||
{
|
||||
icon: <Puzzle className="h-7 w-7" strokeWidth={1.5} />,
|
||||
title: t('cards.plugins.title'),
|
||||
description: t('cards.plugins.desc'),
|
||||
onClick: () => navigate('/skills'),
|
||||
testId: 'dashboard-card-plugins',
|
||||
},
|
||||
{
|
||||
icon: <ShieldCheck className="h-7 w-7" strokeWidth={1.5} />,
|
||||
title: t('cards.security.title'),
|
||||
description: t('cards.security.desc'),
|
||||
onClick: () => navigate('/security'),
|
||||
testId: 'dashboard-card-security',
|
||||
},
|
||||
{
|
||||
icon: <HelpCircle className="h-7 w-7" strokeWidth={1.5} />,
|
||||
title: t('cards.faq.title'),
|
||||
description: t('cards.faq.desc'),
|
||||
onClick: () => navigate('/faq'),
|
||||
testId: 'dashboard-card-faq',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl space-y-8 py-4">
|
||||
{/* Header */}
|
||||
<div className="text-center">
|
||||
<h1 className="text-3xl font-serif font-normal tracking-tight text-foreground">
|
||||
{t('title')}
|
||||
</h1>
|
||||
<p className="mt-2 text-muted-foreground">{t('subtitle')}</p>
|
||||
</div>
|
||||
|
||||
{/* Feature Cards Grid */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{cards.map((card) => (
|
||||
<DashboardCard key={card.testId} {...card} />
|
||||
))}
|
||||
|
||||
{/* API Key Card — inline with input */}
|
||||
<div className="flex flex-col gap-3 rounded-2xl border border-border/60 bg-card p-6 sm:col-span-2 lg:col-span-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
||||
<KeyRound className="h-5 w-5" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-foreground">
|
||||
{t('cards.apiKey.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('cards.apiKey.desc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ApiKeyCard />
|
||||
</div>
|
||||
|
||||
{/* Language Selector Card */}
|
||||
<div className="flex flex-col items-center gap-3 rounded-2xl border border-border/60 bg-card p-6 sm:col-span-2 lg:col-span-3">
|
||||
<div className="flex items-center gap-3 self-start">
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
||||
<Globe className="h-5 w-5" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<h3 className="text-base font-semibold text-foreground">
|
||||
{t('cards.language.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('cards.language.desc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap gap-2">
|
||||
{visibleLanguages.map((lang) => (
|
||||
<Button
|
||||
key={lang.code}
|
||||
variant={language === lang.code ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setLanguage(lang.code)}
|
||||
>
|
||||
{lang.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
187
src/pages/Faq/index.tsx
Normal file
187
src/pages/Faq/index.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* FAQ page — accordion-style Q&A grouped by category
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChevronDown, HelpCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface FaqItem {
|
||||
q: string;
|
||||
a: string;
|
||||
}
|
||||
|
||||
interface FaqSection {
|
||||
title: string;
|
||||
items: FaqItem[];
|
||||
}
|
||||
|
||||
function FaqAccordion({ item, defaultOpen }: { item: FaqItem; defaultOpen: boolean }) {
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-border/60 bg-card overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex w-full items-center justify-between gap-3 px-5 py-4 text-left transition-colors hover:bg-accent/30"
|
||||
>
|
||||
<span className="text-sm font-medium text-foreground">{item.q}</span>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-4 w-4 shrink-0 text-muted-foreground transition-transform',
|
||||
open && 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="px-5 pb-4">
|
||||
<p className="text-sm text-muted-foreground leading-relaxed whitespace-pre-wrap">
|
||||
{item.a}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqSectionBlock({ section, defaultOpen }: { section: FaqSection; defaultOpen: boolean }) {
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex w-full items-center gap-2 text-left"
|
||||
>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-4 w-4 text-muted-foreground transition-transform',
|
||||
open && 'rotate-180',
|
||||
)}
|
||||
/>
|
||||
<h2 className="text-lg font-semibold text-foreground">{section.title}</h2>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="space-y-2">
|
||||
{section.items.map((item, idx) => (
|
||||
<FaqAccordion key={idx} item={item} defaultOpen={idx === 0 && defaultOpen} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Faq() {
|
||||
const { t } = useTranslation('dashboard');
|
||||
|
||||
const sections: FaqSection[] = [
|
||||
{
|
||||
title: t('faq.sections.install', '安装相关'),
|
||||
items: [
|
||||
{
|
||||
q: t('faq.q.howToInstall', '如何安装89猫?'),
|
||||
a: t(
|
||||
'faq.a.howToInstall',
|
||||
'从官方网站下载对应平台的安装包,双击运行即可自动完成安装。安装完成后桌面和开始菜单都会出现89猫图标。',
|
||||
),
|
||||
},
|
||||
{
|
||||
q: t('faq.q.systemRequirements', '89猫的系统要求是什么?'),
|
||||
a: t(
|
||||
'faq.a.systemRequirements',
|
||||
'支持 Windows 10+、macOS 11+、Linux (Ubuntu 20.04+)。最低需要4GB内存,推荐8GB。需要1GB可用磁盘空间。',
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('faq.sections.usage', '使用相关'),
|
||||
items: [
|
||||
{
|
||||
q: t('faq.q.howToUse', '如何开始使用89猫?'),
|
||||
a: t(
|
||||
'faq.a.howToUse',
|
||||
'在首页的"工作钥匙"卡片中输入你的API Key并点击验证,验证通过后即可在聊天页面与AI对话。',
|
||||
),
|
||||
},
|
||||
{
|
||||
q: t('faq.q.stopRestart', '如何停止或重启89猫?'),
|
||||
a: t(
|
||||
'faq.a.stopRestart',
|
||||
'进入"管理中心"页面,点击"停止使用89猫"按钮即可停止服务,再次点击"打开使用89猫"即可重新启动。',
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('faq.sections.key', 'Key相关'),
|
||||
items: [
|
||||
{
|
||||
q: t('faq.q.whereToGetKey', '从哪里获取API Key?'),
|
||||
a: t('faq.a.whereToGetKey', '请访问 89.is89.com 注册账号并获取API Key。'),
|
||||
},
|
||||
{
|
||||
q: t('faq.q.keyNotWorking', 'Key显示无效怎么办?'),
|
||||
a: t(
|
||||
'faq.a.keyNotWorking',
|
||||
'请检查Key是否输入正确(注意大小写和空格),确认Key未过期,确认网络连接正常。如仍有问题,请联系客服。',
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('faq.sections.security', '安全相关'),
|
||||
items: [
|
||||
{
|
||||
q: t('faq.q.isMyDataSafe', '我的数据安全吗?'),
|
||||
a: t(
|
||||
'faq.a.isMyDataSafe',
|
||||
'89猫使用系统原生密钥链安全存储你的API Key。管理中心运行在云端,你的个人数据保留在本地,隐私受到充分保护。',
|
||||
),
|
||||
},
|
||||
{
|
||||
q: t('faq.q.howToScan', '如何进行安全检查?'),
|
||||
a: t(
|
||||
'faq.a.howToScan',
|
||||
'进入"安全检查"页面,先点击"升级病毒库",升级完成后点击"开始扫描"即可对系统进行全面安全扫描。',
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('faq.sections.other', '其他'),
|
||||
items: [
|
||||
{
|
||||
q: t('faq.q.contactUs', '如何联系客服?'),
|
||||
a: t(
|
||||
'faq.a.contactUs',
|
||||
'请发送邮件至 public@valuecell.ai 或访问 89.is89.com 获取更多帮助。',
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl space-y-8 py-4">
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<HelpCircle className="h-7 w-7 text-primary" strokeWidth={1.5} />
|
||||
<h1 className="text-3xl font-serif font-normal tracking-tight text-foreground">
|
||||
{t('cards.faq.title')}
|
||||
</h1>
|
||||
</div>
|
||||
<p className="mt-2 text-muted-foreground">{t('cards.faq.desc')}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{sections.map((section, idx) => (
|
||||
<FaqSectionBlock key={idx} section={section} defaultOpen={idx === 0} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
92
src/pages/Management/index.tsx
Normal file
92
src/pages/Management/index.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Management Center page — Gateway control + admin console link
|
||||
*/
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLink, Power, PowerOff, Loader2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { useGatewayStore } from '@/stores/gateway';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export function Management() {
|
||||
const { t } = useTranslation('dashboard');
|
||||
const gatewayStatus = useGatewayStore((s) => s.status);
|
||||
const startGateway = useGatewayStore((s) => s.start);
|
||||
const stopGateway = useGatewayStore((s) => s.stop);
|
||||
|
||||
const isRunning = gatewayStatus.state === 'running';
|
||||
const isTransitioning = gatewayStatus.state === 'starting';
|
||||
|
||||
const handleToggle = async () => {
|
||||
if (isRunning) {
|
||||
await stopGateway();
|
||||
} else {
|
||||
await startGateway();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl space-y-8 py-4">
|
||||
<div className="text-center">
|
||||
<h1 className="text-3xl font-serif font-normal tracking-tight text-foreground">
|
||||
{t('cards.management.title')}
|
||||
</h1>
|
||||
<p className="mt-2 text-muted-foreground">{t('cards.management.desc')}</p>
|
||||
</div>
|
||||
|
||||
{/* Gateway Toggle Card */}
|
||||
<div className="rounded-2xl border border-border/60 bg-card p-8 text-center space-y-4">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'px-3 py-1 text-sm',
|
||||
isRunning && 'border-green-500 text-green-600 dark:text-green-400',
|
||||
!isRunning && 'border-red-500 text-red-600 dark:text-red-400',
|
||||
)}
|
||||
>
|
||||
{isRunning ? t('gatewayRunning', '89猫运行中') : t('gatewayStopped', '89猫已停止')}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button
|
||||
size="lg"
|
||||
variant={isRunning ? 'destructive' : 'default'}
|
||||
disabled={isTransitioning}
|
||||
onClick={() => { void handleToggle(); }}
|
||||
className="min-w-40"
|
||||
>
|
||||
{isTransitioning ? (
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
) : isRunning ? (
|
||||
<PowerOff className="h-5 w-5" />
|
||||
) : (
|
||||
<Power className="h-5 w-5" />
|
||||
)}
|
||||
<span className="ml-2">
|
||||
{isRunning
|
||||
? t('stop89cat', '停止使用89猫')
|
||||
: t('start89cat', '打开使用89猫')}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Admin Console Link */}
|
||||
<div className="rounded-2xl border border-border/60 bg-card p-8 text-center space-y-4">
|
||||
<h2 className="text-xl font-semibold text-foreground">
|
||||
{t('adminConsole', '89超级人工智能脑管理中心')}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('adminConsoleDesc', '管理中心运行在云端,保护您的个人隐私')}
|
||||
</p>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
onClick={() => window.electron.openExternal('https://89.is89.com')}
|
||||
>
|
||||
<ExternalLink className="h-5 w-5" />
|
||||
<span className="ml-2">{t('openAdminConsole', '前往管理中心')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
260
src/pages/Security/index.tsx
Normal file
260
src/pages/Security/index.tsx
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Security Check page — static UI with simulated scan flow
|
||||
*/
|
||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ShieldCheck, Download, Play, Check, Loader2, Clock, FileSearch } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type ScanStep = 'idle' | 'updating' | 'ready' | 'scanning' | 'done';
|
||||
|
||||
const SCAN_PATHS = [
|
||||
'C:\\Windows\\System32\\',
|
||||
'C:\\Program Files\\',
|
||||
'C:\\Users\\Documents\\',
|
||||
'D:\\Downloads\\',
|
||||
'C:\\Temp\\',
|
||||
];
|
||||
|
||||
const getRandomThreats = () => Math.floor(Math.random() * 5);
|
||||
|
||||
export function Security() {
|
||||
const { t } = useTranslation('dashboard');
|
||||
|
||||
const [step, setStep] = useState<ScanStep>('idle');
|
||||
const [updateProgress, setUpdateProgress] = useState(0);
|
||||
const [scanProgress, setScanProgress] = useState(0);
|
||||
const [currentPath, setCurrentPath] = useState('');
|
||||
const [filesScanned, setFilesScanned] = useState(0);
|
||||
const [threatsFound, setThreatsFound] = useState(0);
|
||||
const [scanTime, setScanTime] = useState(0);
|
||||
const [lastScanTime, setLastScanTime] = useState<string>(t('security.neverScanned'));
|
||||
const [virusDbVersion, setVirusDbVersion] = useState('2026.06.01-v1');
|
||||
|
||||
const updateTimerRef = useRef<ReturnType<typeof setInterval>>();
|
||||
const scanTimerRef = useRef<ReturnType<typeof setInterval>>();
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearInterval(updateTimerRef.current);
|
||||
clearInterval(scanTimerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleUpdateDb = useCallback(() => {
|
||||
setStep('updating');
|
||||
setUpdateProgress(0);
|
||||
|
||||
updateTimerRef.current = setInterval(() => {
|
||||
setUpdateProgress((prev) => {
|
||||
if (prev >= 100) {
|
||||
clearInterval(updateTimerRef.current);
|
||||
setStep('ready');
|
||||
setVirusDbVersion(`2026.06.04-v${Math.floor(Math.random() * 10) + 1}`);
|
||||
return 100;
|
||||
}
|
||||
return prev + Math.random() * 15 + 5;
|
||||
});
|
||||
}, 300);
|
||||
}, []);
|
||||
|
||||
const handleStartScan = useCallback(() => {
|
||||
setStep('scanning');
|
||||
setScanProgress(0);
|
||||
setFilesScanned(0);
|
||||
setThreatsFound(0);
|
||||
setScanTime(0);
|
||||
setCurrentPath(SCAN_PATHS[0]);
|
||||
|
||||
const startTime = Date.now();
|
||||
let pathIndex = 0;
|
||||
|
||||
scanTimerRef.current = setInterval(() => {
|
||||
setScanProgress((prev) => {
|
||||
const next = prev + Math.random() * 4 + 1;
|
||||
if (next >= 100) {
|
||||
clearInterval(scanTimerRef.current);
|
||||
setStep('done');
|
||||
setFilesScanned(Math.floor(Math.random() * 5000) + 10000);
|
||||
setThreatsFound(getRandomThreats());
|
||||
setScanTime(Math.floor((Date.now() - startTime) / 1000));
|
||||
setLastScanTime(new Date().toLocaleString());
|
||||
setCurrentPath('');
|
||||
return 100;
|
||||
}
|
||||
|
||||
// Rotate scan paths
|
||||
const newPathIndex = Math.min(
|
||||
Math.floor((next / 100) * SCAN_PATHS.length),
|
||||
SCAN_PATHS.length - 1,
|
||||
);
|
||||
if (newPathIndex !== pathIndex) {
|
||||
pathIndex = newPathIndex;
|
||||
setCurrentPath(SCAN_PATHS[pathIndex]);
|
||||
}
|
||||
|
||||
setFilesScanned((prev2) => prev2 + Math.floor(Math.random() * 50) + 10);
|
||||
return next;
|
||||
});
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
const getStepLabel = () => {
|
||||
switch (step) {
|
||||
case 'idle':
|
||||
return t('security.idle');
|
||||
case 'updating':
|
||||
return t('security.updatingDb');
|
||||
case 'ready':
|
||||
return t('security.readyToScan');
|
||||
case 'scanning':
|
||||
return t('security.scanning');
|
||||
case 'done':
|
||||
return t('security.scanComplete');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl space-y-8 py-4">
|
||||
<div className="text-center">
|
||||
<h1 className="text-3xl font-serif font-normal tracking-tight text-foreground">
|
||||
{t('cards.security.title')}
|
||||
</h1>
|
||||
<p className="mt-2 text-muted-foreground">{t('cards.security.desc')}</p>
|
||||
</div>
|
||||
|
||||
{/* Virus DB Status */}
|
||||
<div className="rounded-2xl border border-border/60 bg-card p-6 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-foreground">{t('security.virusDbVersion')}</h3>
|
||||
<p className="text-sm text-muted-foreground">{virusDbVersion}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span>
|
||||
{t('security.lastScan')}: {lastScanTime}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleUpdateDb}
|
||||
disabled={step === 'updating' || step === 'scanning'}
|
||||
className="w-full"
|
||||
variant="outline"
|
||||
>
|
||||
{step === 'updating' ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Download className="h-4 w-4" />
|
||||
)}
|
||||
<span className="ml-2">{t('security.updateDb')}</span>
|
||||
</Button>
|
||||
|
||||
{step === 'updating' && (
|
||||
<div className="space-y-1">
|
||||
<Progress value={updateProgress} className="h-2" />
|
||||
<p className="text-xs text-muted-foreground text-right">
|
||||
{Math.floor(updateProgress)}%
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Scan Section */}
|
||||
<div className="rounded-2xl border border-border/60 bg-card p-6 space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
||||
<ShieldCheck className="h-5 w-5" strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-foreground">{getStepLabel()}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{step === 'idle' && t('security.updateDbFirst', '请先升级病毒库')}
|
||||
{step === 'ready' && t('security.clickToScan', '病毒库已就绪,可以开始扫描')}
|
||||
{step === 'scanning' && currentPath}
|
||||
{step === 'done' && t('security.scanResult', '扫描已完成')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleStartScan}
|
||||
disabled={step !== 'ready' || step === 'scanning'}
|
||||
className="w-full"
|
||||
variant={step === 'done' ? 'outline' : 'default'}
|
||||
>
|
||||
{step === 'scanning' ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Play className="h-4 w-4" />
|
||||
)}
|
||||
<span className="ml-2">{t('security.startScan')}</span>
|
||||
</Button>
|
||||
|
||||
{/* Scan Progress */}
|
||||
{(step === 'scanning' || step === 'done') && (
|
||||
<div className="space-y-3">
|
||||
<Progress
|
||||
value={scanProgress}
|
||||
className={cn('h-2', step === 'done' && '[&>div]:bg-green-500')}
|
||||
/>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">
|
||||
<FileSearch className="inline h-3.5 w-3.5 mr-1" />
|
||||
{t('security.filesScanned')}: {filesScanned.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{Math.floor(scanProgress)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Result */}
|
||||
{step === 'done' && (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-xl border p-4 text-center',
|
||||
threatsFound > 0
|
||||
? 'border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950'
|
||||
: 'border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Check
|
||||
className={cn(
|
||||
'h-5 w-5',
|
||||
threatsFound > 0
|
||||
? 'text-red-600 dark:text-red-400'
|
||||
: 'text-green-600 dark:text-green-400',
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
'font-semibold',
|
||||
threatsFound > 0
|
||||
? 'text-red-700 dark:text-red-300'
|
||||
: 'text-green-700 dark:text-green-300',
|
||||
)}
|
||||
>
|
||||
{threatsFound > 0
|
||||
? t('security.threatsFound', { count: threatsFound })
|
||||
: t('security.noThreats')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{t('security.scanSummary', {
|
||||
files: filesScanned.toLocaleString(),
|
||||
time: scanTime,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -268,7 +268,7 @@ function WelcomeContent() {
|
||||
return (
|
||||
<div data-testid="setup-welcome-step" className="text-center space-y-4">
|
||||
<div className="mb-4 flex justify-center">
|
||||
<img src={clawxIcon} alt="ClawX" className="h-16 w-16" />
|
||||
<img src={clawxIcon} alt="89猫" className="h-16 w-16" />
|
||||
</div>
|
||||
<h2 className="text-xl font-serif font-normal tracking-tight">{t('welcome.title')}</h2>
|
||||
<p className="text-muted-foreground">
|
||||
|
||||
@@ -343,6 +343,7 @@ export function Skills() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [categoryFilter, setCategoryFilter] = useState<'all' | 'cat89' | 'openclaw' | 'other'>('all');
|
||||
const safeSkills = Array.isArray(skills) ? skills : [];
|
||||
const enabledSkillsCount = safeSkills.filter((skill) => skill.enabled).length;
|
||||
const disabledSkillsCount = safeSkills.filter((skill) => !skill.enabled).length;
|
||||
@@ -356,7 +357,11 @@ export function Skills() {
|
||||
|| (skill.author || '').toLowerCase().includes(q);
|
||||
const matchesStatus = statusFilter === 'all'
|
||||
|| (statusFilter === 'enabled' ? skill.enabled : !skill.enabled);
|
||||
return matchesSearch && matchesStatus;
|
||||
const matchesCategory = categoryFilter === 'all'
|
||||
|| (categoryFilter === 'cat89' && ((skill as any).isCat89Exclusive || skill.source === 'cat89-plugin'))
|
||||
|| (categoryFilter === 'openclaw' && (skill.source || '').startsWith('openclaw-'))
|
||||
|| (categoryFilter === 'other' && !(skill.source || '').startsWith('openclaw-') && !((skill as any).isCat89Exclusive) && skill.source !== 'cat89-plugin');
|
||||
return matchesSearch && matchesStatus && matchesCategory;
|
||||
}).sort((a, b) => {
|
||||
if (a.enabled && !b.enabled) return -1;
|
||||
if (!a.enabled && b.enabled) return 1;
|
||||
@@ -535,6 +540,27 @@ export function Skills() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Category Tabs */}
|
||||
<div className="flex items-center gap-1 mb-4 shrink-0">
|
||||
{(['all', 'cat89', 'openclaw', 'other'] as const).map((cat) => (
|
||||
<Button
|
||||
key={cat}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCategoryFilter(cat)}
|
||||
className={cn(
|
||||
'h-8 rounded-full px-4 text-meta font-medium border shadow-none',
|
||||
categoryFilter === cat
|
||||
? 'bg-black/5 dark:bg-white/10 border-black/10 dark:border-white/10 text-foreground'
|
||||
: 'bg-transparent border-transparent text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5',
|
||||
)}
|
||||
>
|
||||
{t(`filter.category.${cat}`)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Sub Navigation and Actions */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between border-b border-black/10 dark:border-white/10 pb-4 mb-4 shrink-0 gap-4">
|
||||
<div className="flex items-center flex-wrap gap-2 text-sm">
|
||||
|
||||
@@ -51,9 +51,12 @@ export default defineConfig({
|
||||
{
|
||||
// Main process entry file
|
||||
entry: 'electron/main/index.ts',
|
||||
args: ['--remote-debugging-port=9222'],
|
||||
onstart(options) {
|
||||
options.startup();
|
||||
const cdpPort = process.env.CLAWX_CDP_PORT;
|
||||
const args = cdpPort && cdpPort !== '0'
|
||||
? ['.', `--remote-debugging-port=${cdpPort}`]
|
||||
: [];
|
||||
options.startup(args);
|
||||
},
|
||||
vite: {
|
||||
build: {
|
||||
|
||||
Reference in New Issue
Block a user