feat(ui): AI Tools modal, one-button lane tool picker, AgentHub settings in Overview, markdown preview
- AI Tools moves from a Settings section into a modal behind a button at the Settings tab bar's right edge (AiToolsModal, `actions` on PageTabs, /settings#ai-tools); rows get an enable switch, Default badge, and an Update button for every tool whose catalog names an updater (no version lookup on open); not-installed tools fold behind a disclosure, a finished job's log folds to one line; shared AiToolIcon - codex updates through its own `codex update`, openclaw/hermes gain `update --yes`; update-all runs every installed updater and skips an npm one only when npm does not manage the binary - Add-lane tool picker is one button showing the chosen tool that opens the enabled tools as buttons - Claude Code / Codex AgentHub sub-tab folded into Overview (home editor, hooks, pricing); the path list drops CLAUDE_HOME/CODEX_HOME; HomeDirSetting shows the saved value only while the field differs - Preview/Raw toggle for markdown files in the config FileViewer and the Changes panel's whole-file view (MarkdownPreview: react-markdown + remark-gfm, raw HTML never run, frontmatter as a code block) - docs and the CLAUDE.md marker updated
This commit is contained in:
@@ -2,10 +2,11 @@
|
||||
* @file PageTabs.tsx
|
||||
* @description Shared top-of-page tab bar used by the pages that merged several
|
||||
* former routes into one (Settings hub, Sessions/Events history). Rendering only -
|
||||
* the owning page keeps the active tab in its own query param.
|
||||
* the owning page keeps the active tab in its own query param. `actions` sits at
|
||||
* the bar's right edge, outside the tablist.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
import type { ComponentType } from "react";
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
|
||||
export interface PageTab {
|
||||
id: string;
|
||||
@@ -18,6 +19,8 @@ interface PageTabsProps {
|
||||
activeId: string;
|
||||
onSelect: (id: string) => void;
|
||||
ariaLabel: string;
|
||||
/** Buttons at the right edge of the bar (not tabs). */
|
||||
actions?: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,12 +28,14 @@ interface PageTabsProps {
|
||||
* @param props - Tabs to render, the active tab id, and the selection callback.
|
||||
* @returns The tab bar element.
|
||||
*/
|
||||
export function PageTabs({ tabs, activeId, onSelect, ariaLabel }: PageTabsProps) {
|
||||
return (
|
||||
export function PageTabs({ tabs, activeId, onSelect, ariaLabel, actions }: PageTabsProps) {
|
||||
const list = (
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label={ariaLabel}
|
||||
className="flex items-center gap-1.5 overflow-x-auto no-scrollbar border-b border-border/60 pb-2"
|
||||
className={`flex items-center gap-1.5 overflow-x-auto no-scrollbar ${
|
||||
actions ? "min-w-0 flex-1" : "border-b border-border/60 pb-2"
|
||||
}`}
|
||||
>
|
||||
{tabs.map(({ id, label, Icon }) => {
|
||||
const isActive = id === activeId;
|
||||
@@ -54,4 +59,11 @@ export function PageTabs({ tabs, activeId, onSelect, ariaLabel }: PageTabsProps)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
if (!actions) return list;
|
||||
return (
|
||||
<div className="flex items-center gap-3 border-b border-border/60 pb-2">
|
||||
{list}
|
||||
<div className="flex flex-shrink-0 items-center gap-1.5">{actions}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user