import { useEffect, useState } from "react";
import {
ExternalLink,
Info,
Keyboard,
Monitor,
Puzzle,
Server,
Shield,
Sparkles,
Table,
Zap,
type LucideIcon,
} from "lucide-react";
import { driverStatus, health, type DriverStatusItem } from "./api";
import { OpsModal } from "./OpsModal";
import { AiSettings } from "./AiPanel";
import { McpSettings } from "./McpSettings";
import { DriverManager } from "./DriverManager";
import appPackage from "../package.json";
import { useWorkspaceStore } from "./store";
import { openExternal } from "./openExternal";
const IS_MAC = typeof navigator !== "undefined" && /Mac/i.test(navigator.platform);
const MOD = IS_MAC ? "⌘" : "Ctrl";
const WECHAT_GROUP_URL = "https://docs.qq.com/doc/DTFpxcndud0xPd1hX";
const DRIVER_STORE_URL = "https://gitee.com/attacker/opsxdb-driver";
type TabId = "general" | "execution" | "results" | "ai" | "mcp" | "security" | "drivers" | "shortcuts" | "about";
const TABS: Array<{ id: TabId; icon: LucideIcon; label: string; desc: string }> = [
{ id: "general", icon: Monitor, label: "常规", desc: "主题与界面" },
{ id: "execution", icon: Zap, label: "执行", desc: "超时与多语句" },
{ id: "results", icon: Table, label: "结果", desc: "面板与分页" },
{ id: "ai", icon: Sparkles, label: "AI 助手", desc: "模型、API Key 与连接测试" },
{ id: "mcp", icon: Server, label: "MCP", desc: "安装检测、只读与客户端配置" },
{ id: "security", icon: Shield, label: "安全与数据", desc: "本机存储与执行保护" },
{ id: "drivers", icon: Puzzle, label: "驱动", desc: "agent 驱动状态" },
{ id: "shortcuts", icon: Keyboard, label: "快捷键", desc: "键盘操作一览" },
{ id: "about", icon: Info, label: "关于", desc: "版本与资源" },
];
function Row({ label, children, hint }: { label: string; children: React.ReactNode; hint?: string }) {
return (
{label}
{children}
{hint &&
{hint}
}
);
}
function Toggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
return (
);
}
function Seg({ value, options, onChange }: { value: T; options: Array<{ v: T; label: string }>; onChange: (v: T) => void }) {
return (
{options.map((o) => (
))}
);
}
function AboutLink({ label, description, onClick }: { label: string; description: string; onClick: () => void }) {
return (
);
}
/** 设置弹框:左分类导航 + 右面板(对标 opsxshell / DBeaver Preferences) */
export function SettingsModal({ onClose }: { onClose: () => void }) {
const [tab, setTab] = useState("general");
const theme = useWorkspaceStore((s) => s.theme);
const showAi = useWorkspaceStore((s) => s.showAi);
const stopOnError = useWorkspaceStore((s) => s.stopOnError);
const execTimeoutMs = useWorkspaceStore((s) => s.execTimeoutMs);
const resultRatio = useWorkspaceStore((s) => s.resultRatio);
const set = useWorkspaceStore((s) => s.set);
const notify = useWorkspaceStore((s) => s.notify);
const [showDriverManager, setShowDriverManager] = useState(false);
const [driverError, setDriverError] = useState("");
const [drivers, setDrivers] = useState(null);
const [navSearch, setNavSearch] = useState("");
const [version, setVersion] = useState("");
const reloadDrivers = () => {
setDriverError("");
setDrivers(null);
void driverStatus().then(setDrivers).catch(() => setDriverError("驱动状态加载失败,请确认本机服务已启动。"));
};
useEffect(() => {
reloadDrivers();
void health().then((h) => setVersion(String((h as { version?: string }).version ?? ""))).catch(() => {});
}, []);
const resetLayout = () => {
set("leftWidth", 260);
set("aiWidth", 280);
set("resultRatio", 0.38);
notify({ kind: "success", title: "已重置", message: "面板布局恢复默认", mode: "toast", durationMs: 1400 });
};
const visibleTabs = TABS.filter((t) => (t.label + t.desc + (t.id === "general" ? "主题 深色 浅色 布局" : "") + (t.id === "mcp" ? "mcp cursor claude npm 只读 白名单" : "") + (t.id === "ai" ? "deepseek 模型 agent" : "")).toLowerCase().includes(navSearch.trim().toLowerCase()));
const activeTab = visibleTabs.find((t) => t.id === tab) ?? visibleTabs[0];
const active = activeTab?.id;
return (
<>
{/* 左导航:搜索 + 线框图标 + 主标题/副文案(对齐 opsxshell) */}
{/* 右面板 */}
{activeTab ?
{activeTab.label}
:
没有匹配的设置
}
{active === "ai" &&
setTab("general")} />}
{active === "mcp" && }
{active === "security" && 始终开启
本机存储
按需发送
}
{active === "general" && (
<>
set("theme", v)}
/>
set("showAi", v)} />
>
)}
{active === "execution" && (
<>
set("execTimeoutMs", v)}
/>
set("stopOnError", v)} />
始终开启
>
)}
{active === "results" && (
<>
set("resultRatio", Number(e.target.value) / 100)}
style={{ accentColor: "var(--ops-accent)", width: 120 }}
/>
1000 / 10000
>
)}
{active === "drivers" && (
<>
{driverError &&
{driverError}
}
{drivers?.length === 0 &&
暂无需要管理的驱动。
}
{!driverError && drivers === null &&
加载中…
}
{drivers?.map((d) => (
{d.id}
{d.allInstalled ? "已安装" : d.license === "proprietary" ? "需自备 jar" : "未安装"}
))}
>
)}
{active === "shortcuts" && (
{[
[`${MOD} + Enter`, "执行当前语句或选区"],
[`${MOD} + T`, "新建查询"],
[`${MOD} + W`, "关闭查询"],
[`${MOD} + 1-9`, "切换到第 N 个查询"],
["Ctrl + Tab", "循环切换查询"],
[`${MOD} + ⇧ + F`, "聚焦对象树过滤框"],
[`${MOD} + B`, "显示/隐藏 AI"],
[`${MOD} + /`, "注释 / 取消注释"],
[`${MOD} + ⇧ + /`, "快捷键帮助"],
["双击表名", "打开前 100 行"],
["双击单元格", "进入行编辑"],
].map(([k, v]) => (
{k}
{v}
))}
)}
{active === "about" && (
OpsxDB {appPackage.version}
·
桌面客户端
·
本机服务 {version || "未连接"}
资源
void openExternal(WECHAT_GROUP_URL)} />
void openExternal(DRIVER_STORE_URL)} />
)}
{showDriverManager && { setShowDriverManager(false); reloadDrivers(); }} />}
>
);
}