v0.1.4
parent
84010e33ac
commit
d99ab859ca
|
|
@ -95,7 +95,7 @@ class BotConfigManager:
|
||||||
"token": secret,
|
"token": secret,
|
||||||
"proxy": extra.get("proxy", ""),
|
"proxy": extra.get("proxy", ""),
|
||||||
"replyToMessage": bool(extra.get("replyToMessage", False)),
|
"replyToMessage": bool(extra.get("replyToMessage", False)),
|
||||||
"allowFrom": extra.get("allowFrom", []),
|
"allowFrom": self._normalize_allow_from(extra.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ class BotConfigManager:
|
||||||
"appSecret": secret,
|
"appSecret": secret,
|
||||||
"encryptKey": extra.get("encryptKey", ""),
|
"encryptKey": extra.get("encryptKey", ""),
|
||||||
"verificationToken": extra.get("verificationToken", ""),
|
"verificationToken": extra.get("verificationToken", ""),
|
||||||
"allowFrom": extra.get("allowFrom", []),
|
"allowFrom": self._normalize_allow_from(extra.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ class BotConfigManager:
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
"clientId": external,
|
"clientId": external,
|
||||||
"clientSecret": secret,
|
"clientSecret": secret,
|
||||||
"allowFrom": extra.get("allowFrom", []),
|
"allowFrom": self._normalize_allow_from(extra.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ class BotConfigManager:
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
"appId": external,
|
"appId": external,
|
||||||
"secret": secret,
|
"secret": secret,
|
||||||
"allowFrom": extra.get("allowFrom", []),
|
"allowFrom": self._normalize_allow_from(extra.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -167,3 +167,15 @@ class BotConfigManager:
|
||||||
f.write(str(content).strip() + "\n")
|
f.write(str(content).strip() + "\n")
|
||||||
|
|
||||||
return dot_nanobot_dir
|
return dot_nanobot_dir
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize_allow_from(raw: Any) -> List[str]:
|
||||||
|
rows: List[str] = []
|
||||||
|
if isinstance(raw, list):
|
||||||
|
for item in raw:
|
||||||
|
text = str(item or "").strip()
|
||||||
|
if text and text not in rows:
|
||||||
|
rows.append(text)
|
||||||
|
if not rows:
|
||||||
|
return ["*"]
|
||||||
|
return rows
|
||||||
|
|
|
||||||
|
|
@ -615,6 +615,18 @@ def _normalize_channel_extra(raw: Any) -> Dict[str, Any]:
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_allow_from(raw: Any) -> List[str]:
|
||||||
|
rows: List[str] = []
|
||||||
|
if isinstance(raw, list):
|
||||||
|
for item in raw:
|
||||||
|
text = str(item or "").strip()
|
||||||
|
if text and text not in rows:
|
||||||
|
rows.append(text)
|
||||||
|
if not rows:
|
||||||
|
return ["*"]
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def _read_global_delivery_flags(channels_cfg: Any) -> tuple[bool, bool]:
|
def _read_global_delivery_flags(channels_cfg: Any) -> tuple[bool, bool]:
|
||||||
if not isinstance(channels_cfg, dict):
|
if not isinstance(channels_cfg, dict):
|
||||||
return False, False
|
return False, False
|
||||||
|
|
@ -643,18 +655,18 @@ def _channel_cfg_to_api_dict(bot_id: str, ctype: str, cfg: Dict[str, Any]) -> Di
|
||||||
extra = {
|
extra = {
|
||||||
"encryptKey": cfg.get("encryptKey", ""),
|
"encryptKey": cfg.get("encryptKey", ""),
|
||||||
"verificationToken": cfg.get("verificationToken", ""),
|
"verificationToken": cfg.get("verificationToken", ""),
|
||||||
"allowFrom": cfg.get("allowFrom", []),
|
"allowFrom": _normalize_allow_from(cfg.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
elif ctype == "dingtalk":
|
elif ctype == "dingtalk":
|
||||||
external_app_id = str(cfg.get("clientId") or "")
|
external_app_id = str(cfg.get("clientId") or "")
|
||||||
app_secret = str(cfg.get("clientSecret") or "")
|
app_secret = str(cfg.get("clientSecret") or "")
|
||||||
extra = {"allowFrom": cfg.get("allowFrom", [])}
|
extra = {"allowFrom": _normalize_allow_from(cfg.get("allowFrom", []))}
|
||||||
elif ctype == "telegram":
|
elif ctype == "telegram":
|
||||||
app_secret = str(cfg.get("token") or "")
|
app_secret = str(cfg.get("token") or "")
|
||||||
extra = {
|
extra = {
|
||||||
"proxy": cfg.get("proxy", ""),
|
"proxy": cfg.get("proxy", ""),
|
||||||
"replyToMessage": bool(cfg.get("replyToMessage", False)),
|
"replyToMessage": bool(cfg.get("replyToMessage", False)),
|
||||||
"allowFrom": cfg.get("allowFrom", []),
|
"allowFrom": _normalize_allow_from(cfg.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
elif ctype == "slack":
|
elif ctype == "slack":
|
||||||
external_app_id = str(cfg.get("botToken") or "")
|
external_app_id = str(cfg.get("botToken") or "")
|
||||||
|
|
@ -669,7 +681,7 @@ def _channel_cfg_to_api_dict(bot_id: str, ctype: str, cfg: Dict[str, Any]) -> Di
|
||||||
elif ctype == "qq":
|
elif ctype == "qq":
|
||||||
external_app_id = str(cfg.get("appId") or "")
|
external_app_id = str(cfg.get("appId") or "")
|
||||||
app_secret = str(cfg.get("secret") or "")
|
app_secret = str(cfg.get("secret") or "")
|
||||||
extra = {"allowFrom": cfg.get("allowFrom", [])}
|
extra = {"allowFrom": _normalize_allow_from(cfg.get("allowFrom", []))}
|
||||||
else:
|
else:
|
||||||
external_app_id = str(
|
external_app_id = str(
|
||||||
cfg.get("appId") or cfg.get("clientId") or cfg.get("botToken") or cfg.get("externalAppId") or ""
|
cfg.get("appId") or cfg.get("clientId") or cfg.get("botToken") or cfg.get("externalAppId") or ""
|
||||||
|
|
@ -707,14 +719,14 @@ def _channel_api_to_cfg(row: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"appSecret": app_secret,
|
"appSecret": app_secret,
|
||||||
"encryptKey": extra.get("encryptKey", ""),
|
"encryptKey": extra.get("encryptKey", ""),
|
||||||
"verificationToken": extra.get("verificationToken", ""),
|
"verificationToken": extra.get("verificationToken", ""),
|
||||||
"allowFrom": extra.get("allowFrom", []),
|
"allowFrom": _normalize_allow_from(extra.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
if ctype == "dingtalk":
|
if ctype == "dingtalk":
|
||||||
return {
|
return {
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
"clientId": external_app_id,
|
"clientId": external_app_id,
|
||||||
"clientSecret": app_secret,
|
"clientSecret": app_secret,
|
||||||
"allowFrom": extra.get("allowFrom", []),
|
"allowFrom": _normalize_allow_from(extra.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
if ctype == "telegram":
|
if ctype == "telegram":
|
||||||
return {
|
return {
|
||||||
|
|
@ -722,7 +734,7 @@ def _channel_api_to_cfg(row: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"token": app_secret,
|
"token": app_secret,
|
||||||
"proxy": extra.get("proxy", ""),
|
"proxy": extra.get("proxy", ""),
|
||||||
"replyToMessage": bool(extra.get("replyToMessage", False)),
|
"replyToMessage": bool(extra.get("replyToMessage", False)),
|
||||||
"allowFrom": extra.get("allowFrom", []),
|
"allowFrom": _normalize_allow_from(extra.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
if ctype == "slack":
|
if ctype == "slack":
|
||||||
return {
|
return {
|
||||||
|
|
@ -740,7 +752,7 @@ def _channel_api_to_cfg(row: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
"appId": external_app_id,
|
"appId": external_app_id,
|
||||||
"secret": app_secret,
|
"secret": app_secret,
|
||||||
"allowFrom": extra.get("allowFrom", []),
|
"allowFrom": _normalize_allow_from(extra.get("allowFrom", [])),
|
||||||
}
|
}
|
||||||
merged = dict(extra)
|
merged = dict(extra)
|
||||||
merged.update(
|
merged.update(
|
||||||
|
|
|
||||||
|
|
@ -79,12 +79,43 @@ body {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-header-actions {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header-collapsible {
|
||||||
|
transition: padding 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header-collapsible.is-collapsed {
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header-collapsible.is-collapsed .app-header-top {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header-toggle {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.app-title {
|
.app-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-title-main {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.app-title-icon {
|
.app-title-icon {
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
|
@ -109,6 +140,23 @@ body {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-header-toggle-inline {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--icon);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header-toggle-inline:hover {
|
||||||
|
color: var(--brand);
|
||||||
|
}
|
||||||
|
|
||||||
.global-switches {
|
.global-switches {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect, useMemo, useState, type ReactElement } from 'react';
|
import { useEffect, useMemo, useState, type ReactElement } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { MoonStar, SunMedium, X } from 'lucide-react';
|
import { ChevronDown, ChevronUp, MoonStar, SunMedium, X } from 'lucide-react';
|
||||||
import { useAppStore } from './store/appStore';
|
import { useAppStore } from './store/appStore';
|
||||||
import { useBotsSync } from './hooks/useBotsSync';
|
import { useBotsSync } from './hooks/useBotsSync';
|
||||||
import { APP_ENDPOINTS } from './config/env';
|
import { APP_ENDPOINTS } from './config/env';
|
||||||
|
|
@ -27,6 +27,8 @@ function AuthenticatedApp({
|
||||||
const [showCreateWizard, setShowCreateWizard] = useState(false);
|
const [showCreateWizard, setShowCreateWizard] = useState(false);
|
||||||
useBotsSync();
|
useBotsSync();
|
||||||
const t = pickLocale(locale, { 'zh-cn': appZhCn, en: appEn });
|
const t = pickLocale(locale, { 'zh-cn': appZhCn, en: appEn });
|
||||||
|
const isSingleBotCompactView = compactMode && Boolean(String(forcedBotId || '').trim());
|
||||||
|
const [headerCollapsed, setHeaderCollapsed] = useState(isSingleBotCompactView);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const forced = String(forcedBotId || '').trim();
|
const forced = String(forcedBotId || '').trim();
|
||||||
|
|
@ -39,19 +41,44 @@ function AuthenticatedApp({
|
||||||
document.title = botName ? `${t.title} - ${botName}` : `${t.title} - ${forced}`;
|
document.title = botName ? `${t.title} - ${botName}` : `${t.title} - ${forced}`;
|
||||||
}, [activeBots, t.title, forcedBotId]);
|
}, [activeBots, t.title, forcedBotId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHeaderCollapsed(isSingleBotCompactView);
|
||||||
|
}, [isSingleBotCompactView, forcedBotId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`app-shell ${compactMode ? 'app-shell-compact' : ''}`} data-theme={theme}>
|
<div className={`app-shell ${compactMode ? 'app-shell-compact' : ''}`} data-theme={theme}>
|
||||||
<div className="app-frame">
|
<div className="app-frame">
|
||||||
<header className="app-header">
|
<header
|
||||||
|
className={`app-header ${isSingleBotCompactView ? 'app-header-collapsible' : ''} ${isSingleBotCompactView && headerCollapsed ? 'is-collapsed' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSingleBotCompactView && headerCollapsed) setHeaderCollapsed(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="row-between app-header-top">
|
<div className="row-between app-header-top">
|
||||||
<div className="app-title">
|
<div className="app-title">
|
||||||
<img src="/app-bot-icon.svg" alt="Nanobot" className="app-title-icon" />
|
<img src="/app-bot-icon.svg" alt="Nanobot" className="app-title-icon" />
|
||||||
<div>
|
<div className="app-title-main">
|
||||||
<h1>{t.title}</h1>
|
<h1>{t.title}</h1>
|
||||||
|
{isSingleBotCompactView ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="app-header-toggle-inline"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setHeaderCollapsed((v) => !v);
|
||||||
|
}}
|
||||||
|
title={headerCollapsed ? t.expandHeader : t.collapseHeader}
|
||||||
|
aria-label={headerCollapsed ? t.expandHeader : t.collapseHeader}
|
||||||
|
>
|
||||||
|
{headerCollapsed ? <ChevronDown size={16} /> : <ChevronUp size={16} />}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="global-switches">
|
<div className="app-header-actions">
|
||||||
|
{!headerCollapsed ? (
|
||||||
|
<div className="global-switches">
|
||||||
<div className="switch-compact">
|
<div className="switch-compact">
|
||||||
<LucentTooltip content={t.dark}>
|
<LucentTooltip content={t.dark}>
|
||||||
<button
|
<button
|
||||||
|
|
@ -93,6 +120,8 @@ function AuthenticatedApp({
|
||||||
</button>
|
</button>
|
||||||
</LucentTooltip>
|
</LucentTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ export const appEn = {
|
||||||
zh: 'Chinese',
|
zh: 'Chinese',
|
||||||
en: 'English',
|
en: 'English',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
|
expandHeader: 'Expand header',
|
||||||
|
collapseHeader: 'Collapse header',
|
||||||
nav: {
|
nav: {
|
||||||
images: { title: 'Image Factory', subtitle: 'Manage registered images' },
|
images: { title: 'Image Factory', subtitle: 'Manage registered images' },
|
||||||
onboarding: { title: 'Creation Wizard', subtitle: 'Create bot step-by-step' },
|
onboarding: { title: 'Creation Wizard', subtitle: 'Create bot step-by-step' },
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ export const appZhCn = {
|
||||||
zh: '中文',
|
zh: '中文',
|
||||||
en: 'English',
|
en: 'English',
|
||||||
close: '关闭',
|
close: '关闭',
|
||||||
|
expandHeader: '展开头部',
|
||||||
|
collapseHeader: '收起头部',
|
||||||
nav: {
|
nav: {
|
||||||
images: { title: '镜像工厂', subtitle: '管理已登记镜像' },
|
images: { title: '镜像工厂', subtitle: '管理已登记镜像' },
|
||||||
onboarding: { title: '创建向导', subtitle: '分步创建 Bot' },
|
onboarding: { title: '创建向导', subtitle: '分步创建 Bot' },
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ export const dashboardEn = {
|
||||||
stop: 'Stop',
|
stop: 'Stop',
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
restart: 'Restart Bot',
|
restart: 'Restart Bot',
|
||||||
|
restartConfirm: (id: string) => `Restart bot ${id}?`,
|
||||||
restartFail: 'Restart failed. Check backend logs.',
|
restartFail: 'Restart failed. Check backend logs.',
|
||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
noConversation: 'No conversation yet. Send a command and bot replies will appear here.',
|
noConversation: 'No conversation yet. Send a command and bot replies will appear here.',
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ export const dashboardZhCn = {
|
||||||
stop: '停止',
|
stop: '停止',
|
||||||
start: '启动',
|
start: '启动',
|
||||||
restart: '重启 Bot',
|
restart: '重启 Bot',
|
||||||
|
restartConfirm: (id: string) => `确认重启 Bot ${id}?`,
|
||||||
restartFail: '重启失败,请查看后端日志。',
|
restartFail: '重启失败,请查看后端日志。',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
noConversation: '暂无对话消息。请先发送指令,Bot 回复会在这里按标准会话格式展示。',
|
noConversation: '暂无对话消息。请先发送指令,Bot 回复会在这里按标准会话格式展示。',
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 14px;
|
right: 14px;
|
||||||
bottom: 14px;
|
bottom: 14px;
|
||||||
width: 42px;
|
width: 48px;
|
||||||
height: 42px;
|
height: 48px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
border: 1px solid color-mix(in oklab, var(--brand) 58%, var(--line) 42%);
|
border: 1px solid color-mix(in oklab, var(--brand) 58%, var(--line) 42%);
|
||||||
background: color-mix(in oklab, var(--panel) 70%, var(--brand-soft) 30%);
|
background: color-mix(in oklab, var(--panel) 70%, var(--brand-soft) 30%);
|
||||||
|
|
@ -30,9 +30,83 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 10px 24px rgba(9, 15, 28, 0.35);
|
box-shadow:
|
||||||
|
0 10px 24px rgba(9, 15, 28, 0.42),
|
||||||
|
0 0 0 2px color-mix(in oklab, var(--brand) 22%, transparent);
|
||||||
z-index: 85;
|
z-index: 85;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
overflow: visible;
|
||||||
|
transform: translateY(0);
|
||||||
|
animation: ops-fab-float 2.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-compact-fab-switch::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -7px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 2px solid color-mix(in oklab, var(--brand) 60%, transparent);
|
||||||
|
opacity: 0.45;
|
||||||
|
animation: ops-fab-pulse 1.8s ease-out infinite;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-compact-fab-switch::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -1px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 50%, color-mix(in oklab, var(--brand) 20%, transparent) 0%, transparent 72%);
|
||||||
|
opacity: 0.9;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-compact-fab-switch.is-chat {
|
||||||
|
box-shadow:
|
||||||
|
0 10px 24px rgba(9, 15, 28, 0.42),
|
||||||
|
0 0 18px color-mix(in oklab, #5c98ff 60%, transparent),
|
||||||
|
0 0 0 2px color-mix(in oklab, #5c98ff 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-compact-fab-switch.is-runtime {
|
||||||
|
box-shadow:
|
||||||
|
0 10px 24px rgba(9, 15, 28, 0.42),
|
||||||
|
0 0 18px color-mix(in oklab, #40d6c3 62%, transparent),
|
||||||
|
0 0 0 2px color-mix(in oklab, #40d6c3 38%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-compact-fab-switch.is-runtime::before {
|
||||||
|
border-color: color-mix(in oklab, #40d6c3 62%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-compact-fab-switch:hover {
|
||||||
|
transform: translateY(-1px) scale(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ops-fab-pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.92);
|
||||||
|
opacity: 0.62;
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
transform: scale(1.15);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1.15);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ops-fab-float {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-list-actions {
|
.ops-list-actions {
|
||||||
|
|
@ -549,22 +623,75 @@
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-chat-bubble.assistant {
|
.ops-chat-bubble.assistant {
|
||||||
|
--bubble-bg: color-mix(in oklab, var(--panel-soft) 82%, var(--panel) 18%);
|
||||||
|
--bubble-border: #3661aa;
|
||||||
border-color: #3661aa;
|
border-color: #3661aa;
|
||||||
background: color-mix(in oklab, var(--panel-soft) 82%, var(--panel) 18%);
|
background: var(--bubble-bg);
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-chat-bubble.assistant.progress {
|
.ops-chat-bubble.assistant.progress {
|
||||||
|
--bubble-bg: color-mix(in oklab, var(--brand-soft) 35%, var(--panel-soft) 65%);
|
||||||
|
--bubble-border: color-mix(in oklab, var(--brand) 55%, var(--line) 45%);
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
border-color: color-mix(in oklab, var(--brand) 55%, var(--line) 45%);
|
border-color: color-mix(in oklab, var(--brand) 55%, var(--line) 45%);
|
||||||
background: color-mix(in oklab, var(--brand-soft) 35%, var(--panel-soft) 65%);
|
background: var(--bubble-bg);
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-chat-bubble.user {
|
.ops-chat-bubble.user {
|
||||||
|
--bubble-bg: color-mix(in oklab, #d9fff0 36%, var(--panel-soft) 64%);
|
||||||
|
--bubble-border: #2f8f7f;
|
||||||
border-color: #2f8f7f;
|
border-color: #2f8f7f;
|
||||||
background: color-mix(in oklab, #d9fff0 36%, var(--panel-soft) 64%);
|
background: var(--bubble-bg);
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-chat-bubble.assistant::before,
|
||||||
|
.ops-chat-bubble.assistant::after,
|
||||||
|
.ops-chat-bubble.user::before,
|
||||||
|
.ops-chat-bubble.user::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-chat-bubble.assistant::before {
|
||||||
|
left: -9px;
|
||||||
|
bottom: 8px;
|
||||||
|
border-top: 8px solid transparent;
|
||||||
|
border-bottom: 8px solid transparent;
|
||||||
|
border-right: 9px solid var(--bubble-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-chat-bubble.assistant::after {
|
||||||
|
left: -7px;
|
||||||
|
bottom: 9px;
|
||||||
|
border-top: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid transparent;
|
||||||
|
border-right: 8px solid var(--bubble-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-chat-bubble.user::before {
|
||||||
|
right: -9px;
|
||||||
|
top: 8px;
|
||||||
|
border-top: 8px solid transparent;
|
||||||
|
border-bottom: 8px solid transparent;
|
||||||
|
border-left: 9px solid var(--bubble-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-chat-bubble.user::after {
|
||||||
|
right: -7px;
|
||||||
|
top: 9px;
|
||||||
|
border-top: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid transparent;
|
||||||
|
border-left: 8px solid var(--bubble-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-chat-meta {
|
.ops-chat-meta {
|
||||||
|
|
@ -748,7 +875,8 @@
|
||||||
|
|
||||||
.ops-avatar.bot {
|
.ops-avatar.bot {
|
||||||
background: #102d63;
|
background: #102d63;
|
||||||
border-color: #4e70ad;
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ops-avatar.bot img {
|
.ops-avatar.bot img {
|
||||||
|
|
@ -763,6 +891,10 @@
|
||||||
color: #e9f2ff;
|
color: #e9f2ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ops-chat-row.is-user .ops-avatar.user {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.ops-chat-empty {
|
.ops-chat-empty {
|
||||||
border: 1px dashed var(--line);
|
border: 1px dashed var(--line);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
@ -2081,13 +2213,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-shell[data-theme='light'] .ops-chat-bubble.assistant {
|
.app-shell[data-theme='light'] .ops-chat-bubble.assistant {
|
||||||
background: #eaf1ff;
|
--bubble-bg: #eaf1ff;
|
||||||
border-color: #a9c1ee;
|
--bubble-border: #a9c1ee;
|
||||||
|
background: var(--bubble-bg);
|
||||||
|
border-color: var(--bubble-border);
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-shell[data-theme='light'] .ops-chat-bubble.user {
|
.app-shell[data-theme='light'] .ops-chat-bubble.user {
|
||||||
background: #e8f6f2;
|
--bubble-bg: #e8f6f2;
|
||||||
border-color: #9ccfc2;
|
--bubble-border: #9ccfc2;
|
||||||
|
background: var(--bubble-bg);
|
||||||
|
border-color: var(--bubble-border);
|
||||||
|
border-top-right-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-shell[data-theme='light'] .ops-avatar.bot {
|
.app-shell[data-theme='light'] .ops-avatar.bot {
|
||||||
|
|
|
||||||
|
|
@ -2071,6 +2071,12 @@ export function BotDashboardModule({
|
||||||
|
|
||||||
const restartBot = async (id: string, status: string) => {
|
const restartBot = async (id: string, status: string) => {
|
||||||
const normalized = String(status || '').toUpperCase();
|
const normalized = String(status || '').toUpperCase();
|
||||||
|
const ok = await confirm({
|
||||||
|
title: t.restart,
|
||||||
|
message: t.restartConfirm(id),
|
||||||
|
tone: 'warning',
|
||||||
|
});
|
||||||
|
if (!ok) return;
|
||||||
setOperatingBotId(id);
|
setOperatingBotId(id);
|
||||||
try {
|
try {
|
||||||
if (normalized === 'RUNNING') {
|
if (normalized === 'RUNNING') {
|
||||||
|
|
@ -3366,7 +3372,7 @@ export function BotDashboardModule({
|
||||||
</div>
|
</div>
|
||||||
{compactMode && isCompactMobile ? (
|
{compactMode && isCompactMobile ? (
|
||||||
<LucentIconButton
|
<LucentIconButton
|
||||||
className="ops-compact-fab-switch"
|
className={`ops-compact-fab-switch ${compactPanelTab === 'chat' ? 'is-chat' : 'is-runtime'}`}
|
||||||
onClick={() => setCompactPanelTab((v) => (v === 'chat' ? 'runtime' : 'chat'))}
|
onClick={() => setCompactPanelTab((v) => (v === 'chat' ? 'runtime' : 'chat'))}
|
||||||
tooltip={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')}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue