init
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"test:ui-language": "node scripts/check-chinese-ui.mjs",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
94
frontend/scripts/check-chinese-ui.mjs
Normal file
94
frontend/scripts/check-chinese-ui.mjs
Normal file
@@ -0,0 +1,94 @@
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {resolve} from 'node:path';
|
||||
|
||||
const root = resolve(fileURLToPath(new URL('../..', import.meta.url)));
|
||||
const appSource = readFileSync(resolve(root, 'frontend/src/App.tsx'), 'utf8');
|
||||
const mainSource = readFileSync(resolve(root, 'main.go'), 'utf8');
|
||||
const appOptionsSource = readFileSync(resolve(root, 'app_options.go'), 'utf8');
|
||||
const backendEntrySource = `${mainSource}\n${appOptionsSource}`;
|
||||
|
||||
const forbiddenUiText = [
|
||||
/<h1>Skill Manager<\/h1>/,
|
||||
/>\s*Config\s*</,
|
||||
/>\s*Remote\s*</,
|
||||
/>\s*Local\s*</,
|
||||
/>\s*Check Updates\s*</,
|
||||
/<span>Gitea Base URL<\/span>/,
|
||||
/<span>Organization<\/span>/,
|
||||
/<span>Auth Type<\/span>/,
|
||||
/>\s*Password\s*</,
|
||||
/<span>Username<\/span>/,
|
||||
/<span>Credential Key<\/span>/,
|
||||
/<span>Auto update<\/span>/,
|
||||
/<span>Check on startup<\/span>/,
|
||||
/<span>Interval Minutes<\/span>/,
|
||||
/>\s*Save\s*</,
|
||||
/>\s*Test\s*</,
|
||||
/placeholder="Search repositories"/,
|
||||
/>\s*Refresh\s*</,
|
||||
/<th>Description<\/th>/,
|
||||
/<th>Branch<\/th>/,
|
||||
/<th>Status<\/th>/,
|
||||
/<th>Action<\/th>/,
|
||||
/<th>Actions<\/th>/,
|
||||
/>\s*Update\s*</,
|
||||
/>\s*Download\s*</,
|
||||
/title="Open folder"/,
|
||||
/>No remote skills</,
|
||||
/>No local skills</,
|
||||
/return 'Remote Market';/,
|
||||
/return 'Local Skills';/,
|
||||
/return 'Configuration';/,
|
||||
/Delete local skill/,
|
||||
/title="Delete local"/,
|
||||
/Remote list refreshed/,
|
||||
/Config saved/,
|
||||
/Connected as/,
|
||||
/installed to/,
|
||||
/uninstalled from/,
|
||||
/>not installed</,
|
||||
];
|
||||
|
||||
const requiredChineseText = [
|
||||
'技能管理器',
|
||||
'配置',
|
||||
'远程市场',
|
||||
'本地技能',
|
||||
'检查更新',
|
||||
'保存',
|
||||
'测试连接',
|
||||
'搜索仓库',
|
||||
'刷新',
|
||||
'下载',
|
||||
'更新',
|
||||
'操作',
|
||||
'暂无远程技能',
|
||||
'暂无本地技能',
|
||||
'已下载',
|
||||
'已安装',
|
||||
'未安装',
|
||||
];
|
||||
|
||||
const failures = [];
|
||||
|
||||
for (const pattern of forbiddenUiText) {
|
||||
if (pattern.test(appSource)) {
|
||||
failures.push(`App.tsx still matches English UI pattern: ${pattern}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const text of requiredChineseText) {
|
||||
if (!appSource.includes(text)) {
|
||||
failures.push(`App.tsx is missing Chinese UI text: ${text}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!backendEntrySource.includes('SGG AI 技能管理器')) {
|
||||
failures.push('window title is not Chinese');
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error(failures.join('\n'));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -131,6 +131,13 @@ button.danger:hover:not(:disabled) {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin-bottom: 12px;
|
||||
padding: 10px 12px;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Trash2,
|
||||
Unlink,
|
||||
} from 'lucide-react';
|
||||
import {BrowserOpenURL} from '../wailsjs/runtime/runtime';
|
||||
import './App.css';
|
||||
import {api} from './api';
|
||||
import {Config, RemoteSkill, SkillState, defaultConfig, saveConfigRequest} from './types';
|
||||
@@ -18,6 +19,29 @@ import {domain} from '../wailsjs/go/models';
|
||||
|
||||
type Tab = 'config' | 'remote' | 'local';
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
remote: '远程',
|
||||
downloaded: '已下载',
|
||||
check_failed: '检查失败',
|
||||
installed: '已安装',
|
||||
not_installed: '未安装',
|
||||
conflict: '冲突',
|
||||
unknown: '未知',
|
||||
local: '就绪',
|
||||
};
|
||||
|
||||
const localizedMessages: Record<string, string> = {
|
||||
connected: '已连接',
|
||||
'gitea baseURL is required': '请填写 Gitea 服务地址',
|
||||
'gitea org is required': '请填写组织',
|
||||
'gitea username is required': '请填写用户名',
|
||||
'authType must be password or token': '认证方式必须是密码或 Token',
|
||||
'update interval must be positive': '检查间隔必须大于 0',
|
||||
'local changes present': '存在本地改动',
|
||||
};
|
||||
|
||||
const projectRepositoryURL = 'http://10.1.0.1:3000/sgg-sgg-tools/sgg-sgg-ai-skill-manage-windows';
|
||||
|
||||
function App() {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('config');
|
||||
const [config, setConfig] = useState<Config>(defaultConfig());
|
||||
@@ -57,7 +81,7 @@ function App() {
|
||||
async function refreshRemote() {
|
||||
await run('remote:refresh', async () => {
|
||||
setRemoteSkills(await api.listRemoteSkills());
|
||||
setMessage('Remote list refreshed');
|
||||
setMessage('远程列表已刷新');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,14 +94,14 @@ function App() {
|
||||
await api.saveConfig(saveConfigRequest(config, password, token));
|
||||
setPassword('');
|
||||
setToken('');
|
||||
setMessage('Config saved');
|
||||
setMessage('配置已保存');
|
||||
});
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
await run('config:test', async () => {
|
||||
const result = await api.testConnection(saveConfigRequest(config, password, token));
|
||||
setMessage(result.ok ? `Connected as ${result.username}` : result.message);
|
||||
setMessage(result.ok ? (result.username ? `已连接:${result.username}` : '已连接') : localizeMessage(result.message));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,10 +109,10 @@ function App() {
|
||||
await run(`remote:${skill.name}`, async () => {
|
||||
if (skill.isDownloaded) {
|
||||
await api.updateSkill(config.gitea.org, skill.name);
|
||||
setMessage(`${skill.name} updated`);
|
||||
setMessage(`${skill.name} 已更新`);
|
||||
} else {
|
||||
await api.downloadSkill(skill);
|
||||
setMessage(`${skill.name} downloaded`);
|
||||
setMessage(`${skill.name} 已下载`);
|
||||
}
|
||||
await refreshLocal();
|
||||
await refreshRemote();
|
||||
@@ -99,7 +123,7 @@ function App() {
|
||||
await run(`local:update:${skill.repo}`, async () => {
|
||||
await api.updateSkill(skill.org, skill.repo);
|
||||
await refreshLocal();
|
||||
setMessage(`${skill.repo} updated`);
|
||||
setMessage(`${skill.repo} 已更新`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,7 +131,7 @@ function App() {
|
||||
await run(`install:${targetID}:${skill.repo}`, async () => {
|
||||
await api.installSkill(skill.org, skill.repo, targetID);
|
||||
await refreshLocal();
|
||||
setMessage(`${skill.repo} installed to ${targetLabel(targetID)}`);
|
||||
setMessage(`${skill.repo} 已安装到 ${targetLabel(targetID)}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,18 +139,18 @@ function App() {
|
||||
await run(`uninstall:${targetID}:${skill.repo}`, async () => {
|
||||
await api.uninstallSkill(skill.org, skill.repo, targetID);
|
||||
await refreshLocal();
|
||||
setMessage(`${skill.repo} uninstalled from ${targetLabel(targetID)}`);
|
||||
setMessage(`${skill.repo} 已从 ${targetLabel(targetID)} 卸载`);
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteSkill(skill: SkillState) {
|
||||
if (!window.confirm(`Delete local skill ${skill.repo}?`)) {
|
||||
if (!window.confirm(`确认删除本地技能 ${skill.repo}?`)) {
|
||||
return;
|
||||
}
|
||||
await run(`delete:${skill.repo}`, async () => {
|
||||
await api.deleteSkill(skill.org, skill.repo);
|
||||
await refreshLocal();
|
||||
setMessage(`${skill.repo} deleted`);
|
||||
setMessage(`${skill.repo} 已删除`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -155,7 +179,7 @@ function App() {
|
||||
try {
|
||||
await action();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
setError(localizeMessage(err instanceof Error ? err.message : String(err)));
|
||||
} finally {
|
||||
setBusy('');
|
||||
}
|
||||
@@ -173,19 +197,19 @@ function App() {
|
||||
<div className="brand">
|
||||
<div className="brand-mark">S</div>
|
||||
<div>
|
||||
<h1>Skill Manager</h1>
|
||||
<h1>技能管理器</h1>
|
||||
<span>Gitea / Codex / Claude</span>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="tabs" aria-label="Primary">
|
||||
<nav className="tabs" aria-label="主导航">
|
||||
<button className={activeTab === 'config' ? 'active' : ''} onClick={() => setActiveTab('config')}>
|
||||
<Settings size={17} /> Config
|
||||
<Settings size={17} /> 配置
|
||||
</button>
|
||||
<button className={activeTab === 'remote' ? 'active' : ''} onClick={() => setActiveTab('remote')}>
|
||||
<Download size={17} /> Remote
|
||||
<Download size={17} /> 远程
|
||||
</button>
|
||||
<button className={activeTab === 'local' ? 'active' : ''} onClick={() => setActiveTab('local')}>
|
||||
<FolderOpen size={17} /> Local
|
||||
<FolderOpen size={17} /> 本地
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
@@ -196,9 +220,14 @@ function App() {
|
||||
<h2>{pageTitle(activeTab)}</h2>
|
||||
<span>{statusSummary(remoteSkills, localSkills)}</span>
|
||||
</div>
|
||||
<button className="ghost" onClick={() => run('auto:update', async () => { await api.runAutoUpdate(); await refreshLocal(); })} disabled={busy !== ''}>
|
||||
<RefreshCw size={16} /> Check Updates
|
||||
</button>
|
||||
<div className="topbar-actions">
|
||||
<button className="ghost" onClick={() => run('auto:update', async () => { await api.runAutoUpdate(); await refreshLocal(); })} disabled={busy !== ''}>
|
||||
<RefreshCw size={16} /> 检查更新
|
||||
</button>
|
||||
<button className="ghost icon-only" onClick={() => BrowserOpenURL(projectRepositoryURL)} title="打开项目仓库" aria-label="打开项目仓库">
|
||||
<GithubIcon size={17} />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{(message || error) && (
|
||||
@@ -211,7 +240,7 @@ function App() {
|
||||
<section className="panel">
|
||||
<div className="form-grid">
|
||||
<label>
|
||||
<span>Gitea Base URL</span>
|
||||
<span>Gitea 服务地址</span>
|
||||
<input
|
||||
value={config.gitea.baseURL || ''}
|
||||
placeholder="https://gitea.example.com"
|
||||
@@ -219,20 +248,20 @@ function App() {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>Organization</span>
|
||||
<span>组织</span>
|
||||
<input
|
||||
value={config.gitea.org || ''}
|
||||
onChange={(event) => updateConfig((next) => { next.gitea.org = event.target.value; })}
|
||||
/>
|
||||
</label>
|
||||
<div className="field">
|
||||
<span>Auth Type</span>
|
||||
<span>认证方式</span>
|
||||
<div className="segmented">
|
||||
<button
|
||||
className={config.gitea.authType !== 'token' ? 'active' : ''}
|
||||
onClick={() => updateConfig((next) => { next.gitea.authType = 'password'; })}
|
||||
>
|
||||
Password
|
||||
密码
|
||||
</button>
|
||||
<button
|
||||
className={config.gitea.authType === 'token' ? 'active' : ''}
|
||||
@@ -243,7 +272,7 @@ function App() {
|
||||
</div>
|
||||
</div>
|
||||
<label>
|
||||
<span>Username</span>
|
||||
<span>用户名</span>
|
||||
<input
|
||||
value={config.gitea.username || ''}
|
||||
onChange={(event) => updateConfig((next) => { next.gitea.username = event.target.value; })}
|
||||
@@ -256,24 +285,17 @@ function App() {
|
||||
</label>
|
||||
) : (
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<span>密码</span>
|
||||
<input type="password" value={password} onChange={(event) => setPassword(event.target.value)} />
|
||||
</label>
|
||||
)}
|
||||
<label>
|
||||
<span>Credential Key</span>
|
||||
<input
|
||||
value={config.gitea.credentialKey || ''}
|
||||
onChange={(event) => updateConfig((next) => { next.gitea.credentialKey = event.target.value; })}
|
||||
/>
|
||||
</label>
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(config.update.autoUpdate)}
|
||||
onChange={(event) => updateConfig((next) => { next.update.autoUpdate = event.target.checked; })}
|
||||
/>
|
||||
<span>Auto update</span>
|
||||
<span>自动更新</span>
|
||||
</label>
|
||||
<label className="checkbox">
|
||||
<input
|
||||
@@ -281,10 +303,10 @@ function App() {
|
||||
checked={Boolean(config.update.checkOnStartup)}
|
||||
onChange={(event) => updateConfig((next) => { next.update.checkOnStartup = event.target.checked; })}
|
||||
/>
|
||||
<span>Check on startup</span>
|
||||
<span>启动时检查</span>
|
||||
</label>
|
||||
<label>
|
||||
<span>Interval Minutes</span>
|
||||
<span>检查间隔(分钟)</span>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -295,10 +317,10 @@ function App() {
|
||||
</div>
|
||||
<div className="toolbar">
|
||||
<button onClick={saveConfig} disabled={busy !== ''}>
|
||||
<Save size={16} /> Save
|
||||
<Save size={16} /> 保存
|
||||
</button>
|
||||
<button className="secondary" onClick={testConnection} disabled={busy !== ''}>
|
||||
<PlugZap size={16} /> Test
|
||||
<PlugZap size={16} /> 测试连接
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
@@ -310,22 +332,22 @@ function App() {
|
||||
<input
|
||||
className="search"
|
||||
value={search}
|
||||
placeholder="Search repositories"
|
||||
placeholder="搜索仓库"
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
<button onClick={refreshRemote} disabled={busy !== ''}>
|
||||
<RefreshCw size={16} /> Refresh
|
||||
<RefreshCw size={16} /> 刷新
|
||||
</button>
|
||||
</div>
|
||||
<div className="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Branch</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
<th>名称</th>
|
||||
<th>描述</th>
|
||||
<th>分支</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -337,12 +359,12 @@ function App() {
|
||||
<td>{badge(skill.status || (skill.isDownloaded ? 'downloaded' : 'remote'))}</td>
|
||||
<td>
|
||||
<div className="row-actions">
|
||||
<button onClick={() => downloadOrUpdate(skill)} disabled={busy !== ''} title={skill.isDownloaded ? 'Update' : 'Download'}>
|
||||
<button onClick={() => downloadOrUpdate(skill)} disabled={busy !== ''} title={skill.isDownloaded ? '更新' : '下载'}>
|
||||
{skill.isDownloaded ? <RefreshCw size={15} /> : <Download size={15} />}
|
||||
{skill.isDownloaded ? 'Update' : 'Download'}
|
||||
{skill.isDownloaded ? '更新' : '下载'}
|
||||
</button>
|
||||
{skill.isDownloaded && (
|
||||
<button className="icon-only" onClick={() => openRemoteFolder(skill)} title="Open folder">
|
||||
<button className="icon-only" onClick={() => openRemoteFolder(skill)} title="打开目录">
|
||||
<FolderOpen size={15} />
|
||||
</button>
|
||||
)}
|
||||
@@ -352,7 +374,7 @@ function App() {
|
||||
))}
|
||||
{filteredRemote.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={5} className="empty">No remote skills</td>
|
||||
<td colSpan={5} className="empty">暂无远程技能</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
@@ -365,20 +387,20 @@ function App() {
|
||||
<section className="panel">
|
||||
<div className="toolbar">
|
||||
<button onClick={refreshLocal} disabled={busy !== ''}>
|
||||
<RefreshCw size={16} /> Refresh
|
||||
<RefreshCw size={16} /> 刷新
|
||||
</button>
|
||||
</div>
|
||||
<div className="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Path</th>
|
||||
<th>Commit</th>
|
||||
<th>Status</th>
|
||||
<th>名称</th>
|
||||
<th>路径</th>
|
||||
<th>提交</th>
|
||||
<th>状态</th>
|
||||
<th>Codex</th>
|
||||
<th>Claude</th>
|
||||
<th>Actions</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -387,39 +409,39 @@ function App() {
|
||||
<td className="name-cell">{skill.repo}</td>
|
||||
<td className="path-cell" title={skill.localPath}>{skill.localPath}</td>
|
||||
<td>{shortCommit(skill.currentCommit)}</td>
|
||||
<td>{skill.lastError ? <span className="badge error">{skill.lastError}</span> : <span className="badge local">ready</span>}</td>
|
||||
<td>{skill.lastError ? <span className="badge error">{localizeMessage(skill.lastError)}</span> : <span className="badge local">就绪</span>}</td>
|
||||
<td>{targetBadge(skill, 'codex')}</td>
|
||||
<td>{targetBadge(skill, 'claude')}</td>
|
||||
<td>
|
||||
<div className="row-actions">
|
||||
<button onClick={() => updateLocal(skill)} disabled={busy !== ''} title="Update">
|
||||
<RefreshCw size={15} /> Update
|
||||
<button onClick={() => updateLocal(skill)} disabled={busy !== ''} title="更新">
|
||||
<RefreshCw size={15} /> 更新
|
||||
</button>
|
||||
{targetInstalled(skill, 'codex') ? (
|
||||
<button className="secondary" onClick={() => uninstall(skill, 'codex')} disabled={busy !== ''} title="Uninstall Codex">
|
||||
<button className="secondary" onClick={() => uninstall(skill, 'codex')} disabled={busy !== ''} title="卸载 Codex">
|
||||
<Unlink size={15} /> Codex
|
||||
</button>
|
||||
) : (
|
||||
<button className="secondary" onClick={() => install(skill, 'codex')} disabled={busy !== ''} title="Install Codex">
|
||||
<button className="secondary" onClick={() => install(skill, 'codex')} disabled={busy !== ''} title="安装 Codex">
|
||||
<Link2 size={15} /> Codex
|
||||
</button>
|
||||
)}
|
||||
{targetInstalled(skill, 'claude') ? (
|
||||
<button className="secondary" onClick={() => uninstall(skill, 'claude')} disabled={busy !== ''} title="Uninstall Claude">
|
||||
<button className="secondary" onClick={() => uninstall(skill, 'claude')} disabled={busy !== ''} title="卸载 Claude">
|
||||
<Unlink size={15} /> Claude
|
||||
</button>
|
||||
) : (
|
||||
<button className="secondary" onClick={() => install(skill, 'claude')} disabled={busy !== ''} title="Install Claude">
|
||||
<button className="secondary" onClick={() => install(skill, 'claude')} disabled={busy !== ''} title="安装 Claude">
|
||||
<Link2 size={15} /> Claude
|
||||
</button>
|
||||
)}
|
||||
<button className="icon-only" onClick={() => openCode(skill)} disabled={busy !== ''} title="Open VS Code">
|
||||
<button className="icon-only" onClick={() => openCode(skill)} disabled={busy !== ''} title="打开 VS Code">
|
||||
<Code2 size={15} />
|
||||
</button>
|
||||
<button className="icon-only" onClick={() => openFolder(skill)} disabled={busy !== ''} title="Open folder">
|
||||
<button className="icon-only" onClick={() => openFolder(skill)} disabled={busy !== ''} title="打开目录">
|
||||
<FolderOpen size={15} />
|
||||
</button>
|
||||
<button className="danger icon-only" onClick={() => deleteSkill(skill)} disabled={busy !== ''} title="Delete local">
|
||||
<button className="danger icon-only" onClick={() => deleteSkill(skill)} disabled={busy !== ''} title="删除本地技能">
|
||||
<Trash2 size={15} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -428,7 +450,7 @@ function App() {
|
||||
))}
|
||||
{localSkills.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={7} className="empty">No local skills</td>
|
||||
<td colSpan={7} className="empty">暂无本地技能</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
@@ -441,27 +463,35 @@ function App() {
|
||||
);
|
||||
}
|
||||
|
||||
function GithubIcon({size}: {size: number}) {
|
||||
return (
|
||||
<svg aria-hidden="true" focusable="false" width={size} height={size} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 .5C5.65.5.5 5.65.5 12c0 5.09 3.29 9.4 7.86 10.93.58.1.79-.25.79-.56v-2.15c-3.2.7-3.87-1.37-3.87-1.37-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.03 1.75 2.69 1.24 3.35.95.1-.74.4-1.24.73-1.53-2.55-.29-5.23-1.28-5.23-5.68 0-1.25.45-2.28 1.18-3.08-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.16 1.17.92-.25 1.9-.38 2.88-.38.98 0 1.96.13 2.88.38 2.2-1.48 3.16-1.17 3.16-1.17.62 1.58.23 2.75.11 3.04.73.8 1.18 1.83 1.18 3.08 0 4.41-2.69 5.38-5.25 5.67.41.36.78 1.06.78 2.13v3.18c0 .31.21.67.8.56A11.51 11.51 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function pageTitle(tab: Tab) {
|
||||
if (tab === 'remote') {
|
||||
return 'Remote Market';
|
||||
return '远程市场';
|
||||
}
|
||||
if (tab === 'local') {
|
||||
return 'Local Skills';
|
||||
return '本地技能';
|
||||
}
|
||||
return 'Configuration';
|
||||
return '配置';
|
||||
}
|
||||
|
||||
function statusSummary(remote: RemoteSkill[], local: SkillState[]) {
|
||||
return `${remote.length} remote / ${local.length} local`;
|
||||
return `${remote.length} 个远程 / ${local.length} 个本地`;
|
||||
}
|
||||
|
||||
function badge(status: string) {
|
||||
const label = status.replace(/_/g, ' ') || 'remote';
|
||||
const label = statusLabels[status] || status.replace(/_/g, ' ') || statusLabels.remote;
|
||||
return <span className={`badge ${status}`}>{label}</span>;
|
||||
}
|
||||
|
||||
function targetBadge(skill: SkillState, targetID: 'codex' | 'claude') {
|
||||
return targetInstalled(skill, targetID) ? <span className="badge installed">installed</span> : <span className="badge">not installed</span>;
|
||||
return targetInstalled(skill, targetID) ? <span className="badge installed">已安装</span> : <span className="badge">未安装</span>;
|
||||
}
|
||||
|
||||
function targetInstalled(skill: SkillState, targetID: 'codex' | 'claude') {
|
||||
@@ -476,4 +506,9 @@ function shortCommit(commit: string) {
|
||||
return commit ? commit.slice(0, 8) : '-';
|
||||
}
|
||||
|
||||
function localizeMessage(message: string) {
|
||||
const normalized = message.trim();
|
||||
return localizedMessages[normalized] || normalized;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user