fix: replace all buttons for design system buttons

This commit is contained in:
Austin Pickett
2026-04-28 08:57:33 -04:00
parent 529eb29b6a
commit e116957a63
15 changed files with 117 additions and 193 deletions

View File

@@ -160,7 +160,10 @@ function resolveIcon(name: string): ComponentType<{ className?: string }> {
return ICON_MAP[name] ?? Puzzle; return ICON_MAP[name] ?? Puzzle;
} }
function buildNavItems(builtIn: NavItem[], manifests: PluginManifest[]): NavItem[] { function buildNavItems(
builtIn: NavItem[],
manifests: PluginManifest[],
): NavItem[] {
const items = [...builtIn]; const items = [...builtIn];
for (const manifest of manifests) { for (const manifest of manifests) {
@@ -425,18 +428,22 @@ export default function App() {
> >
<div <div
className={cn( className={cn(
"flex h-14 shrink-0 items-center justify-between gap-2 px-5", "flex h-14 shrink-0 items-center justify-between gap-2",
"border-b border-current/20", "border-b border-current/20",
)} )}
> >
<Typography <div className="flex items-center gap-2">
className="font-bold text-[1.125rem] leading-[0.95] tracking-[0.0525rem] text-midground" <PluginSlot name="header-left" />
style={{ mixBlendMode: "plus-lighter" }}
> <Typography
Hermes className="font-bold text-[1.125rem] leading-[0.95] tracking-[0.0525rem] text-midground"
<br /> style={{ mixBlendMode: "plus-lighter" }}
Agent >
</Typography> Hermes
<br />
Agent
</Typography>
</div>
<button <button
type="button" type="button"
@@ -452,8 +459,6 @@ export default function App() {
</button> </button>
</div> </div>
<PluginSlot name="header-left" />
<nav <nav
className="min-h-0 w-full flex-1 overflow-y-auto overflow-x-hidden border-t border-current/10 py-2" className="min-h-0 w-full flex-1 overflow-y-auto overflow-x-hidden border-t border-current/10 py-2"
aria-label={t.app.navigation} aria-label={t.app.navigation}
@@ -545,7 +550,8 @@ export default function App() {
<div <div
className={cn( className={cn(
"w-full min-w-0", "w-full min-w-0",
(isDocsRoute || isChatRoute) && "min-h-0 flex flex-1 flex-col", (isDocsRoute || isChatRoute) &&
"min-h-0 flex flex-1 flex-col",
)} )}
> >
<Routes> <Routes>
@@ -579,8 +585,9 @@ export default function App() {
or idle-disconnect after N minutes hidden; neither is or idle-disconnect after N minutes hidden; neither is
shipped today. shipped today.
*/} */}
{embeddedChat && !chatOverriddenByPlugin && ( {embeddedChat &&
pluginsLoading ? ( !chatOverriddenByPlugin &&
(pluginsLoading ? (
// Direct /chat deep-link: plugin manifests haven't resolved // Direct /chat deep-link: plugin manifests haven't resolved
// yet, so we can't tell if a plugin is going to claim this // yet, so we can't tell if a plugin is going to claim this
// route. Show a lightweight placeholder instead of a // route. Show a lightweight placeholder instead of a
@@ -593,7 +600,10 @@ export default function App() {
aria-live="polite" aria-live="polite"
> >
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> <Loader2
className="h-4 w-4 animate-spin"
aria-hidden
/>
<span>Loading chat</span> <span>Loading chat</span>
</div> </div>
</div> </div>
@@ -609,8 +619,7 @@ export default function App() {
> >
<ChatPage isActive={isChatRoute} /> <ChatPage isActive={isChatRoute} />
</div> </div>
) ))}
)}
</div> </div>
<PluginSlot name="post-main" /> <PluginSlot name="post-main" />
</div> </div>

View File

@@ -23,8 +23,8 @@
* terminal pane keeps working unimpaired. * terminal pane keeps working unimpaired.
*/ */
import { Button } from "@nous-research/ui";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { ModelPickerDialog } from "@/components/ModelPickerDialog"; import { ModelPickerDialog } from "@/components/ModelPickerDialog";
@@ -337,12 +337,11 @@ export function ChatSidebar({ channel, className }: ChatSidebarProps) {
{error && ( {error && (
<Button <Button
variant="ghost" outlined
size="sm" className="mt-1"
className="mt-1 h-6 px-1.5 text-xs"
onClick={reconnect} onClick={reconnect}
prefix={<RefreshCw />}
> >
<RefreshCw className="mr-1 h-3 w-3" />
reconnect reconnect
</Button> </Button>
)} )}

View File

@@ -1,4 +1,4 @@
import { Button } from "@/components/ui/button"; import { Button } from "@nous-research/ui";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import type { GatewayClient } from "@/lib/gatewayClient"; import type { GatewayClient } from "@/lib/gatewayClient";
import { Check, Loader2, Search, X } from "lucide-react"; import { Check, Loader2, Search, X } from "lucide-react";
@@ -222,10 +222,10 @@ export function ModelPickerDialog({ gw, sessionId, onClose, onSubmit }: Props) {
</label> </label>
<div className="flex items-center gap-2 ml-auto"> <div className="flex items-center gap-2 ml-auto">
<Button variant="ghost" size="sm" onClick={onClose}> <Button outlined onClick={onClose}>
Cancel Cancel
</Button> </Button>
<Button size="sm" onClick={confirm} disabled={!canConfirm}> <Button onClick={confirm} disabled={!canConfirm}>
Switch Switch
</Button> </Button>
</div> </div>

View File

@@ -1,8 +1,7 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { ExternalLink, Copy, X, Check, Loader2 } from "lucide-react"; import { ExternalLink, Copy, X, Check, Loader2 } from "lucide-react";
import { H2 } from "@nous-research/ui"; import { Button, H2 } from "@nous-research/ui";
import { api, type OAuthProvider, type OAuthStartResponse } from "@/lib/api"; import { api, type OAuthProvider, type OAuthStartResponse } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
@@ -254,7 +253,6 @@ export function OAuthLoginModal({
<Button <Button
onClick={handleSubmitPkceCode} onClick={handleSubmitPkceCode}
disabled={!pkceCode.trim()} disabled={!pkceCode.trim()}
size="sm"
> >
{t.oauth.submitCode} {t.oauth.submitCode}
</Button> </Button>
@@ -289,8 +287,7 @@ export function OAuthLoginModal({
} }
</code> </code>
<Button <Button
variant="outline" outlined
size="sm"
onClick={() => onClick={() =>
handleCopyUserCode( handleCopyUserCode(
( (
@@ -301,12 +298,12 @@ export function OAuthLoginModal({
).user_code, ).user_code,
) )
} }
className="text-xs" className="!p-2 aspect-square"
> >
{codeCopied ? ( {codeCopied ? (
<Check className="h-3 w-3" /> <Check className="h-3.5 w-3.5" />
) : ( ) : (
<Copy className="h-3 w-3" /> <Copy className="h-3.5 w-3.5" />
)} )}
</Button> </Button>
</div> </div>
@@ -348,11 +345,10 @@ export function OAuthLoginModal({
{errorMsg || t.oauth.loginFailed} {errorMsg || t.oauth.loginFailed}
</div> </div>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<Button variant="outline" size="sm" onClick={handleClose}> <Button outlined onClick={handleClose}>
{t.common.close} {t.common.close}
</Button> </Button>
<Button <Button
size="sm"
onClick={() => { onClick={() => {
if (start?.session_id) { if (start?.session_id) {
api.cancelOAuthSession(start.session_id).catch(() => {}); api.cancelOAuthSession(start.session_id).catch(() => {});

View File

@@ -1,8 +1,8 @@
import { useEffect, useState, useCallback, useRef } from "react"; import { useEffect, useState, useCallback, useRef } from "react";
import { ShieldCheck, ShieldOff, Copy, ExternalLink, RefreshCw, LogOut, Terminal, LogIn } from "lucide-react"; import { ShieldCheck, ShieldOff, Copy, ExternalLink, RefreshCw, LogOut, Terminal, LogIn } from "lucide-react";
import { api, type OAuthProvider } from "@/lib/api"; import { api, type OAuthProvider } from "@/lib/api";
import { Button } from "@nous-research/ui";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { OAuthLoginModal } from "@/components/OAuthLoginModal"; import { OAuthLoginModal } from "@/components/OAuthLoginModal";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
@@ -94,13 +94,11 @@ export function OAuthProvidersCard({ onError, onSuccess }: Props) {
<CardTitle className="text-base">{t.oauth.providerLogins}</CardTitle> <CardTitle className="text-base">{t.oauth.providerLogins}</CardTitle>
</div> </div>
<Button <Button
variant="ghost" outlined
size="sm"
onClick={refresh} onClick={refresh}
disabled={loading} disabled={loading}
className="text-xs" prefix={<RefreshCw className={loading ? "animate-spin" : undefined} />}
> >
<RefreshCw className={`h-3 w-3 mr-1 ${loading ? "animate-spin" : ""}`} />
{t.common.refresh} {t.common.refresh}
</Button> </Button>
</div> </div>
@@ -194,53 +192,42 @@ export function OAuthProvidersCard({ onError, onSuccess }: Props) {
className="inline-flex" className="inline-flex"
title={`Open ${p.name} docs`} title={`Open ${p.name} docs`}
> >
<Button variant="ghost" size="sm" className="h-7 w-7 p-0"> <Button outlined className="!p-1.5 aspect-square">
<ExternalLink className="h-3.5 w-3.5" /> <ExternalLink className="h-3.5 w-3.5" />
</Button> </Button>
</a> </a>
)} )}
{!p.status.logged_in && p.flow !== "external" && ( {!p.status.logged_in && p.flow !== "external" && (
<Button <Button
variant="default"
size="sm"
onClick={() => setLoginFor(p)} onClick={() => setLoginFor(p)}
className="text-xs h-7" prefix={<LogIn />}
> >
<LogIn className="h-3 w-3 mr-1" />
{t.oauth.login} {t.oauth.login}
</Button> </Button>
)} )}
{!p.status.logged_in && ( {!p.status.logged_in && (
<Button <Button
variant="outline" outlined
size="sm"
onClick={() => handleCopy(p)} onClick={() => handleCopy(p)}
className="text-xs h-7"
title={t.oauth.copyCliCommand} title={t.oauth.copyCliCommand}
prefix={copiedId === p.id ? undefined : <Copy />}
> >
{copiedId === p.id ? ( {copiedId === p.id ? t.oauth.copied : t.oauth.cli}
<>{t.oauth.copied}</>
) : (
<>
<Copy className="h-3 w-3 mr-1" />
{t.oauth.cli}
</>
)}
</Button> </Button>
)} )}
{p.status.logged_in && p.flow !== "external" && ( {p.status.logged_in && p.flow !== "external" && (
<Button <Button
variant="outline" outlined
size="sm"
onClick={() => handleDisconnect(p)} onClick={() => handleDisconnect(p)}
disabled={isBusy} disabled={isBusy}
className="text-xs h-7" prefix={
isBusy ? (
<RefreshCw className="animate-spin" />
) : (
<LogOut />
)
}
> >
{isBusy ? (
<RefreshCw className="h-3 w-3 mr-1 animate-spin" />
) : (
<LogOut className="h-3 w-3 mr-1" />
)}
{t.oauth.disconnect} {t.oauth.disconnect}
</Button> </Button>
)} )}

View File

@@ -1,38 +0,0 @@
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
export const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap font-mondwest text-xs tracking-[0.1em] uppercase transition-colors cursor-pointer"
+ " disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-foreground/90 text-background hover:bg-foreground",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-border bg-transparent hover:bg-foreground/10 hover:text-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-foreground/10 hover:text-foreground",
link: "text-foreground underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 px-3 text-[0.65rem]",
lg: "h-10 px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export function Button({
className,
variant,
size,
...props
}: React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>) {
return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />;
}

View File

@@ -1,8 +1,8 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { AlertTriangle } from "lucide-react"; import { AlertTriangle } from "lucide-react";
import { Button } from "@nous-research/ui";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
export function ConfirmDialog({ export function ConfirmDialog({
cancelLabel = "Cancel", cancelLabel = "Cancel",
@@ -101,8 +101,7 @@ export function ConfirmDialog({
<div className="flex items-center justify-end gap-2 p-3"> <div className="flex items-center justify-end gap-2 p-3">
<Button <Button
type="button" type="button"
variant="ghost" outlined
size="sm"
onClick={onCancel} onClick={onCancel}
disabled={loading} disabled={loading}
> >
@@ -111,10 +110,9 @@ export function ConfirmDialog({
<Button <Button
data-confirm data-confirm
type="button" type="button"
variant={destructive ? "destructive" : "default"}
size="sm"
onClick={onConfirm} onClick={onConfirm}
disabled={loading} disabled={loading}
className={destructive ? "!bg-destructive !text-destructive-foreground" : undefined}
> >
{loading ? "…" : confirmLabel} {loading ? "…" : confirmLabel}
</Button> </Button>

View File

@@ -10,9 +10,9 @@ import {
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import type { AnalyticsResponse, AnalyticsDailyEntry, AnalyticsModelEntry, AnalyticsSkillEntry } from "@/lib/api"; import type { AnalyticsResponse, AnalyticsDailyEntry, AnalyticsModelEntry, AnalyticsSkillEntry } from "@/lib/api";
import { timeAgo } from "@/lib/utils"; import { timeAgo } from "@/lib/utils";
import { Button } from "@nous-research/ui";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { usePageHeader } from "@/contexts/usePageHeader"; import { usePageHeader } from "@/contexts/usePageHeader";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
import { PluginSlot } from "@/plugins"; import { PluginSlot } from "@/plugins";
@@ -317,9 +317,7 @@ export default function AnalyticsPage() {
<Button <Button
key={p.label} key={p.label}
type="button" type="button"
variant={days === p.days ? "default" : "outline"} outlined={days !== p.days}
size="sm"
className="h-7 min-w-0 text-xs"
onClick={() => setDays(p.days)} onClick={() => setDays(p.days)}
> >
{p.label} {p.label}
@@ -328,13 +326,11 @@ export default function AnalyticsPage() {
</div> </div>
<Button <Button
type="button" type="button"
variant="outline" outlined
size="sm"
onClick={load} onClick={load}
disabled={loading} disabled={loading}
className="h-7 text-xs" prefix={<RefreshCw />}
> >
<RefreshCw className="mr-1 h-3 w-3" />
{t.common.refresh} {t.common.refresh}
</Button> </Button>
</div>, </div>,

View File

@@ -33,8 +33,8 @@ import { getNestedValue, setNestedValue } from "@/lib/nested";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { Toast } from "@/components/Toast"; import { Toast } from "@/components/Toast";
import { AutoField } from "@/components/AutoField"; import { AutoField } from "@/components/AutoField";
import { Button } from "@nous-research/ui";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
@@ -345,10 +345,10 @@ export default function ConfigPage() {
</code> </code>
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Button variant="ghost" size="sm" onClick={handleExport} title={t.config.exportConfig} aria-label={t.config.exportConfig}> <Button outlined onClick={handleExport} title={t.config.exportConfig} aria-label={t.config.exportConfig} className="!p-2 aspect-square">
<Download className="h-3.5 w-3.5" /> <Download className="h-3.5 w-3.5" />
</Button> </Button>
<Button variant="ghost" size="sm" onClick={() => fileInputRef.current?.click()} title={t.config.importConfig} aria-label={t.config.importConfig}> <Button outlined onClick={() => fileInputRef.current?.click()} title={t.config.importConfig} aria-label={t.config.importConfig} className="!p-2 aspect-square">
<Upload className="h-3.5 w-3.5" /> <Upload className="h-3.5 w-3.5" />
</Button> </Button>
<input ref={fileInputRef} type="file" accept=".json" className="hidden" onChange={handleImport} /> <input ref={fileInputRef} type="file" accept=".json" className="hidden" onChange={handleImport} />
@@ -358,7 +358,7 @@ export default function ConfigPage() {
: prettyCategoryName(activeCategory); : prettyCategoryName(activeCategory);
const resetTitle = t.config.resetScopeTooltip.replace("{scope}", resetScopeLabel); const resetTitle = t.config.resetScopeTooltip.replace("{scope}", resetScopeLabel);
return ( return (
<Button variant="ghost" size="sm" onClick={handleReset} title={resetTitle} aria-label={resetTitle}> <Button outlined onClick={handleReset} title={resetTitle} aria-label={resetTitle} className="!p-2 aspect-square">
<RotateCcw className="h-3.5 w-3.5" /> <RotateCcw className="h-3.5 w-3.5" />
</Button> </Button>
); );
@@ -367,32 +367,19 @@ export default function ConfigPage() {
<div className="w-px h-5 bg-border mx-1" /> <div className="w-px h-5 bg-border mx-1" />
<Button <Button
variant={yamlMode ? "default" : "outline"} outlined={!yamlMode}
size="sm"
onClick={() => setYamlMode(!yamlMode)} onClick={() => setYamlMode(!yamlMode)}
className="gap-1.5" prefix={yamlMode ? <FormInput /> : <Code />}
> >
{yamlMode ? ( {yamlMode ? t.common.form : "YAML"}
<>
<FormInput className="h-3.5 w-3.5" />
{t.common.form}
</>
) : (
<>
<Code className="h-3.5 w-3.5" />
YAML
</>
)}
</Button> </Button>
{yamlMode ? ( {yamlMode ? (
<Button size="sm" onClick={handleYamlSave} disabled={yamlSaving} className="gap-1.5"> <Button onClick={handleYamlSave} disabled={yamlSaving} prefix={<Save />}>
<Save className="h-3.5 w-3.5" />
{yamlSaving ? t.common.saving : t.common.save} {yamlSaving ? t.common.saving : t.common.save}
</Button> </Button>
) : ( ) : (
<Button size="sm" onClick={handleSave} disabled={saving} className="gap-1.5"> <Button onClick={handleSave} disabled={saving} prefix={<Save />}>
<Save className="h-3.5 w-3.5" />
{saving ? t.common.saving : t.common.save} {saving ? t.common.saving : t.common.save}
</Button> </Button>
)} )}

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Clock, Pause, Play, Plus, Trash2, Zap } from "lucide-react"; import { Clock, Pause, Play, Plus, Trash2, Zap } from "lucide-react";
import { H2 } from "@nous-research/ui"; import { Button, H2 } from "@nous-research/ui";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import type { CronJob } from "@/lib/api"; import type { CronJob } from "@/lib/api";
import { DeleteConfirmDialog } from "@/components/DeleteConfirmDialog"; import { DeleteConfirmDialog } from "@/components/DeleteConfirmDialog";
@@ -9,7 +9,6 @@ import { useConfirmDelete } from "@/hooks/useConfirmDelete";
import { Toast } from "@/components/Toast"; import { Toast } from "@/components/Toast";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Select, SelectOption } from "@/components/ui/select"; import { Select, SelectOption } from "@/components/ui/select";
@@ -166,7 +165,6 @@ export default function CronPage() {
loading={jobDelete.isDeleting} loading={jobDelete.isDeleting}
/> />
{/* Create new job form */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2 text-base"> <CardTitle className="flex items-center gap-2 text-base">
@@ -237,9 +235,9 @@ export default function CronPage() {
<Button <Button
onClick={handleCreate} onClick={handleCreate}
disabled={creating} disabled={creating}
prefix={<Plus />}
className="w-full" className="w-full"
> >
<Plus className="h-3 w-3" />
{creating ? t.common.creating : t.common.create} {creating ? t.common.creating : t.common.create}
</Button> </Button>
</div> </div>
@@ -248,7 +246,6 @@ export default function CronPage() {
</CardContent> </CardContent>
</Card> </Card>
{/* Jobs list */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<H2 <H2
variant="sm" variant="sm"
@@ -269,7 +266,6 @@ export default function CronPage() {
{jobs.map((job) => ( {jobs.map((job) => (
<Card key={job.id}> <Card key={job.id}>
<CardContent className="flex items-center gap-4 py-4"> <CardContent className="flex items-center gap-4 py-4">
{/* Info */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<span className="font-medium text-sm truncate"> <span className="font-medium text-sm truncate">
@@ -306,16 +302,15 @@ export default function CronPage() {
)} )}
</div> </div>
{/* Actions */}
<div className="flex items-center gap-1 shrink-0"> <div className="flex items-center gap-1 shrink-0">
<Button <Button
variant="ghost" outlined
size="icon"
title={job.state === "paused" ? t.cron.resume : t.cron.pause} title={job.state === "paused" ? t.cron.resume : t.cron.pause}
aria-label={ aria-label={
job.state === "paused" ? t.cron.resume : t.cron.pause job.state === "paused" ? t.cron.resume : t.cron.pause
} }
onClick={() => handlePauseResume(job)} onClick={() => handlePauseResume(job)}
className="!p-2 aspect-square"
> >
{job.state === "paused" ? ( {job.state === "paused" ? (
<Play className="h-4 w-4 text-success" /> <Play className="h-4 w-4 text-success" />
@@ -325,21 +320,21 @@ export default function CronPage() {
</Button> </Button>
<Button <Button
variant="ghost" outlined
size="icon"
title={t.cron.triggerNow} title={t.cron.triggerNow}
aria-label={t.cron.triggerNow} aria-label={t.cron.triggerNow}
onClick={() => handleTrigger(job)} onClick={() => handleTrigger(job)}
className="!p-2 aspect-square"
> >
<Zap className="h-4 w-4" /> <Zap className="h-4 w-4" />
</Button> </Button>
<Button <Button
variant="ghost" outlined
size="icon"
title={t.common.delete} title={t.common.delete}
aria-label={t.common.delete} aria-label={t.common.delete}
onClick={() => jobDelete.requestDelete(job.id)} onClick={() => jobDelete.requestDelete(job.id)}
className="!p-2 aspect-square"
> >
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
</Button> </Button>
@@ -348,6 +343,7 @@ export default function CronPage() {
</Card> </Card>
))} ))}
</div> </div>
<PluginSlot name="cron:bottom" /> <PluginSlot name="cron:bottom" />
</div> </div>
); );

View File

@@ -2,12 +2,19 @@ import { useLayoutEffect } from "react";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
import { usePageHeader } from "@/contexts/usePageHeader"; import { usePageHeader } from "@/contexts/usePageHeader";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { PluginSlot } from "@/plugins"; import { PluginSlot } from "@/plugins";
export const HERMES_DOCS_URL = "https://hermes-agent.nousresearch.com/docs/"; export const HERMES_DOCS_URL = "https://hermes-agent.nousresearch.com/docs/";
const DS_BUTTON_OUTLINED_LINK_CN = cn(
"group relative inline-grid grid-cols-[auto_1fr_auto] items-center",
"px-[.9em_.75em] py-[1.25em] gap-2",
"leading-0 font-bold tracking-[0.2em] uppercase",
"text-midground bg-transparent shadow-midground",
"shadow-[inset_-1px_-1px_0_0_#00000080,inset_1px_1px_0_0_#ffffff80]",
);
export default function DocsPage() { export default function DocsPage() {
const { t } = useI18n(); const { t } = useI18n();
const { setEnd } = usePageHeader(); const { setEnd } = usePageHeader();
@@ -18,12 +25,9 @@ export default function DocsPage() {
href={HERMES_DOCS_URL} href={HERMES_DOCS_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={cn( className={DS_BUTTON_OUTLINED_LINK_CN}
buttonVariants({ variant: "outline", size: "sm" }),
"h-7 text-xs",
)}
> >
<ExternalLink className="mr-1.5 h-3 w-3" /> <ExternalLink className="size-3.5" />
{t.app.openDocumentation} {t.app.openDocumentation}
</a>, </a>,
); );

View File

@@ -21,9 +21,9 @@ import { Toast } from "@/components/Toast";
import { useConfirmDelete } from "@/hooks/useConfirmDelete"; import { useConfirmDelete } from "@/hooks/useConfirmDelete";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { OAuthProvidersCard } from "@/components/OAuthProvidersCard"; import { OAuthProvidersCard } from "@/components/OAuthProvidersCard";
import { Button } from "@nous-research/ui";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
@@ -134,9 +134,8 @@ function EnvVarRow({
{t.env.getKey} <ExternalLink className="h-2.5 w-2.5" /> {t.env.getKey} <ExternalLink className="h-2.5 w-2.5" />
</a> </a>
)} )}
<Button size="sm" variant="outline" className="h-6 text-[0.6rem] px-2" <Button outlined prefix={<Pencil />}
onClick={() => setEdits((prev) => ({ ...prev, [varKey]: "" }))}> onClick={() => setEdits((prev) => ({ ...prev, [varKey]: "" }))}>
<Pencil className="h-2.5 w-2.5" />
{t.common.set} {t.common.set}
</Button> </Button>
</div> </div>
@@ -159,9 +158,8 @@ function EnvVarRow({
{t.env.getKey} <ExternalLink className="h-2.5 w-2.5" /> {t.env.getKey} <ExternalLink className="h-2.5 w-2.5" />
</a> </a>
)} )}
<Button size="sm" variant="outline" className="h-7 text-[0.6rem]" <Button outlined prefix={<Pencil />}
onClick={() => setEdits((prev) => ({ ...prev, [varKey]: "" }))}> onClick={() => setEdits((prev) => ({ ...prev, [varKey]: "" }))}>
<Pencil className="h-3 w-3" />
{t.common.set} {t.common.set}
</Button> </Button>
</div> </div>
@@ -206,26 +204,25 @@ function EnvVarRow({
</div> </div>
{info.is_set && ( {info.is_set && (
<Button size="sm" variant="ghost" onClick={() => onReveal(varKey)} <Button outlined onClick={() => onReveal(varKey)}
title={isRevealed ? t.env.hideValue : t.env.showValue} title={isRevealed ? t.env.hideValue : t.env.showValue}
aria-label={isRevealed ? `Hide ${varKey}` : `Reveal ${varKey}`}> aria-label={isRevealed ? `Hide ${varKey}` : `Reveal ${varKey}`}
className="!p-2 aspect-square">
{isRevealed {isRevealed
? <EyeOff className="h-4 w-4" /> ? <EyeOff className="h-4 w-4" />
: <Eye className="h-4 w-4" />} : <Eye className="h-4 w-4" />}
</Button> </Button>
)} )}
<Button size="sm" variant="outline" <Button outlined prefix={<Pencil />}
onClick={() => setEdits((prev) => ({ ...prev, [varKey]: "" }))}> onClick={() => setEdits((prev) => ({ ...prev, [varKey]: "" }))}>
<Pencil className="h-3 w-3" />
{info.is_set ? t.common.replace : t.common.set} {info.is_set ? t.common.replace : t.common.set}
</Button> </Button>
{info.is_set && ( {info.is_set && (
<Button size="sm" variant="ghost" <Button outlined prefix={<Trash2 />}
className="text-destructive hover:text-destructive hover:bg-destructive/10" className="text-destructive hover:!text-destructive"
onClick={() => onClear(varKey)} disabled={saving === varKey || clearDialogOpen}> onClick={() => onClear(varKey)} disabled={saving === varKey || clearDialogOpen}>
<Trash2 className="h-3 w-3" />
{saving === varKey ? "..." : t.common.clear} {saving === varKey ? "..." : t.common.clear}
</Button> </Button>
)} )}
@@ -238,13 +235,12 @@ function EnvVarRow({
onChange={(e) => setEdits((prev) => ({ ...prev, [varKey]: e.target.value }))} onChange={(e) => setEdits((prev) => ({ ...prev, [varKey]: e.target.value }))}
placeholder={info.is_set ? t.env.replaceCurrentValue.replace("{preview}", info.redacted_value ?? "---") : t.env.enterValue} placeholder={info.is_set ? t.env.replaceCurrentValue.replace("{preview}", info.redacted_value ?? "---") : t.env.enterValue}
className="flex-1 font-mono-ui text-xs" /> className="flex-1 font-mono-ui text-xs" />
<Button size="sm" onClick={() => onSave(varKey)} <Button onClick={() => onSave(varKey)} prefix={<Save />}
disabled={saving === varKey || !edits[varKey]}> disabled={saving === varKey || !edits[varKey]}>
<Save className="h-3 w-3" />
{saving === varKey ? "..." : t.common.save} {saving === varKey ? "..." : t.common.save}
</Button> </Button>
<Button size="sm" variant="ghost" onClick={() => onCancelEdit(varKey)}> <Button outlined prefix={<X />} onClick={() => onCancelEdit(varKey)}>
<X className="h-3 w-3" /> {t.common.cancel} {t.common.cancel}
</Button> </Button>
</div> </div>
)} )}
@@ -537,7 +533,7 @@ export default function EnvPage() {
{t.env.changesNote} {t.env.changesNote}
</p> </p>
</div> </div>
<Button variant="ghost" size="sm" onClick={() => setShowAdvanced(!showAdvanced)}> <Button outlined onClick={() => setShowAdvanced(!showAdvanced)}>
{showAdvanced ? t.env.hideAdvanced : t.env.showAdvanced} {showAdvanced ? t.env.hideAdvanced : t.env.showAdvanced}
</Button> </Button>
</div> </div>

View File

@@ -1,8 +1,8 @@
import { useEffect, useLayoutEffect, useState, useCallback, useRef } from "react"; import { useEffect, useLayoutEffect, useState, useCallback, useRef } from "react";
import { FileText, RefreshCw } from "lucide-react"; import { FileText, RefreshCw } from "lucide-react";
import { api } from "@/lib/api"; import { api } from "@/lib/api";
import { Button } from "@nous-research/ui";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@@ -101,13 +101,11 @@ export default function LogsPage() {
</div> </div>
<Button <Button
type="button" type="button"
variant="outline" outlined
size="sm"
onClick={fetchLogs} onClick={fetchLogs}
disabled={loading} disabled={loading}
className="h-7 text-xs" prefix={<RefreshCw />}
> >
<RefreshCw className="mr-1 h-3 w-3" />
{t.common.refresh} {t.common.refresh}
</Button> </Button>
</div>, </div>,

View File

@@ -36,8 +36,8 @@ import { timeAgo } from "@/lib/utils";
import { Markdown } from "@/components/Markdown"; import { Markdown } from "@/components/Markdown";
import { PlatformsCard } from "@/components/PlatformsCard"; import { PlatformsCard } from "@/components/PlatformsCard";
import { Toast } from "@/components/Toast"; import { Toast } from "@/components/Toast";
import { Button } from "@nous-research/ui";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { DeleteConfirmDialog } from "@/components/DeleteConfirmDialog"; import { DeleteConfirmDialog } from "@/components/DeleteConfirmDialog";
import { useConfirmDelete } from "@/hooks/useConfirmDelete"; import { useConfirmDelete } from "@/hooks/useConfirmDelete";
@@ -356,9 +356,8 @@ function SessionRow({
</Badge> </Badge>
{resumeInChatEnabled && ( {resumeInChatEnabled && (
<Button <Button
variant="ghost" outlined
size="icon" className="!p-1.5 aspect-square text-muted-foreground hover:text-success"
className="h-7 w-7 text-muted-foreground hover:text-success"
aria-label={t.sessions.resumeInChat} aria-label={t.sessions.resumeInChat}
title={t.sessions.resumeInChat} title={t.sessions.resumeInChat}
onClick={(e) => { onClick={(e) => {
@@ -370,9 +369,8 @@ function SessionRow({
</Button> </Button>
)} )}
<Button <Button
variant="ghost" outlined
size="icon" className="!p-1.5 aspect-square text-muted-foreground hover:text-destructive"
className="h-7 w-7 text-muted-foreground hover:text-destructive"
aria-label={t.sessions.deleteSession} aria-label={t.sessions.deleteSession}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@@ -808,9 +806,8 @@ export default function SessionsPage() {
</span> </span>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Button <Button
variant="outline" outlined
size="sm" className="!p-1.5 aspect-square"
className="h-7 w-7 p-0"
disabled={page === 0} disabled={page === 0}
onClick={() => setPage((p) => p - 1)} onClick={() => setPage((p) => p - 1)}
aria-label={t.sessions.previousPage} aria-label={t.sessions.previousPage}
@@ -822,9 +819,8 @@ export default function SessionsPage() {
{Math.ceil(total / PAGE_SIZE)} {Math.ceil(total / PAGE_SIZE)}
</span> </span>
<Button <Button
variant="outline" outlined
size="sm" className="!p-1.5 aspect-square"
className="h-7 w-7 p-0"
disabled={(page + 1) * PAGE_SIZE >= total} disabled={(page + 1) * PAGE_SIZE >= total}
onClick={() => setPage((p) => p + 1)} onClick={() => setPage((p) => p + 1)}
aria-label={t.sessions.nextPage} aria-label={t.sessions.nextPage}

View File

@@ -19,9 +19,9 @@ import React, {
} from "react"; } from "react";
import { api, fetchJSON } from "@/lib/api"; import { api, fetchJSON } from "@/lib/api";
import { cn, timeAgo, isoTimeAgo } from "@/lib/utils"; import { cn, timeAgo, isoTimeAgo } from "@/lib/utils";
import { Button } from "@nous-research/ui";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Select, SelectOption } from "@/components/ui/select"; import { Select, SelectOption } from "@/components/ui/select";