main
mula.liu 2026-03-03 16:12:27 +08:00
parent 0ef036621c
commit 9ddeedf0b6
9 changed files with 300 additions and 239 deletions

View File

@ -10,6 +10,8 @@ import { BotDashboardModule } from './modules/dashboard/BotDashboardModule';
import { pickLocale } from './i18n'; import { pickLocale } from './i18n';
import { appZhCn } from './i18n/app.zh-cn'; import { appZhCn } from './i18n/app.zh-cn';
import { appEn } from './i18n/app.en'; import { appEn } from './i18n/app.en';
import { LucentIconButton } from './components/lucent/LucentIconButton';
import { LucentTooltip } from './components/lucent/LucentTooltip';
import './App.css'; import './App.css';
function App() { function App() {
@ -53,41 +55,45 @@ function App() {
<div className="global-switches"> <div className="global-switches">
<div className="switch-compact"> <div className="switch-compact">
<LucentTooltip content={t.dark}>
<button <button
className={`switch-btn ${theme === 'dark' ? 'active' : ''}`} className={`switch-btn ${theme === 'dark' ? 'active' : ''}`}
onClick={() => setTheme('dark')} onClick={() => setTheme('dark')}
title={t.dark}
aria-label={t.dark} aria-label={t.dark}
> >
<MoonStar size={14} /> <MoonStar size={14} />
</button> </button>
</LucentTooltip>
<LucentTooltip content={t.light}>
<button <button
className={`switch-btn ${theme === 'light' ? 'active' : ''}`} className={`switch-btn ${theme === 'light' ? 'active' : ''}`}
onClick={() => setTheme('light')} onClick={() => setTheme('light')}
title={t.light}
aria-label={t.light} aria-label={t.light}
> >
<SunMedium size={14} /> <SunMedium size={14} />
</button> </button>
</LucentTooltip>
</div> </div>
<div className="switch-compact"> <div className="switch-compact">
<LucentTooltip content={t.zh}>
<button <button
className={`switch-btn switch-btn-lang ${locale === 'zh' ? 'active' : ''}`} className={`switch-btn switch-btn-lang ${locale === 'zh' ? 'active' : ''}`}
onClick={() => setLocale('zh')} onClick={() => setLocale('zh')}
title={t.zh}
aria-label={t.zh} aria-label={t.zh}
> >
<span>ZH</span> <span>ZH</span>
</button> </button>
</LucentTooltip>
<LucentTooltip content={t.en}>
<button <button
className={`switch-btn switch-btn-lang ${locale === 'en' ? 'active' : ''}`} className={`switch-btn switch-btn-lang ${locale === 'en' ? 'active' : ''}`}
onClick={() => setLocale('en')} onClick={() => setLocale('en')}
title={t.en}
aria-label={t.en} aria-label={t.en}
> >
<span>EN</span> <span>EN</span>
</button> </button>
</LucentTooltip>
</div> </div>
</div> </div>
</div> </div>
@ -111,9 +117,9 @@ function App() {
<h3>{t.nav.images.title}</h3> <h3>{t.nav.images.title}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowImageFactory(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowImageFactory(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="app-modal-body"> <div className="app-modal-body">
@ -131,9 +137,9 @@ function App() {
<h3>{t.nav.onboarding.title}</h3> <h3>{t.nav.onboarding.title}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowCreateWizard(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowCreateWizard(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="app-modal-body"> <div className="app-modal-body">

View File

@ -0,0 +1,29 @@
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { LucentTooltip } from './LucentTooltip';
interface LucentIconButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'title'> {
tooltip?: string;
tooltipSide?: 'top' | 'bottom';
children: ReactNode;
}
export function LucentIconButton({
tooltip,
tooltipSide = 'top',
children,
'aria-label': ariaLabel,
...buttonProps
}: LucentIconButtonProps) {
const tipText = String(tooltip || ariaLabel || '').trim();
return (
<LucentTooltip content={tipText} side={tooltipSide}>
<button
{...buttonProps}
aria-label={String(ariaLabel || tipText || '').trim() || undefined}
>
{children}
</button>
</LucentTooltip>
);
}

View File

@ -1,6 +1,7 @@
import { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from 'react'; import { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from 'react';
import { AlertCircle, AlertTriangle, CheckCircle2, Info, X } from 'lucide-react'; import { AlertCircle, AlertTriangle, CheckCircle2, Info, X } from 'lucide-react';
import { useAppStore } from '../../store/appStore'; import { useAppStore } from '../../store/appStore';
import { LucentIconButton } from './LucentIconButton';
import './lucent-prompt.css'; import './lucent-prompt.css';
type PromptTone = 'info' | 'success' | 'warning' | 'error'; type PromptTone = 'info' | 'success' | 'warning' | 'error';
@ -133,14 +134,14 @@ export function LucentPromptProvider({ children }: { children: ReactNode }) {
<div className="lucent-confirm-title"> <div className="lucent-confirm-title">
{confirmState.title || (locale === 'zh' ? '请确认操作' : 'Please Confirm')} {confirmState.title || (locale === 'zh' ? '请确认操作' : 'Please Confirm')}
</div> </div>
<button <LucentIconButton
className="lucent-confirm-close" className="lucent-confirm-close"
onClick={() => closeConfirm(false)} onClick={() => closeConfirm(false)}
aria-label={locale === 'zh' ? '关闭' : 'Close'} aria-label={locale === 'zh' ? '关闭' : 'Close'}
title={locale === 'zh' ? '关闭' : 'Close'} tooltip={locale === 'zh' ? '关闭' : 'Close'}
> >
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
<div className="lucent-confirm-message">{confirmState.message}</div> <div className="lucent-confirm-message">{confirmState.message}</div>
<div className="lucent-confirm-actions"> <div className="lucent-confirm-actions">
@ -165,4 +166,3 @@ export function useLucentPrompt() {
} }
return ctx; return ctx;
} }

View File

@ -21,19 +21,40 @@
visibility: hidden; visibility: hidden;
transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s ease; transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s ease;
z-index: 40; z-index: 40;
box-shadow: 0 8px 18px rgba(6, 12, 24, 0.24);
}
.lucent-tooltip-bubble::after {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 7px;
height: 7px;
border-right: 1px solid color-mix(in oklab, var(--line) 72%, var(--brand) 28%);
border-bottom: 1px solid color-mix(in oklab, var(--line) 72%, var(--brand) 28%);
background: color-mix(in oklab, var(--panel) 88%, #000 12%);
} }
.lucent-tooltip-wrap.side-top .lucent-tooltip-bubble { .lucent-tooltip-wrap.side-top .lucent-tooltip-bubble {
bottom: calc(100% + 8px); bottom: calc(100% + 8px);
} }
.lucent-tooltip-wrap.side-top .lucent-tooltip-bubble::after {
bottom: -5px;
}
.lucent-tooltip-wrap.side-bottom .lucent-tooltip-bubble { .lucent-tooltip-wrap.side-bottom .lucent-tooltip-bubble {
top: calc(100% + 8px); top: calc(100% + 8px);
} }
.lucent-tooltip-wrap.side-bottom .lucent-tooltip-bubble::after {
top: -5px;
transform: translateX(-50%) rotate(225deg);
}
.lucent-tooltip-wrap:hover .lucent-tooltip-bubble, .lucent-tooltip-wrap:hover .lucent-tooltip-bubble,
.lucent-tooltip-wrap:focus-within .lucent-tooltip-bubble { .lucent-tooltip-wrap:focus-within .lucent-tooltip-bubble {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
} }

View File

@ -99,19 +99,19 @@
} }
.ops-bot-icon-btn { .ops-bot-icon-btn {
width: 31px; width: 36px;
height: 31px; height: 36px;
padding: 0; padding: 0;
border-radius: 8px; border-radius: 10px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.ops-bot-icon-btn svg { .ops-bot-icon-btn svg {
width: 13px; width: 17px;
height: 13px; height: 17px;
stroke-width: 2.2; stroke-width: 2.1;
} }
.ops-bot-actions .ops-bot-action-monitor { .ops-bot-actions .ops-bot-action-monitor {

View File

@ -17,7 +17,7 @@ import { pickLocale } from '../../i18n';
import { dashboardZhCn } from '../../i18n/dashboard.zh-cn'; import { dashboardZhCn } from '../../i18n/dashboard.zh-cn';
import { dashboardEn } from '../../i18n/dashboard.en'; import { dashboardEn } from '../../i18n/dashboard.en';
import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider'; import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider';
import { LucentTooltip } from '../../components/lucent/LucentTooltip'; import { LucentIconButton } from '../../components/lucent/LucentIconButton';
interface BotDashboardModuleProps { interface BotDashboardModuleProps {
onOpenCreateWizard?: () => void; onOpenCreateWizard?: () => void;
@ -905,7 +905,7 @@ export function BotDashboardModule({
<div className="ops-chat-meta-right"> <div className="ops-chat-meta-right">
<span className="mono">{formatClock(item.ts)}</span> <span className="mono">{formatClock(item.ts)}</span>
{collapsible ? ( {collapsible ? (
<button <LucentIconButton
className="ops-chat-expand-icon-btn" className="ops-chat-expand-icon-btn"
onClick={() => onClick={() =>
setExpandedProgressByKey((prev) => ({ setExpandedProgressByKey((prev) => ({
@ -913,11 +913,11 @@ export function BotDashboardModule({
[itemKey]: !prev[itemKey], [itemKey]: !prev[itemKey],
})) }))
} }
title={expanded ? (isZh ? '收起' : 'Collapse') : (isZh ? '展开' : 'Expand')} tooltip={expanded ? (isZh ? '收起' : 'Collapse') : (isZh ? '展开' : 'Expand')}
aria-label={expanded ? (isZh ? '收起' : 'Collapse') : (isZh ? '展开' : 'Expand')} aria-label={expanded ? (isZh ? '收起' : 'Collapse') : (isZh ? '展开' : 'Expand')}
> >
{expanded ? '×' : '…'} {expanded ? '×' : '…'}
</button> </LucentIconButton>
) : null} ) : null}
</div> </div>
</div> </div>
@ -1994,22 +1994,22 @@ export function BotDashboardModule({
<div className="row-between"> <div className="row-between">
<h2 style={{ fontSize: 18 }}>{t.titleBots}</h2> <h2 style={{ fontSize: 18 }}>{t.titleBots}</h2>
<div className="ops-list-actions"> <div className="ops-list-actions">
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={onOpenImageFactory} onClick={onOpenImageFactory}
title={t.manageImages} tooltip={t.manageImages}
aria-label={t.manageImages} aria-label={t.manageImages}
> >
<Boxes size={14} /> <Boxes size={14} />
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-primary btn-sm icon-btn" className="btn btn-primary btn-sm icon-btn"
onClick={onOpenCreateWizard} onClick={onOpenCreateWizard}
title={t.newBot} tooltip={t.newBot}
aria-label={t.newBot} aria-label={t.newBot}
> >
<Plus size={14} /> <Plus size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
@ -2032,27 +2032,26 @@ export function BotDashboardModule({
</div> </div>
<div className="ops-bot-meta">{t.image}: <span className="mono">{bot.image_tag || '-'}</span></div> <div className="ops-bot-meta">{t.image}: <span className="mono">{bot.image_tag || '-'}</span></div>
<div className="ops-bot-actions"> <div className="ops-bot-actions">
<LucentTooltip content={isZh ? '资源监测' : 'Resource Monitor'}> <LucentIconButton
<button
className="btn btn-sm ops-bot-icon-btn ops-bot-action-monitor" className="btn btn-sm ops-bot-icon-btn ops-bot-action-monitor"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
openResourceMonitor(bot.id); openResourceMonitor(bot.id);
}} }}
tooltip={isZh ? '资源监测' : 'Resource Monitor'}
aria-label={isZh ? '资源监测' : 'Resource Monitor'} aria-label={isZh ? '资源监测' : 'Resource Monitor'}
> >
<Gauge size={14} /> <Gauge size={14} />
</button> </LucentIconButton>
</LucentTooltip>
{bot.docker_status === 'RUNNING' ? ( {bot.docker_status === 'RUNNING' ? (
<LucentTooltip content={t.stop}> <LucentIconButton
<button
className="btn btn-sm ops-bot-icon-btn ops-bot-action-stop" className="btn btn-sm ops-bot-icon-btn ops-bot-action-stop"
disabled={isOperating} disabled={isOperating}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
void stopBot(bot.id, bot.docker_status); void stopBot(bot.id, bot.docker_status);
}} }}
tooltip={t.stop}
aria-label={t.stop} aria-label={t.stop}
> >
{isStopping ? ( {isStopping ? (
@ -2064,17 +2063,16 @@ export function BotDashboardModule({
</span> </span>
</span> </span>
) : <Square size={14} />} ) : <Square size={14} />}
</button> </LucentIconButton>
</LucentTooltip>
) : ( ) : (
<LucentTooltip content={t.start}> <LucentIconButton
<button
className="btn btn-sm ops-bot-icon-btn ops-bot-action-start" className="btn btn-sm ops-bot-icon-btn ops-bot-action-start"
disabled={isOperating} disabled={isOperating}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
void startBot(bot.id, bot.docker_status); void startBot(bot.id, bot.docker_status);
}} }}
tooltip={t.start}
aria-label={t.start} aria-label={t.start}
> >
{isStarting ? ( {isStarting ? (
@ -2086,21 +2084,19 @@ export function BotDashboardModule({
</span> </span>
</span> </span>
) : <Power size={14} />} ) : <Power size={14} />}
</button> </LucentIconButton>
</LucentTooltip>
)} )}
<LucentTooltip content={t.delete}> <LucentIconButton
<button
className="btn btn-sm ops-bot-icon-btn ops-bot-action-delete" className="btn btn-sm ops-bot-icon-btn ops-bot-action-delete"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
void removeBot(bot.id); void removeBot(bot.id);
}} }}
tooltip={t.delete}
aria-label={t.delete} aria-label={t.delete}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </LucentIconButton>
</LucentTooltip>
</div> </div>
</div> </div>
); );
@ -2163,15 +2159,15 @@ export function BotDashboardModule({
: t.disabledPlaceholder : t.disabledPlaceholder
} }
/> />
<button <LucentIconButton
className="btn btn-secondary icon-btn" className="btn btn-secondary icon-btn"
disabled={!canChat || isUploadingAttachments} disabled={!canChat || isUploadingAttachments}
onClick={triggerPickAttachments} onClick={triggerPickAttachments}
title={isUploadingAttachments ? t.uploadingFile : t.uploadFile} tooltip={isUploadingAttachments ? t.uploadingFile : t.uploadFile}
aria-label={isUploadingAttachments ? t.uploadingFile : t.uploadFile} aria-label={isUploadingAttachments ? t.uploadingFile : t.uploadFile}
> >
<Paperclip size={14} className={isUploadingAttachments ? 'animate-spin' : ''} /> <Paperclip size={14} className={isUploadingAttachments ? 'animate-spin' : ''} />
</button> </LucentIconButton>
<button <button
className="btn btn-primary" className="btn btn-primary"
disabled={!isChatEnabled || (!command.trim() && pendingAttachments.length === 0)} disabled={!isChatEnabled || (!command.trim() && pendingAttachments.length === 0)}
@ -2210,18 +2206,18 @@ export function BotDashboardModule({
</a> </a>
); );
})()} })()}
<button <LucentIconButton
className="icon-btn ops-chip-remove" className="icon-btn ops-chip-remove"
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setPendingAttachments((prev) => prev.filter((v) => v !== p)); setPendingAttachments((prev) => prev.filter((v) => v !== p));
}} }}
title={t.removeAttachment} tooltip={t.removeAttachment}
aria-label={t.removeAttachment} aria-label={t.removeAttachment}
> >
<X size={12} /> <X size={12} />
</button> </LucentIconButton>
</span> </span>
))} ))}
</div> </div>
@ -2254,24 +2250,24 @@ export function BotDashboardModule({
<div className="row-between ops-runtime-head"> <div className="row-between ops-runtime-head">
<h2 style={{ fontSize: 18 }}>{t.runtime}</h2> <h2 style={{ fontSize: 18 }}>{t.runtime}</h2>
<div className="ops-panel-tools" ref={runtimeMenuRef}> <div className="ops-panel-tools" ref={runtimeMenuRef}>
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => setRuntimeViewMode((m) => (m === 'visual' ? 'text' : 'visual'))} onClick={() => setRuntimeViewMode((m) => (m === 'visual' ? 'text' : 'visual'))}
title={runtimeViewMode === 'visual' ? (isZh ? '切换为文字面板' : 'Switch to text panel') : (isZh ? '切换为机器人面板' : 'Switch to bot panel')} tooltip={runtimeViewMode === 'visual' ? (isZh ? '切换为文字面板' : 'Switch to text panel') : (isZh ? '切换为机器人面板' : 'Switch to bot panel')}
aria-label={runtimeViewMode === 'visual' ? (isZh ? '切换为文字面板' : 'Switch to text panel') : (isZh ? '切换为机器人面板' : 'Switch to bot panel')} aria-label={runtimeViewMode === 'visual' ? (isZh ? '切换为文字面板' : 'Switch to text panel') : (isZh ? '切换为机器人面板' : 'Switch to bot panel')}
> >
<Repeat2 size={14} /> <Repeat2 size={14} />
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => setRuntimeMenuOpen((v) => !v)} onClick={() => setRuntimeMenuOpen((v) => !v)}
title={runtimeMoreLabel} tooltip={runtimeMoreLabel}
aria-label={runtimeMoreLabel} aria-label={runtimeMoreLabel}
aria-haspopup="menu" aria-haspopup="menu"
aria-expanded={runtimeMenuOpen} aria-expanded={runtimeMenuOpen}
> >
<EllipsisVertical size={14} /> <EllipsisVertical size={14} />
</button> </LucentIconButton>
{runtimeMenuOpen ? ( {runtimeMenuOpen ? (
<div className="ops-more-menu" role="menu" aria-label={runtimeMoreLabel}> <div className="ops-more-menu" role="menu" aria-label={runtimeMoreLabel}>
<button <button
@ -2412,14 +2408,14 @@ export function BotDashboardModule({
<div className="ops-runtime-action-inline"> <div className="ops-runtime-action-inline">
<strong className="ops-runtime-action-text">{runtimeActionDisplay}</strong> <strong className="ops-runtime-action-text">{runtimeActionDisplay}</strong>
{runtimeActionHasMore ? ( {runtimeActionHasMore ? (
<button <LucentIconButton
className="ops-runtime-expand-btn" className="ops-runtime-expand-btn"
onClick={() => setShowRuntimeActionModal(true)} onClick={() => setShowRuntimeActionModal(true)}
title={isZh ? '查看完整内容' : 'Show full content'} tooltip={isZh ? '查看完整内容' : 'Show full content'}
aria-label={isZh ? '查看完整内容' : 'Show full content'} aria-label={isZh ? '查看完整内容' : 'Show full content'}
> >
</button> </LucentIconButton>
) : null} ) : null}
</div> </div>
</div> </div>
@ -2433,15 +2429,15 @@ export function BotDashboardModule({
<div className="section-mini-title">{t.workspaceOutputs}</div> <div className="section-mini-title">{t.workspaceOutputs}</div>
{workspaceError ? <div className="ops-empty-inline">{workspaceError}</div> : null} {workspaceError ? <div className="ops-empty-inline">{workspaceError}</div> : null}
<div className="workspace-toolbar"> <div className="workspace-toolbar">
<button <LucentIconButton
className="workspace-refresh-icon-btn" className="workspace-refresh-icon-btn"
disabled={workspaceLoading || !selectedBotId} disabled={workspaceLoading || !selectedBotId}
onClick={() => void loadWorkspaceTree(selectedBot.id, workspaceCurrentPath)} onClick={() => void loadWorkspaceTree(selectedBot.id, workspaceCurrentPath)}
title={lc.refreshHint} tooltip={lc.refreshHint}
aria-label={lc.refreshHint} aria-label={lc.refreshHint}
> >
<RefreshCw size={14} className={workspaceLoading ? 'animate-spin' : ''} /> <RefreshCw size={14} className={workspaceLoading ? 'animate-spin' : ''} />
</button> </LucentIconButton>
<label className="workspace-auto-switch" title={lc.autoRefresh}> <label className="workspace-auto-switch" title={lc.autoRefresh}>
<span className="workspace-auto-switch-label">{lc.autoRefresh}</span> <span className="workspace-auto-switch-label">{lc.autoRefresh}</span>
<input <input
@ -2487,14 +2483,14 @@ export function BotDashboardModule({
</section> </section>
</div> </div>
{compactMode && isCompactMobile ? ( {compactMode && isCompactMobile ? (
<button <LucentIconButton
className="ops-compact-fab-switch" className="ops-compact-fab-switch"
onClick={() => setCompactPanelTab((v) => (v === 'chat' ? 'runtime' : 'chat'))} onClick={() => setCompactPanelTab((v) => (v === 'chat' ? 'runtime' : 'chat'))}
title={compactPanelTab === 'chat' ? (isZh ? '切换到运行面板' : 'Switch to runtime') : (isZh ? '切换到对话面板' : 'Switch to chat')} tooltip={compactPanelTab === 'chat' ? (isZh ? '切换到运行面板' : 'Switch to runtime') : (isZh ? '切换到对话面板' : 'Switch to chat')}
aria-label={compactPanelTab === 'chat' ? (isZh ? '切换到运行面板' : 'Switch to runtime') : (isZh ? '切换到对话面板' : 'Switch to chat')} aria-label={compactPanelTab === 'chat' ? (isZh ? '切换到运行面板' : 'Switch to runtime') : (isZh ? '切换到对话面板' : 'Switch to chat')}
> >
{compactPanelTab === 'chat' ? <Activity size={18} /> : <MessageSquareText size={18} />} {compactPanelTab === 'chat' ? <Activity size={18} /> : <MessageSquareText size={18} />}
</button> </LucentIconButton>
) : null} ) : null}
{showResourceModal && ( {showResourceModal && (
@ -2506,22 +2502,22 @@ export function BotDashboardModule({
<span className="modal-sub mono">{resourceBot?.name || resourceBotId}</span> <span className="modal-sub mono">{resourceBot?.name || resourceBotId}</span>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => void loadResourceSnapshot(resourceBotId)} onClick={() => void loadResourceSnapshot(resourceBotId)}
title={isZh ? '立即刷新' : 'Refresh now'} tooltip={isZh ? '立即刷新' : 'Refresh now'}
aria-label={isZh ? '立即刷新' : 'Refresh now'} aria-label={isZh ? '立即刷新' : 'Refresh now'}
> >
<RefreshCw size={14} className={resourceLoading ? 'animate-spin' : ''} /> <RefreshCw size={14} className={resourceLoading ? 'animate-spin' : ''} />
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => setShowResourceModal(false)} onClick={() => setShowResourceModal(false)}
title={t.close} tooltip={t.close}
aria-label={t.close} aria-label={t.close}
> >
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
@ -2589,9 +2585,9 @@ export function BotDashboardModule({
<span className="modal-sub">{t.baseConfigSub}</span> <span className="modal-sub">{t.baseConfigSub}</span>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowBaseModal(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowBaseModal(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
@ -2661,9 +2657,9 @@ export function BotDashboardModule({
<h3>{t.modelParams}</h3> <h3>{t.modelParams}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowParamModal(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowParamModal(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="slider-row"> <div className="slider-row">
@ -2743,9 +2739,9 @@ export function BotDashboardModule({
<h3>{lc.wizardSectionTitle}</h3> <h3>{lc.wizardSectionTitle}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowChannelModal(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowChannelModal(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="card" style={{ fontSize: 12, color: 'var(--muted)' }}> <div className="card" style={{ fontSize: 12, color: 'var(--muted)' }}>
@ -2773,15 +2769,15 @@ export function BotDashboardModule({
/> />
{lc.sendToolHints} {lc.sendToolHints}
</label> </label>
<button <LucentIconButton
className="btn btn-primary btn-sm icon-btn" className="btn btn-primary btn-sm icon-btn"
disabled={isSavingGlobalDelivery || !selectedBot} disabled={isSavingGlobalDelivery || !selectedBot}
onClick={() => void saveGlobalDelivery()} onClick={() => void saveGlobalDelivery()}
title={lc.saveChannel} tooltip={lc.saveChannel}
aria-label={lc.saveChannel} aria-label={lc.saveChannel}
> >
<Save size={14} /> <Save size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="wizard-channel-list"> <div className="wizard-channel-list">
@ -2801,28 +2797,29 @@ export function BotDashboardModule({
/> />
{lc.enabled} {lc.enabled}
</label> </label>
<button <LucentIconButton
className="btn btn-danger btn-sm wizard-icon-btn" className="btn btn-danger btn-sm wizard-icon-btn"
disabled={isDashboardChannel(channel) || isSavingChannel} disabled={isDashboardChannel(channel) || isSavingChannel}
onClick={() => void removeChannel(channel)} onClick={() => void removeChannel(channel)}
title={lc.remove} tooltip={lc.remove}
aria-label={lc.remove}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
{renderChannelFields(channel, idx)} {renderChannelFields(channel, idx)}
<div className="row-between"> <div className="row-between">
<span className="field-label">{lc.customChannel}</span> <span className="field-label">{lc.customChannel}</span>
<button <LucentIconButton
className="btn btn-primary btn-sm icon-btn" className="btn btn-primary btn-sm icon-btn"
disabled={isSavingChannel} disabled={isSavingChannel}
onClick={() => void saveChannel(channel)} onClick={() => void saveChannel(channel)}
title={lc.saveChannel} tooltip={lc.saveChannel}
aria-label={lc.saveChannel} aria-label={lc.saveChannel}
> >
<Save size={14} /> <Save size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
) )
@ -2840,15 +2837,15 @@ export function BotDashboardModule({
<option key={t} value={t}>{t}</option> <option key={t} value={t}>{t}</option>
))} ))}
</select> </select>
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
disabled={addableChannelTypes.length === 0 || isSavingChannel} disabled={addableChannelTypes.length === 0 || isSavingChannel}
onClick={() => void addChannel()} onClick={() => void addChannel()}
title={lc.addChannel} tooltip={lc.addChannel}
aria-label={lc.addChannel} aria-label={lc.addChannel}
> >
<Plus size={14} /> <Plus size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
</div> </div>
@ -2862,9 +2859,9 @@ export function BotDashboardModule({
<h3>{t.skillsPanel}</h3> <h3>{t.skillsPanel}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowSkillsModal(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowSkillsModal(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="wizard-channel-list"> <div className="wizard-channel-list">
@ -2880,13 +2877,14 @@ export function BotDashboardModule({
<div className="field-label mono">{String(skill.type || '').toUpperCase()}</div> <div className="field-label mono">{String(skill.type || '').toUpperCase()}</div>
<div className="field-label">{skill.description || '-'}</div> <div className="field-label">{skill.description || '-'}</div>
</div> </div>
<button <LucentIconButton
className="btn btn-danger btn-sm wizard-icon-btn" className="btn btn-danger btn-sm wizard-icon-btn"
onClick={() => void removeBotSkill(skill)} onClick={() => void removeBotSkill(skill)}
title={t.removeSkill} tooltip={t.removeSkill}
aria-label={t.removeSkill}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
)) ))
@ -2927,9 +2925,9 @@ export function BotDashboardModule({
<h3>{t.envParams}</h3> <h3>{t.envParams}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowEnvParamsModal(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowEnvParamsModal(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="field-label" style={{ marginBottom: 8 }}>{t.envParamsDesc}</div> <div className="field-label" style={{ marginBottom: 8 }}>{t.envParamsDesc}</div>
@ -2948,21 +2946,22 @@ export function BotDashboardModule({
onChange={(e) => upsertEnvParam(key, e.target.value)} onChange={(e) => upsertEnvParam(key, e.target.value)}
placeholder={t.envValue} placeholder={t.envValue}
/> />
<button <LucentIconButton
className="btn btn-secondary btn-sm wizard-icon-btn" className="btn btn-secondary btn-sm wizard-icon-btn"
onClick={() => setEnvVisibleByKey((prev) => ({ ...prev, [key]: !prev[key] }))} onClick={() => setEnvVisibleByKey((prev) => ({ ...prev, [key]: !prev[key] }))}
title={envVisibleByKey[key] ? t.hideEnvValue : t.showEnvValue} tooltip={envVisibleByKey[key] ? t.hideEnvValue : t.showEnvValue}
aria-label={envVisibleByKey[key] ? t.hideEnvValue : t.showEnvValue} aria-label={envVisibleByKey[key] ? t.hideEnvValue : t.showEnvValue}
> >
{envVisibleByKey[key] ? <EyeOff size={14} /> : <Eye size={14} />} {envVisibleByKey[key] ? <EyeOff size={14} /> : <Eye size={14} />}
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-danger btn-sm wizard-icon-btn" className="btn btn-danger btn-sm wizard-icon-btn"
onClick={() => removeEnvParam(key)} onClick={() => removeEnvParam(key)}
title={t.removeEnvParam} tooltip={t.removeEnvParam}
aria-label={t.removeEnvParam}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
)) ))
@ -2983,15 +2982,15 @@ export function BotDashboardModule({
onChange={(e) => setEnvDraftValue(e.target.value)} onChange={(e) => setEnvDraftValue(e.target.value)}
placeholder={t.envValue} placeholder={t.envValue}
/> />
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => setEnvDraftVisible((v) => !v)} onClick={() => setEnvDraftVisible((v) => !v)}
title={envDraftVisible ? t.hideEnvValue : t.showEnvValue} tooltip={envDraftVisible ? t.hideEnvValue : t.showEnvValue}
aria-label={envDraftVisible ? t.hideEnvValue : t.showEnvValue} aria-label={envDraftVisible ? t.hideEnvValue : t.showEnvValue}
> >
{envDraftVisible ? <EyeOff size={14} /> : <Eye size={14} />} {envDraftVisible ? <EyeOff size={14} /> : <Eye size={14} />}
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => { onClick={() => {
const key = String(envDraftKey || '').trim().toUpperCase(); const key = String(envDraftKey || '').trim().toUpperCase();
@ -3000,11 +2999,11 @@ export function BotDashboardModule({
setEnvDraftKey(''); setEnvDraftKey('');
setEnvDraftValue(''); setEnvDraftValue('');
}} }}
title={t.addEnvParam} tooltip={t.addEnvParam}
aria-label={t.addEnvParam} aria-label={t.addEnvParam}
> >
<Plus size={14} /> <Plus size={14} />
</button> </LucentIconButton>
</div> </div>
<div className="row-between"> <div className="row-between">
<span className="field-label">{t.envParamsHint}</span> <span className="field-label">{t.envParamsHint}</span>
@ -3025,18 +3024,18 @@ export function BotDashboardModule({
<h3>{t.cronViewer}</h3> <h3>{t.cronViewer}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => selectedBot && void loadCronJobs(selectedBot.id)} onClick={() => selectedBot && void loadCronJobs(selectedBot.id)}
title={t.cronReload} tooltip={t.cronReload}
aria-label={t.cronReload} aria-label={t.cronReload}
disabled={cronLoading} disabled={cronLoading}
> >
<RefreshCw size={14} className={cronLoading ? 'animate-spin' : ''} /> <RefreshCw size={14} className={cronLoading ? 'animate-spin' : ''} />
</button> </LucentIconButton>
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowCronModal(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowCronModal(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
{cronLoading ? ( {cronLoading ? (
@ -3067,24 +3066,24 @@ export function BotDashboardModule({
<div className="ops-cron-meta">{job.enabled === false ? t.cronDisabled : t.cronEnabled}</div> <div className="ops-cron-meta">{job.enabled === false ? t.cronDisabled : t.cronEnabled}</div>
</div> </div>
<div className="ops-cron-actions"> <div className="ops-cron-actions">
<button <LucentIconButton
className="btn btn-danger btn-sm icon-btn" className="btn btn-danger btn-sm icon-btn"
onClick={() => void stopCronJob(job.id)} onClick={() => void stopCronJob(job.id)}
title={t.cronStop} tooltip={t.cronStop}
aria-label={t.cronStop} aria-label={t.cronStop}
disabled={stopping || job.enabled === false} disabled={stopping || job.enabled === false}
> >
<PowerOff size={13} /> <PowerOff size={13} />
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-danger btn-sm icon-btn" className="btn btn-danger btn-sm icon-btn"
onClick={() => void deleteCronJob(job.id)} onClick={() => void deleteCronJob(job.id)}
title={t.cronDelete} tooltip={t.cronDelete}
aria-label={t.cronDelete} aria-label={t.cronDelete}
disabled={stopping} disabled={stopping}
> >
<Trash2 size={13} /> <Trash2 size={13} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
); );
@ -3103,9 +3102,9 @@ export function BotDashboardModule({
<h3>{t.agentFiles}</h3> <h3>{t.agentFiles}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowAgentModal(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowAgentModal(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="wizard-agent-layout"> <div className="wizard-agent-layout">
@ -3132,9 +3131,9 @@ export function BotDashboardModule({
<h3>{t.lastAction}</h3> <h3>{t.lastAction}</h3>
</div> </div>
<div className="modal-title-actions"> <div className="modal-title-actions">
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowRuntimeActionModal(false)} title={t.close} aria-label={t.close}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowRuntimeActionModal(false)} tooltip={t.close} aria-label={t.close}>
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className="workspace-preview-body"> <div className="workspace-preview-body">
@ -3153,22 +3152,22 @@ export function BotDashboardModule({
<span className="modal-sub mono">{workspacePreview.path}</span> <span className="modal-sub mono">{workspacePreview.path}</span>
</div> </div>
<div className="workspace-preview-header-actions"> <div className="workspace-preview-header-actions">
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => setWorkspacePreviewFullscreen((v) => !v)} onClick={() => setWorkspacePreviewFullscreen((v) => !v)}
title={workspacePreviewFullscreen ? (isZh ? '退出全屏' : 'Exit full screen') : (isZh ? '全屏预览' : 'Full screen')} tooltip={workspacePreviewFullscreen ? (isZh ? '退出全屏' : 'Exit full screen') : (isZh ? '全屏预览' : 'Full screen')}
aria-label={workspacePreviewFullscreen ? (isZh ? '退出全屏' : 'Exit full screen') : (isZh ? '全屏预览' : 'Full screen')} aria-label={workspacePreviewFullscreen ? (isZh ? '退出全屏' : 'Exit full screen') : (isZh ? '全屏预览' : 'Full screen')}
> >
{workspacePreviewFullscreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />} {workspacePreviewFullscreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={closeWorkspacePreview} onClick={closeWorkspacePreview}
title={t.close} tooltip={t.close}
aria-label={t.close} aria-label={t.close}
> >
<X size={14} /> <X size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
<div className={`workspace-preview-body ${workspacePreview.isMarkdown ? 'markdown' : ''}`}> <div className={`workspace-preview-body ${workspacePreview.isMarkdown ? 'markdown' : ''}`}>

View File

@ -7,6 +7,7 @@ import { pickLocale } from '../../i18n';
import { imageFactoryZhCn } from '../../i18n/image-factory.zh-cn'; import { imageFactoryZhCn } from '../../i18n/image-factory.zh-cn';
import { imageFactoryEn } from '../../i18n/image-factory.en'; import { imageFactoryEn } from '../../i18n/image-factory.en';
import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider'; import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider';
import { LucentIconButton } from '../../components/lucent/LucentIconButton';
interface NanobotImage { interface NanobotImage {
tag: string; tag: string;
@ -178,15 +179,15 @@ export function ImageFactoryModule() {
<td>{img.version}</td> <td>{img.version}</td>
<td><span className={statusClass(img.status)}>{img.status}</span></td> <td><span className={statusClass(img.status)}>{img.status}</span></td>
<td> <td>
<button <LucentIconButton
className="btn btn-danger btn-sm icon-btn" className="btn btn-danger btn-sm icon-btn"
disabled={isDeletingTag === img.tag} disabled={isDeletingTag === img.tag}
onClick={() => void handleDeleteRegistered(img.tag)} onClick={() => void handleDeleteRegistered(img.tag)}
title={isDeletingTag === img.tag ? t.deleting : t.deleteRegistry} tooltip={isDeletingTag === img.tag ? t.deleting : t.deleteRegistry}
aria-label={isDeletingTag === img.tag ? t.deleting : t.deleteRegistry} aria-label={isDeletingTag === img.tag ? t.deleting : t.deleteRegistry}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </LucentIconButton>
</td> </td>
</tr> </tr>
))} ))}
@ -202,14 +203,14 @@ export function ImageFactoryModule() {
<h2>{t.dockerTitle}</h2> <h2>{t.dockerTitle}</h2>
<p className="panel-desc">{t.dockerDesc}</p> <p className="panel-desc">{t.dockerDesc}</p>
</div> </div>
<button <LucentIconButton
className="btn btn-secondary icon-btn" className="btn btn-secondary icon-btn"
onClick={() => void refreshDockerImages()} onClick={() => void refreshDockerImages()}
title={isRefreshing ? t.refreshing : t.refresh} tooltip={isRefreshing ? t.refreshing : t.refresh}
aria-label={isRefreshing ? t.refreshing : t.refresh} aria-label={isRefreshing ? t.refreshing : t.refresh}
> >
<RefreshCw size={14} className={isRefreshing ? 'animate-spin' : ''} /> <RefreshCw size={14} className={isRefreshing ? 'animate-spin' : ''} />
</button> </LucentIconButton>
</div> </div>
<div className="card" style={{ fontSize: 12, color: 'var(--muted)' }}> <div className="card" style={{ fontSize: 12, color: 'var(--muted)' }}>

View File

@ -7,6 +7,7 @@ import { pickLocale } from '../../../i18n';
import { managementZhCn } from '../../../i18n/management.zh-cn'; import { managementZhCn } from '../../../i18n/management.zh-cn';
import { managementEn } from '../../../i18n/management.en'; import { managementEn } from '../../../i18n/management.en';
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider'; import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider';
import { LucentIconButton } from '../../../components/lucent/LucentIconButton';
interface KernelManagerModalProps { interface KernelManagerModalProps {
isOpen: boolean; isOpen: boolean;
@ -65,9 +66,9 @@ export function KernelManagerModal({ isOpen, onClose }: KernelManagerModalProps)
<Cpu className="text-blue-400" size={24} /> <Cpu className="text-blue-400" size={24} />
<h2 className="text-xl font-bold text-white">{t.title}</h2> <h2 className="text-xl font-bold text-white">{t.title}</h2>
</div> </div>
<button onClick={onClose} className="p-2 hover:bg-slate-700 rounded-full transition-colors text-white"> <LucentIconButton onClick={onClose} className="p-2 hover:bg-slate-700 rounded-full transition-colors text-white" tooltip={locale === 'zh' ? '关闭' : 'Close'} aria-label={locale === 'zh' ? '关闭' : 'Close'}>
<X size={20} /> <X size={20} />
</button> </LucentIconButton>
</div> </div>
<div className="p-6"> <div className="p-6">
@ -87,13 +88,14 @@ export function KernelManagerModal({ isOpen, onClose }: KernelManagerModalProps)
<span className={`text-[9px] font-bold ${img.status === 'READY' ? 'text-green-500' : 'text-slate-400'}`}> <span className={`text-[9px] font-bold ${img.status === 'READY' ? 'text-green-500' : 'text-slate-400'}`}>
{img.status} {img.status}
</span> </span>
<button <LucentIconButton
onClick={() => handleRemoveImage(img.tag)} onClick={() => handleRemoveImage(img.tag)}
className="ml-2 p-1.5 hover:bg-red-500/20 text-slate-500 hover:text-red-500 rounded transition-colors" className="ml-2 p-1.5 hover:bg-red-500/20 text-slate-500 hover:text-red-500 rounded transition-colors"
title={t.removeRecord} tooltip={t.removeRecord}
aria-label={t.removeRecord}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
))} ))}

View File

@ -9,6 +9,7 @@ import { pickLocale } from '../../i18n';
import { wizardZhCn } from '../../i18n/wizard.zh-cn'; import { wizardZhCn } from '../../i18n/wizard.zh-cn';
import { wizardEn } from '../../i18n/wizard.en'; import { wizardEn } from '../../i18n/wizard.en';
import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider'; import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider';
import { LucentIconButton } from '../../components/lucent/LucentIconButton';
type AgentTab = 'AGENTS' | 'SOUL' | 'USER' | 'TOOLS' | 'IDENTITY'; type AgentTab = 'AGENTS' | 'SOUL' | 'USER' | 'TOOLS' | 'IDENTITY';
type ChannelType = 'feishu' | 'qq' | 'dingtalk' | 'telegram' | 'slack'; type ChannelType = 'feishu' | 'qq' | 'dingtalk' | 'telegram' | 'slack';
@ -698,9 +699,9 @@ export function BotWizardModule({ onCreated, onGoDashboard }: BotWizardModulePro
<div className="mono"> <div className="mono">
{configuredChannelsLabel} {configuredChannelsLabel}
</div> </div>
<button className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowChannelModal(true)} title={lc.openManager} aria-label={lc.openManager}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" onClick={() => setShowChannelModal(true)} tooltip={lc.openManager} aria-label={lc.openManager}>
<Settings2 size={14} /> <Settings2 size={14} />
</button> </LucentIconButton>
</div> </div>
<div className="section-mini-title" style={{ marginTop: 6 }}>{ui.toolsConfig}</div> <div className="section-mini-title" style={{ marginTop: 6 }}>{ui.toolsConfig}</div>
@ -709,14 +710,14 @@ export function BotWizardModule({ onCreated, onGoDashboard }: BotWizardModulePro
<div className="mono"> <div className="mono">
{envEntries.length > 0 ? envEntries.map(([k]) => k).join(', ') : ui.noEnvParams} {envEntries.length > 0 ? envEntries.map(([k]) => k).join(', ') : ui.noEnvParams}
</div> </div>
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => setShowToolsConfigModal(true)} onClick={() => setShowToolsConfigModal(true)}
title={ui.openToolsManager} tooltip={ui.openToolsManager}
aria-label={ui.openToolsManager} aria-label={ui.openToolsManager}
> >
<Settings2 size={14} /> <Settings2 size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
</div> </div>
@ -825,13 +826,14 @@ export function BotWizardModule({ onCreated, onGoDashboard }: BotWizardModulePro
/> />
{lc.enabled} {lc.enabled}
</label> </label>
<button <LucentIconButton
className="btn btn-danger btn-sm wizard-icon-btn" className="btn btn-danger btn-sm wizard-icon-btn"
onClick={() => removeChannel(idx)} onClick={() => removeChannel(idx)}
title={lc.remove} tooltip={lc.remove}
aria-label={lc.remove}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
@ -846,9 +848,9 @@ export function BotWizardModule({ onCreated, onGoDashboard }: BotWizardModulePro
<option key={t} value={t}>{t}</option> <option key={t} value={t}>{t}</option>
))} ))}
</select> </select>
<button className="btn btn-secondary btn-sm icon-btn" disabled={addableChannelTypes.length === 0} onClick={addChannel} title={lc.addChannel} aria-label={lc.addChannel}> <LucentIconButton className="btn btn-secondary btn-sm icon-btn" disabled={addableChannelTypes.length === 0} onClick={addChannel} tooltip={lc.addChannel} aria-label={lc.addChannel}>
<Plus size={14} /> <Plus size={14} />
</button> </LucentIconButton>
</div> </div>
<div className="row-between"> <div className="row-between">
<span className="field-label">{lc.wizardSectionDesc}</span> <span className="field-label">{lc.wizardSectionDesc}</span>
@ -883,21 +885,22 @@ export function BotWizardModule({ onCreated, onGoDashboard }: BotWizardModulePro
onChange={(e) => upsertEnvParam(key, e.target.value)} onChange={(e) => upsertEnvParam(key, e.target.value)}
placeholder={ui.envValue} placeholder={ui.envValue}
/> />
<button <LucentIconButton
className="btn btn-secondary btn-sm wizard-icon-btn" className="btn btn-secondary btn-sm wizard-icon-btn"
onClick={() => setEnvVisibleByKey((prev) => ({ ...prev, [key]: !prev[key] }))} onClick={() => setEnvVisibleByKey((prev) => ({ ...prev, [key]: !prev[key] }))}
title={envVisibleByKey[key] ? ui.hideEnvValue : ui.showEnvValue} tooltip={envVisibleByKey[key] ? ui.hideEnvValue : ui.showEnvValue}
aria-label={envVisibleByKey[key] ? ui.hideEnvValue : ui.showEnvValue} aria-label={envVisibleByKey[key] ? ui.hideEnvValue : ui.showEnvValue}
> >
{envVisibleByKey[key] ? <EyeOff size={14} /> : <Eye size={14} />} {envVisibleByKey[key] ? <EyeOff size={14} /> : <Eye size={14} />}
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-danger btn-sm wizard-icon-btn" className="btn btn-danger btn-sm wizard-icon-btn"
onClick={() => removeEnvParam(key)} onClick={() => removeEnvParam(key)}
title={ui.removeEnvParam} tooltip={ui.removeEnvParam}
aria-label={ui.removeEnvParam}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </LucentIconButton>
</div> </div>
</div> </div>
)) ))
@ -917,15 +920,15 @@ export function BotWizardModule({ onCreated, onGoDashboard }: BotWizardModulePro
onChange={(e) => setEnvDraftValue(e.target.value)} onChange={(e) => setEnvDraftValue(e.target.value)}
placeholder={ui.envValue} placeholder={ui.envValue}
/> />
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => setEnvDraftVisible((v) => !v)} onClick={() => setEnvDraftVisible((v) => !v)}
title={envDraftVisible ? ui.hideEnvValue : ui.showEnvValue} tooltip={envDraftVisible ? ui.hideEnvValue : ui.showEnvValue}
aria-label={envDraftVisible ? ui.hideEnvValue : ui.showEnvValue} aria-label={envDraftVisible ? ui.hideEnvValue : ui.showEnvValue}
> >
{envDraftVisible ? <EyeOff size={14} /> : <Eye size={14} />} {envDraftVisible ? <EyeOff size={14} /> : <Eye size={14} />}
</button> </LucentIconButton>
<button <LucentIconButton
className="btn btn-secondary btn-sm icon-btn" className="btn btn-secondary btn-sm icon-btn"
onClick={() => { onClick={() => {
const key = String(envDraftKey || '').trim().toUpperCase(); const key = String(envDraftKey || '').trim().toUpperCase();
@ -934,11 +937,11 @@ export function BotWizardModule({ onCreated, onGoDashboard }: BotWizardModulePro
setEnvDraftKey(''); setEnvDraftKey('');
setEnvDraftValue(''); setEnvDraftValue('');
}} }}
title={ui.addEnvParam} tooltip={ui.addEnvParam}
aria-label={ui.addEnvParam} aria-label={ui.addEnvParam}
> >
<Plus size={14} /> <Plus size={14} />
</button> </LucentIconButton>
</div> </div>
<div className="row-between"> <div className="row-between">
<span className="field-label">{ui.toolsDesc}</span> <span className="field-label">{ui.toolsDesc}</span>