feat(i18n): Implement internationalization for Channels, Skills, Setup, and Models pages
This commit is contained in:
@@ -167,7 +167,7 @@ export function Sidebar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ to: '/models', icon: <Cpu className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.models', 'Models') },
|
{ to: '/models', icon: <Cpu className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.models') },
|
||||||
{ to: '/channels', icon: <Network className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.channels') },
|
{ to: '/channels', icon: <Network className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.channels') },
|
||||||
{ to: '/skills', icon: <Puzzle className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.skills') },
|
{ to: '/skills', icon: <Puzzle className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.skills') },
|
||||||
{ to: '/cron', icon: <Clock className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.cronTasks') },
|
{ to: '/cron', icon: <Clock className="h-[18px] w-[18px]" strokeWidth={2} />, label: t('sidebar.cronTasks') },
|
||||||
@@ -318,7 +318,7 @@ export function Sidebar() {
|
|||||||
</div>
|
</div>
|
||||||
{!sidebarCollapsed && (
|
{!sidebarCollapsed && (
|
||||||
<>
|
<>
|
||||||
<span className="flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap">OpenClaw Page</span>
|
<span className="flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap">{t('common:sidebar.openClawPage')}</span>
|
||||||
<ExternalLink className="h-3 w-3 shrink-0 ml-auto opacity-50 text-muted-foreground" />
|
<ExternalLink className="h-3 w-3 shrink-0 ml-auto opacity-50 text-muted-foreground" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -327,10 +327,10 @@ export function Sidebar() {
|
|||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={!!sessionToDelete}
|
open={!!sessionToDelete}
|
||||||
title={t('common.confirm', 'Confirm')}
|
title={t('common:actions.confirm')}
|
||||||
message={sessionToDelete ? t('sidebar.deleteSessionConfirm', `Delete "${sessionToDelete.label}"?`) : ''}
|
message={t('common:sidebar.deleteSessionConfirm', { label: sessionToDelete?.label })}
|
||||||
confirmLabel={t('common.delete', 'Delete')}
|
confirmLabel={t('common:actions.delete')}
|
||||||
cancelLabel={t('common.cancel', 'Cancel')}
|
cancelLabel={t('common:actions.cancel')}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
if (!sessionToDelete) return;
|
if (!sessionToDelete) return;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
"disconnected": "Disconnected"
|
"disconnected": "Disconnected"
|
||||||
},
|
},
|
||||||
"gatewayWarning": "Gateway service is not running. Channels cannot connect.",
|
"gatewayWarning": "Gateway service is not running. Channels cannot connect.",
|
||||||
|
"availableChannels": "Available Channels",
|
||||||
|
"supportedChannels": "Supported Channels",
|
||||||
"available": "Available Channels",
|
"available": "Available Channels",
|
||||||
"availableDesc": "Connect a new channel",
|
"availableDesc": "Connect a new channel",
|
||||||
"configured": "Configured Channels",
|
"configured": "Configured Channels",
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
"channels": "Channels",
|
"channels": "Channels",
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"devConsole": "Developer Console"
|
"devConsole": "Developer Console",
|
||||||
|
"models": "Models",
|
||||||
|
"deleteSessionConfirm": "Are you sure you want to delete session \"{{label}}\"?",
|
||||||
|
"openClawPage": "OpenClaw Page"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -52,4 +55,4 @@
|
|||||||
"notRunningDesc": "The OpenClaw Gateway needs to be running to use this feature. It will start automatically, or you can start it from Settings.",
|
"notRunningDesc": "The OpenClaw Gateway needs to be running to use this feature. It will start automatically, or you can start it from Settings.",
|
||||||
"warning": "Gateway is not running."
|
"warning": "Gateway is not running."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,10 @@
|
|||||||
"enabledOf": "{{enabled}} of {{total}} enabled",
|
"enabledOf": "{{enabled}} of {{total}} enabled",
|
||||||
"sinceRestart": "Since last restart",
|
"sinceRestart": "Since last restart",
|
||||||
"gatewayNotRunning": "Gateway not running",
|
"gatewayNotRunning": "Gateway not running",
|
||||||
|
"models": {
|
||||||
|
"title": "Models",
|
||||||
|
"subtitle": "Manage your AI providers and monitor token usage."
|
||||||
|
},
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"title": "Quick Actions",
|
"title": "Quick Actions",
|
||||||
"description": "Common tasks and shortcuts",
|
"description": "Common tasks and shortcuts",
|
||||||
@@ -53,4 +57,4 @@
|
|||||||
"cacheWrite": "Cache write {{value}}",
|
"cacheWrite": "Cache write {{value}}",
|
||||||
"cost": "Cost ${{amount}}"
|
"cost": "Cost ${{amount}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,5 +115,27 @@
|
|||||||
"skipStep": "Skip this step",
|
"skipStep": "Skip this step",
|
||||||
"skipSetup": "Skip Setup",
|
"skipSetup": "Skip Setup",
|
||||||
"getStarted": "Get Started"
|
"getStarted": "Get Started"
|
||||||
|
},
|
||||||
|
"defaultSkills": {
|
||||||
|
"opencode": {
|
||||||
|
"name": "OpenCode",
|
||||||
|
"description": "AI coding assistant backend"
|
||||||
|
},
|
||||||
|
"python-env": {
|
||||||
|
"name": "Python Environment",
|
||||||
|
"description": "Python runtime for skills"
|
||||||
|
},
|
||||||
|
"code-assist": {
|
||||||
|
"name": "Code Assist",
|
||||||
|
"description": "Code analysis and suggestions"
|
||||||
|
},
|
||||||
|
"file-tools": {
|
||||||
|
"name": "File Tools",
|
||||||
|
"description": "File operations and management"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"name": "Terminal",
|
||||||
|
"description": "Shell command execution"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,10 @@
|
|||||||
"saveConfig": "Save Configuration",
|
"saveConfig": "Save Configuration",
|
||||||
"configSaved": "Configuration saved",
|
"configSaved": "Configuration saved",
|
||||||
"openManual": "Open Manual",
|
"openManual": "Open Manual",
|
||||||
"configurable": "Configurable"
|
"configurable": "Configurable",
|
||||||
|
"uninstall": "Uninstall",
|
||||||
|
"enable": "Enable",
|
||||||
|
"disable": "Disable"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"enabled": "Skill enabled",
|
"enabled": "Skill enabled",
|
||||||
@@ -83,4 +86,4 @@
|
|||||||
"emptyPrompt": "Search for new skills to expand your capabilities.",
|
"emptyPrompt": "Search for new skills to expand your capabilities.",
|
||||||
"searchError": "ClawHub search failed. Check your connection or installation."
|
"searchError": "ClawHub search failed. Check your connection or installation."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
"disconnected": "未接続"
|
"disconnected": "未接続"
|
||||||
},
|
},
|
||||||
"gatewayWarning": "ゲートウェイサービスが実行されていないため、チャンネルに接続できません。",
|
"gatewayWarning": "ゲートウェイサービスが実行されていないため、チャンネルに接続できません。",
|
||||||
|
"availableChannels": "利用可能なチャンネル",
|
||||||
|
"supportedChannels": "サポートされているチャンネル",
|
||||||
"available": "利用可能なチャンネル",
|
"available": "利用可能なチャンネル",
|
||||||
"availableDesc": "新しいチャンネルを接続",
|
"availableDesc": "新しいチャンネルを接続",
|
||||||
"configured": "設定済みチャンネル",
|
"configured": "設定済みチャンネル",
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
"channels": "チャンネル",
|
"channels": "チャンネル",
|
||||||
"dashboard": "ダッシュボード",
|
"dashboard": "ダッシュボード",
|
||||||
"settings": "設定",
|
"settings": "設定",
|
||||||
"devConsole": "開発者コンソール"
|
"devConsole": "開発者コンソール",
|
||||||
|
"models": "モデル",
|
||||||
|
"deleteSessionConfirm": "セッション \"{{label}}\" を削除してもよろしいですか?",
|
||||||
|
"openClawPage": "OpenClaw ページ"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
@@ -52,4 +55,4 @@
|
|||||||
"notRunningDesc": "この機能を使用するには OpenClaw ゲートウェイが実行されている必要があります。自動的に起動するか、設定から起動できます。",
|
"notRunningDesc": "この機能を使用するには OpenClaw ゲートウェイが実行されている必要があります。自動的に起動するか、設定から起動できます。",
|
||||||
"warning": "ゲートウェイが停止中です。"
|
"warning": "ゲートウェイが停止中です。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,10 @@
|
|||||||
"enabledOf": "{{total}} 中 {{enabled}} 有効",
|
"enabledOf": "{{total}} 中 {{enabled}} 有効",
|
||||||
"sinceRestart": "前回の再起動から",
|
"sinceRestart": "前回の再起動から",
|
||||||
"gatewayNotRunning": "ゲートウェイが停止中",
|
"gatewayNotRunning": "ゲートウェイが停止中",
|
||||||
|
"models": {
|
||||||
|
"title": "モデル",
|
||||||
|
"subtitle": "AI プロバイダーを管理し、トークン使用量を監視します。"
|
||||||
|
},
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"title": "クイックアクション",
|
"title": "クイックアクション",
|
||||||
"description": "よく使うタスクとショートカット",
|
"description": "よく使うタスクとショートカット",
|
||||||
@@ -53,4 +57,4 @@
|
|||||||
"cacheWrite": "キャッシュ書込 {{value}}",
|
"cacheWrite": "キャッシュ書込 {{value}}",
|
||||||
"cost": "コスト ${{amount}}"
|
"cost": "コスト ${{amount}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,5 +115,27 @@
|
|||||||
"skipStep": "この手順をスキップ",
|
"skipStep": "この手順をスキップ",
|
||||||
"skipSetup": "セットアップをスキップ",
|
"skipSetup": "セットアップをスキップ",
|
||||||
"getStarted": "始める"
|
"getStarted": "始める"
|
||||||
|
},
|
||||||
|
"defaultSkills": {
|
||||||
|
"opencode": {
|
||||||
|
"name": "OpenCode",
|
||||||
|
"description": "AI コーディングアシスタントのバックエンド"
|
||||||
|
},
|
||||||
|
"python-env": {
|
||||||
|
"name": "Python 環境",
|
||||||
|
"description": "スキルのための Python ランタイム"
|
||||||
|
},
|
||||||
|
"code-assist": {
|
||||||
|
"name": "コードアシスト",
|
||||||
|
"description": "コードの分析と提案"
|
||||||
|
},
|
||||||
|
"file-tools": {
|
||||||
|
"name": "ファイルツール",
|
||||||
|
"description": "ファイルの操作と管理"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"name": "ターミナル",
|
||||||
|
"description": "シェルコマンドの実行"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,10 @@
|
|||||||
"saveConfig": "設定を保存",
|
"saveConfig": "設定を保存",
|
||||||
"configSaved": "設定を保存しました",
|
"configSaved": "設定を保存しました",
|
||||||
"openManual": "マニュアルを開く",
|
"openManual": "マニュアルを開く",
|
||||||
"configurable": "設定可能"
|
"configurable": "設定可能",
|
||||||
|
"uninstall": "アンインストール",
|
||||||
|
"enable": "有効化",
|
||||||
|
"disable": "無効化"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"enabled": "スキルを有効にしました",
|
"enabled": "スキルを有効にしました",
|
||||||
@@ -83,4 +86,4 @@
|
|||||||
"emptyPrompt": "新しいスキルを検索して機能を拡張しましょう。",
|
"emptyPrompt": "新しいスキルを検索して機能を拡張しましょう。",
|
||||||
"searchError": "ClawHub検索に失敗しました。接続またはインストールを確認してください。"
|
"searchError": "ClawHub検索に失敗しました。接続またはインストールを確認してください。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "消息频道",
|
"title": "消息频道",
|
||||||
"subtitle": "管理您的消息频道和连接",
|
"subtitle": "连接到消息平台。",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"addChannel": "添加频道",
|
"addChannel": "添加频道",
|
||||||
"stats": {
|
"stats": {
|
||||||
@@ -8,7 +8,10 @@
|
|||||||
"connected": "已连接",
|
"connected": "已连接",
|
||||||
"disconnected": "未连接"
|
"disconnected": "未连接"
|
||||||
},
|
},
|
||||||
"gatewayWarning": "网关服务未运行,频道无法连接。",
|
"gatewayWarning": "网关未运行。未启用网关时无法管理频道。",
|
||||||
|
"availableChannels": "可用频道",
|
||||||
|
"supportedChannels": "支持的频道",
|
||||||
|
"pluginBadge": "插件",
|
||||||
"available": "可用频道",
|
"available": "可用频道",
|
||||||
"availableDesc": "连接一个新的频道",
|
"availableDesc": "连接一个新的频道",
|
||||||
"configured": "已配置频道",
|
"configured": "已配置频道",
|
||||||
@@ -16,7 +19,6 @@
|
|||||||
"configuredBadge": "已配置",
|
"configuredBadge": "已配置",
|
||||||
"deleteConfirm": "确定要删除此频道吗?",
|
"deleteConfirm": "确定要删除此频道吗?",
|
||||||
"showAll": "显示全部",
|
"showAll": "显示全部",
|
||||||
"pluginBadge": "插件",
|
|
||||||
"toast": {
|
"toast": {
|
||||||
"whatsappConnected": "WhatsApp 连接成功",
|
"whatsappConnected": "WhatsApp 连接成功",
|
||||||
"whatsappFailed": "WhatsApp 连接失败: {{error}}",
|
"whatsappFailed": "WhatsApp 连接失败: {{error}}",
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
"channels": "频道",
|
"channels": "频道",
|
||||||
"dashboard": "仪表盘",
|
"dashboard": "仪表盘",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"devConsole": "开发者控制台"
|
"devConsole": "开发者控制台",
|
||||||
|
"models": "模型",
|
||||||
|
"deleteSessionConfirm": "确定要删除对话 \"{{label}}\" 吗?",
|
||||||
|
"openClawPage": "OpenClaw 页面"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
@@ -52,4 +55,4 @@
|
|||||||
"notRunningDesc": "OpenClaw 网关需要运行才能使用此功能。它将自动启动,或者您可以从设置中启动。",
|
"notRunningDesc": "OpenClaw 网关需要运行才能使用此功能。它将自动启动,或者您可以从设置中启动。",
|
||||||
"warning": "网关未运行。"
|
"warning": "网关未运行。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,10 @@
|
|||||||
"enabledOf": "{{enabled}} / {{total}} 已启用",
|
"enabledOf": "{{enabled}} / {{total}} 已启用",
|
||||||
"sinceRestart": "自上次重启",
|
"sinceRestart": "自上次重启",
|
||||||
"gatewayNotRunning": "网关未运行",
|
"gatewayNotRunning": "网关未运行",
|
||||||
|
"models": {
|
||||||
|
"title": "模型",
|
||||||
|
"subtitle": "管理您的 AI 提供商并监控 Token 用量。"
|
||||||
|
},
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"title": "快捷操作",
|
"title": "快捷操作",
|
||||||
"description": "常用任务和快捷方式",
|
"description": "常用任务和快捷方式",
|
||||||
@@ -53,4 +57,4 @@
|
|||||||
"cacheWrite": "缓存写入 {{value}}",
|
"cacheWrite": "缓存写入 {{value}}",
|
||||||
"cost": "费用 ${{amount}}"
|
"cost": "费用 ${{amount}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,5 +115,27 @@
|
|||||||
"skipStep": "跳过此步骤",
|
"skipStep": "跳过此步骤",
|
||||||
"skipSetup": "跳过设置",
|
"skipSetup": "跳过设置",
|
||||||
"getStarted": "开始使用"
|
"getStarted": "开始使用"
|
||||||
|
},
|
||||||
|
"defaultSkills": {
|
||||||
|
"opencode": {
|
||||||
|
"name": "OpenCode",
|
||||||
|
"description": "AI 编程助手后端"
|
||||||
|
},
|
||||||
|
"python-env": {
|
||||||
|
"name": "Python 环境",
|
||||||
|
"description": "技能所需的 Python 运行时"
|
||||||
|
},
|
||||||
|
"code-assist": {
|
||||||
|
"name": "代码辅助",
|
||||||
|
"description": "代码分析与建议"
|
||||||
|
},
|
||||||
|
"file-tools": {
|
||||||
|
"name": "文件工具",
|
||||||
|
"description": "文件操作与管理"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"name": "终端",
|
||||||
|
"description": "Shell 命令执行"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,10 @@
|
|||||||
"saveConfig": "保存配置",
|
"saveConfig": "保存配置",
|
||||||
"configSaved": "配置已保存",
|
"configSaved": "配置已保存",
|
||||||
"openManual": "打开手册",
|
"openManual": "打开手册",
|
||||||
"configurable": "可配置"
|
"configurable": "可配置",
|
||||||
|
"uninstall": "卸载",
|
||||||
|
"enable": "启用",
|
||||||
|
"disable": "禁用"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"enabled": "技能已启用",
|
"enabled": "技能已启用",
|
||||||
@@ -83,4 +86,4 @@
|
|||||||
"emptyPrompt": "搜索新技能以扩展您的能力。",
|
"emptyPrompt": "搜索新技能以扩展您的能力。",
|
||||||
"searchError": "ClawHub 搜索失败。请检查您的连接或安装。"
|
"searchError": "ClawHub 搜索失败。请检查您的连接或安装。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,10 +121,10 @@ export function Channels() {
|
|||||||
<div className="flex flex-col md:flex-row md:items-start justify-between mb-8 shrink-0 gap-4">
|
<div className="flex flex-col md:flex-row md:items-start justify-between mb-8 shrink-0 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-5xl md:text-6xl font-serif text-foreground mb-3 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
<h1 className="text-5xl md:text-6xl font-serif text-foreground mb-3 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
{t('title') || 'Channels'}
|
{t('title')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[17px] text-foreground/80 font-medium">
|
<p className="text-[17px] text-foreground/80 font-medium">
|
||||||
{t('subtitle') || 'Connect to messaging platforms.'}
|
{t('subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ export function Channels() {
|
|||||||
{safeChannels.length > 0 && (
|
{safeChannels.length > 0 && (
|
||||||
<div className="mb-12">
|
<div className="mb-12">
|
||||||
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
Available Channels
|
{t('availableChannels')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{safeChannels.map((channel) => (
|
{safeChannels.map((channel) => (
|
||||||
@@ -187,7 +187,7 @@ export function Channels() {
|
|||||||
{/* Supported Channels (Not yet configured) */}
|
{/* Supported Channels (Not yet configured) */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
<h2 className="text-3xl font-serif text-foreground mb-6 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
Supported Channels
|
{t('supportedChannels')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
||||||
@@ -217,7 +217,7 @@ export function Channels() {
|
|||||||
<h3 className="text-[16px] font-semibold text-foreground truncate">{meta.name}</h3>
|
<h3 className="text-[16px] font-semibold text-foreground truncate">{meta.name}</h3>
|
||||||
{meta.isPlugin && (
|
{meta.isPlugin && (
|
||||||
<Badge variant="secondary" className="font-mono text-[10px] font-medium px-2 py-0.5 rounded-full bg-black/[0.04] dark:bg-white/[0.08] border-0 shadow-none text-foreground/70">
|
<Badge variant="secondary" className="font-mono text-[10px] font-medium px-2 py-0.5 rounded-full bg-black/[0.04] dark:bg-white/[0.08] border-0 shadow-none text-foreground/70">
|
||||||
{t('pluginBadge', 'Plugin')}
|
{t('pluginBadge')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ export function Models() {
|
|||||||
<div className="flex flex-col md:flex-row md:items-start justify-between mb-12 shrink-0 gap-4">
|
<div className="flex flex-col md:flex-row md:items-start justify-between mb-12 shrink-0 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-5xl md:text-6xl font-serif text-foreground mb-3 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
<h1 className="text-5xl md:text-6xl font-serif text-foreground mb-3 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
Models
|
{t('dashboard:models.title')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[17px] text-foreground/80 font-medium">
|
<p className="text-[17px] text-foreground/80 font-medium">
|
||||||
Manage your AI providers and monitor token usage.
|
{t('dashboard:models.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { useGatewayStore } from '@/stores/gateway';
|
import { useGatewayStore } from '@/stores/gateway';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import type { TFunction } from 'i18next';
|
||||||
import { SUPPORTED_LANGUAGES } from '@/i18n';
|
import { SUPPORTED_LANGUAGES } from '@/i18n';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { invokeIpc } from '@/lib/api-client';
|
import { invokeIpc } from '@/lib/api-client';
|
||||||
@@ -48,31 +49,31 @@ const STEP = {
|
|||||||
COMPLETE: 4,
|
COMPLETE: 4,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const steps: SetupStep[] = [
|
const getSteps = (t: TFunction): SetupStep[] => [
|
||||||
{
|
{
|
||||||
id: 'welcome',
|
id: 'welcome',
|
||||||
title: 'Welcome to ClawX',
|
title: t('steps.welcome.title'),
|
||||||
description: 'Your AI assistant is ready to be configured',
|
description: t('steps.welcome.description'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'runtime',
|
id: 'runtime',
|
||||||
title: 'Environment Check',
|
title: t('steps.runtime.title'),
|
||||||
description: 'Verifying system requirements',
|
description: t('steps.runtime.description'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'provider',
|
id: 'provider',
|
||||||
title: 'AI Provider',
|
title: t('steps.provider.title'),
|
||||||
description: 'Configure your AI service',
|
description: t('steps.provider.description'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'installing',
|
id: 'installing',
|
||||||
title: 'Setting Up',
|
title: t('steps.installing.title'),
|
||||||
description: 'Installing essential components',
|
description: t('steps.installing.description'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'complete',
|
id: 'complete',
|
||||||
title: 'All Set!',
|
title: t('steps.complete.title'),
|
||||||
description: 'ClawX is ready to use',
|
description: t('steps.complete.description'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -83,12 +84,12 @@ interface DefaultSkill {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSkills: DefaultSkill[] = [
|
const getDefaultSkills = (t: TFunction): DefaultSkill[] => [
|
||||||
{ id: 'opencode', name: 'OpenCode', description: 'AI coding assistant backend' },
|
{ id: 'opencode', name: t('defaultSkills.opencode.name'), description: t('defaultSkills.opencode.description') },
|
||||||
{ id: 'python-env', name: 'Python Environment', description: 'Python runtime for skills' },
|
{ id: 'python-env', name: t('defaultSkills.python-env.name'), description: t('defaultSkills.python-env.description') },
|
||||||
{ id: 'code-assist', name: 'Code Assist', description: 'Code analysis and suggestions' },
|
{ id: 'code-assist', name: t('defaultSkills.code-assist.name'), description: t('defaultSkills.code-assist.description') },
|
||||||
{ id: 'file-tools', name: 'File Tools', description: 'File operations and management' },
|
{ id: 'file-tools', name: t('defaultSkills.file-tools.name'), description: t('defaultSkills.file-tools.description') },
|
||||||
{ id: 'terminal', name: 'Terminal', description: 'Shell command execution' },
|
{ id: 'terminal', name: t('defaultSkills.terminal.name'), description: t('defaultSkills.terminal.description') },
|
||||||
];
|
];
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -130,6 +131,7 @@ export function Setup() {
|
|||||||
// Runtime check status
|
// Runtime check status
|
||||||
const [runtimeChecksPassed, setRuntimeChecksPassed] = useState(false);
|
const [runtimeChecksPassed, setRuntimeChecksPassed] = useState(false);
|
||||||
|
|
||||||
|
const steps = getSteps(t);
|
||||||
const safeStepIndex = Number.isInteger(currentStep)
|
const safeStepIndex = Number.isInteger(currentStep)
|
||||||
? Math.min(Math.max(currentStep, STEP.WELCOME), steps.length - 1)
|
? Math.min(Math.max(currentStep, STEP.WELCOME), steps.length - 1)
|
||||||
: STEP.WELCOME;
|
: STEP.WELCOME;
|
||||||
@@ -255,7 +257,7 @@ export function Setup() {
|
|||||||
)}
|
)}
|
||||||
{safeStepIndex === STEP.INSTALLING && (
|
{safeStepIndex === STEP.INSTALLING && (
|
||||||
<InstallingContent
|
<InstallingContent
|
||||||
skills={defaultSkills}
|
skills={getDefaultSkills(t)}
|
||||||
onComplete={handleInstallationComplete}
|
onComplete={handleInstallationComplete}
|
||||||
onSkip={() => setCurrentStep((i) => i + 1)}
|
onSkip={() => setCurrentStep((i) => i + 1)}
|
||||||
/>
|
/>
|
||||||
@@ -634,10 +636,10 @@ function RuntimeContent({ onStatusChange }: RuntimeContentProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-[1fr_auto] items-center gap-4 p-3 rounded-lg bg-muted/50">
|
<div className="grid grid-cols-[1fr_auto] items-center gap-4 p-3 rounded-lg bg-muted/50">
|
||||||
<div className="flex items-center gap-2 text-left">
|
<div className="flex items-center gap-2 text-left">
|
||||||
<span>Gateway Service</span>
|
<span>{t('runtime.gateway')}</span>
|
||||||
{checks.gateway.status === 'error' && (
|
{checks.gateway.status === 'error' && (
|
||||||
<Button variant="outline" size="sm" onClick={handleStartGateway}>
|
<Button variant="outline" size="sm" onClick={handleStartGateway}>
|
||||||
Start Gateway
|
{t('runtime.startGateway')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -665,19 +667,19 @@ function RuntimeContent({ onStatusChange }: RuntimeContentProps) {
|
|||||||
{showLogs && (
|
{showLogs && (
|
||||||
<div className="mt-4 p-4 rounded-lg bg-black/40 border border-border">
|
<div className="mt-4 p-4 rounded-lg bg-black/40 border border-border">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<p className="font-medium text-foreground text-sm">Application Logs</p>
|
<p className="font-medium text-foreground text-sm">{t('runtime.logs.title')}</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={handleOpenLogDir}>
|
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={handleOpenLogDir}>
|
||||||
<ExternalLink className="h-3 w-3 mr-1" />
|
<ExternalLink className="h-3 w-3 mr-1" />
|
||||||
Open Log Folder
|
{t('runtime.logs.openFolder')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setShowLogs(false)}>
|
<Button variant="ghost" size="sm" className="h-7 text-xs" onClick={() => setShowLogs(false)}>
|
||||||
Close
|
{t('runtime.logs.close')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pre className="text-xs text-slate-300 bg-black/50 p-3 rounded max-h-60 overflow-auto whitespace-pre-wrap font-mono">
|
<pre className="text-xs text-slate-300 bg-black/50 p-3 rounded max-h-60 overflow-auto whitespace-pre-wrap font-mono">
|
||||||
{logContent || '(No logs available yet)'}
|
{logContent || t('runtime.logs.noLogs')}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1574,9 +1576,9 @@ function CompleteContent({ selectedProvider, installedSkills }: CompleteContentP
|
|||||||
const gatewayStatus = useGatewayStore((state) => state.status);
|
const gatewayStatus = useGatewayStore((state) => state.status);
|
||||||
|
|
||||||
const providerData = providers.find((p) => p.id === selectedProvider);
|
const providerData = providers.find((p) => p.id === selectedProvider);
|
||||||
const installedSkillNames = defaultSkills
|
const installedSkillNames = getDefaultSkills(t)
|
||||||
.filter((s) => installedSkills.includes(s.id))
|
.filter((s: DefaultSkill) => installedSkills.includes(s.id))
|
||||||
.map((s) => s.name)
|
.map((s: DefaultSkill) => s.name)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall }: Sk
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-[13px] font-bold flex items-center gap-2 text-foreground/80">
|
<h3 className="text-[13px] font-bold flex items-center gap-2 text-foreground/80">
|
||||||
<Key className="h-3.5 w-3.5 text-blue-500" />
|
<Key className="h-3.5 w-3.5 text-blue-500" />
|
||||||
API Key
|
{t('detail.apiKey')}
|
||||||
</h3>
|
</h3>
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('detail.apiKeyPlaceholder', 'Enter API Key (optional)')}
|
placeholder={t('detail.apiKeyPlaceholder', 'Enter API Key (optional)')}
|
||||||
@@ -218,7 +218,7 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall }: Sk
|
|||||||
<div className="flex items-center justify-between w-full">
|
<div className="flex items-center justify-between w-full">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="text-[13px] font-bold text-foreground/80">
|
<h3 className="text-[13px] font-bold text-foreground/80">
|
||||||
Environment Variables
|
{t('detail.envVars')}
|
||||||
{envVars.length > 0 && (
|
{envVars.length > 0 && (
|
||||||
<Badge variant="secondary" className="ml-2 px-1.5 py-0 text-[10px] h-5 bg-black/10 dark:bg-white/10 text-foreground">
|
<Badge variant="secondary" className="ml-2 px-1.5 py-0 text-[10px] h-5 bg-black/10 dark:bg-white/10 text-foreground">
|
||||||
{envVars.length}
|
{envVars.length}
|
||||||
@@ -298,7 +298,7 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall }: Sk
|
|||||||
)}
|
)}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
>
|
>
|
||||||
{isSaving ? t('detail.saving', 'Saving...') : 'Save Configuration'}
|
{isSaving ? t('detail.saving') : t('detail.saveConfig')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -316,8 +316,8 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall }: Sk
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!skill.isBundled && onUninstall
|
{!skill.isBundled && onUninstall
|
||||||
? 'Uninstall'
|
? t('detail.uninstall')
|
||||||
: (skill.enabled ? t('detail.disable', 'Disable') : t('detail.enable', 'Enable'))}
|
: (skill.enabled ? t('detail.disable') : t('detail.enable'))}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -554,10 +554,10 @@ export function Skills() {
|
|||||||
<div className="flex flex-col md:flex-row md:items-start justify-between mb-6 shrink-0 gap-4">
|
<div className="flex flex-col md:flex-row md:items-start justify-between mb-6 shrink-0 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-5xl md:text-6xl font-serif text-foreground mb-3 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
<h1 className="text-5xl md:text-6xl font-serif text-foreground mb-3 font-normal tracking-tight" style={{ fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif' }}>
|
||||||
{t('title') || 'Skills'}
|
{t('title')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[17px] text-foreground/80 font-medium">
|
<p className="text-[17px] text-foreground/80 font-medium">
|
||||||
{t('subtitle') || 'Browse and manage AI capabilities.'}
|
{t('subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -568,7 +568,7 @@ export function Skills() {
|
|||||||
className="hover:bg-black/5 dark:hover:bg-white/5 transition-colors shrink-0 text-[13px] font-medium px-4 h-8 rounded-full border border-black/10 dark:border-white/10 flex items-center justify-center text-foreground/80 hover:text-foreground"
|
className="hover:bg-black/5 dark:hover:bg-white/5 transition-colors shrink-0 text-[13px] font-medium px-4 h-8 rounded-full border border-black/10 dark:border-white/10 flex items-center justify-center text-foreground/80 hover:text-foreground"
|
||||||
>
|
>
|
||||||
<FolderOpen className="h-4 w-4 mr-2" />
|
<FolderOpen className="h-4 w-4 mr-2" />
|
||||||
Open Skills Folder
|
{t('openFolder')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -611,22 +611,19 @@ export function Skills() {
|
|||||||
onClick={() => { setActiveTab('all'); setSelectedSource('all'); }}
|
onClick={() => { setActiveTab('all'); setSelectedSource('all'); }}
|
||||||
className={cn("font-medium transition-colors flex items-center gap-1.5", activeTab === 'all' && selectedSource === 'all' ? "text-foreground" : "text-muted-foreground hover:text-foreground")}
|
className={cn("font-medium transition-colors flex items-center gap-1.5", activeTab === 'all' && selectedSource === 'all' ? "text-foreground" : "text-muted-foreground hover:text-foreground")}
|
||||||
>
|
>
|
||||||
All Skills
|
{t('filter.all', { count: sourceStats.all })}
|
||||||
<span className="text-[12px] font-normal opacity-70">{sourceStats.all}</span>
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setActiveTab('all'); setSelectedSource('built-in'); }}
|
onClick={() => { setActiveTab('all'); setSelectedSource('built-in'); }}
|
||||||
className={cn("font-medium transition-colors flex items-center gap-1.5", activeTab === 'all' && selectedSource === 'built-in' ? "text-foreground" : "text-muted-foreground hover:text-foreground")}
|
className={cn("font-medium transition-colors flex items-center gap-1.5", activeTab === 'all' && selectedSource === 'built-in' ? "text-foreground" : "text-muted-foreground hover:text-foreground")}
|
||||||
>
|
>
|
||||||
Built-in
|
{t('filter.builtIn', { count: sourceStats.builtIn })}
|
||||||
<span className="text-[12px] font-normal opacity-70">{sourceStats.builtIn}</span>
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('marketplace')}
|
onClick={() => setActiveTab('marketplace')}
|
||||||
className={cn("font-medium transition-colors flex items-center gap-1.5", activeTab === 'marketplace' ? "text-foreground" : "text-muted-foreground hover:text-foreground")}
|
className={cn("font-medium transition-colors flex items-center gap-1.5", activeTab === 'marketplace' ? "text-foreground" : "text-muted-foreground hover:text-foreground")}
|
||||||
>
|
>
|
||||||
Marketplace
|
{t('filter.marketplace', { count: sourceStats.marketplace })}
|
||||||
<span className="text-[12px] font-normal opacity-70">{sourceStats.marketplace}</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -640,7 +637,7 @@ export function Skills() {
|
|||||||
onClick={() => bulkToggleVisible(true)}
|
onClick={() => bulkToggleVisible(true)}
|
||||||
className="h-8 text-[13px] font-medium rounded-md px-3 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5 shadow-none"
|
className="h-8 text-[13px] font-medium rounded-md px-3 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5 shadow-none"
|
||||||
>
|
>
|
||||||
Enable All
|
{t('actions.enableVisible')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -648,7 +645,7 @@ export function Skills() {
|
|||||||
onClick={() => bulkToggleVisible(false)}
|
onClick={() => bulkToggleVisible(false)}
|
||||||
className="h-8 text-[13px] font-medium rounded-md px-3 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5 shadow-none"
|
className="h-8 text-[13px] font-medium rounded-md px-3 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5 shadow-none"
|
||||||
>
|
>
|
||||||
Disable All
|
{t('actions.disableVisible')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user