Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions dashboard/src/components/common/DocsLinkAnchor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { AnchorHTMLAttributes, ReactElement, ReactNode } from 'react';
import { FiExternalLink } from 'react-icons/fi';

import type { DocLink } from '../../lib/docsLinks';
import { cn } from '../../lib/utils';

const BUTTON_CLASS_NAME = 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl border border-border/90 bg-card/80 px-3 py-2 text-xs font-semibold text-foreground shadow-soft transition-all duration-200 hover:border-primary/40 hover:bg-card';
const TEXT_CLASS_NAME = 'inline-flex items-center gap-1 text-sm font-semibold text-primary underline-offset-4 hover:underline';

export interface DocsLinkAnchorProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
doc: DocLink;
children?: ReactNode;
appearance?: 'button' | 'text';
}

export function DocsLinkAnchor({
doc,
children,
appearance = 'button',
className,
...props
}: DocsLinkAnchorProps): ReactElement {
return (
<a
{...props}
href={doc.url}
target="_blank"
rel="noreferrer"
title={`Open ${doc.title} on docs.golemcore.me`}
className={cn(appearance === 'button' ? BUTTON_CLASS_NAME : TEXT_CLASS_NAME, className)}
>
<span>{children ?? doc.shortLabel}</span>
<FiExternalLink size={appearance === 'button' ? 14 : 12} aria-hidden="true" />
</a>
);
}
30 changes: 22 additions & 8 deletions dashboard/src/components/common/HelpTip.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import type { ReactElement } from 'react';
import { FiHelpCircle } from 'react-icons/fi';
import { FiExternalLink, FiHelpCircle } from 'react-icons/fi';

interface HelpTipProps {
export interface HelpTipProps {
text: string;
href?: string;
linkLabel?: string;
}

export default function HelpTip({ text }: HelpTipProps): ReactElement {
export default function HelpTip({ text, href, linkLabel }: HelpTipProps): ReactElement {
const tooltipClassName = href != null
? 'absolute bottom-[calc(100%+0.5rem)] left-1/2 z-30 hidden w-64 -translate-x-1/2 rounded-2xl border border-border/80 bg-card/95 px-3 py-2 text-xs leading-5 text-card-foreground shadow-2xl backdrop-blur-sm group-hover:block group-focus-within:block'
: 'pointer-events-none absolute bottom-[calc(100%+0.5rem)] left-1/2 z-30 hidden w-56 -translate-x-1/2 rounded-2xl border border-border/80 bg-card/95 px-3 py-2 text-xs leading-5 text-card-foreground shadow-2xl backdrop-blur-sm group-hover:block group-focus-within:block';

return (
<span className="group relative inline-flex">
<button
Expand All @@ -15,11 +21,19 @@ export default function HelpTip({ text }: HelpTipProps): ReactElement {
>
<FiHelpCircle aria-hidden="true" focusable="false" />
</button>
<span
role="tooltip"
className="pointer-events-none absolute bottom-[calc(100%+0.5rem)] left-1/2 z-30 hidden w-56 -translate-x-1/2 rounded-2xl border border-border/80 bg-card/95 px-3 py-2 text-xs leading-5 text-card-foreground shadow-2xl backdrop-blur-sm group-hover:block group-focus-within:block"
>
{text}
<span role="tooltip" className={tooltipClassName}>
<span>{text}</span>
{href != null && (
<a
href={href}
target="_blank"
rel="noreferrer"
className="mt-2 inline-flex items-center gap-1 text-[11px] font-semibold text-primary underline-offset-4 hover:underline"
>
<span>{linkLabel ?? 'Open docs'}</span>
<FiExternalLink size={11} aria-hidden="true" />
</a>
)}
</span>
</span>
);
Expand Down
34 changes: 34 additions & 0 deletions dashboard/src/components/common/PageDocsLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { ReactElement } from 'react';

import type { DocLink } from '../../lib/docsLinks';
import { cn } from '../../lib/utils';
import { DocsLinkAnchor } from './DocsLinkAnchor';

export interface PageDocsLinksProps {
title?: string;
docs: DocLink[];
className?: string;
}

export function PageDocsLinks({
title = 'Relevant docs',
docs,
className,
}: PageDocsLinksProps): ReactElement | null {
if (docs.length === 0) {
return null;
}

return (
<div className={cn('space-y-2', className)}>
<div className="text-[0.72rem] font-semibold uppercase tracking-[0.24em] text-primary/80">
{title}
</div>
<div className="flex flex-wrap gap-2">
{docs.map((doc) => (
<DocsLinkAnchor key={doc.id} doc={doc} />
))}
</div>
</div>
);
}
13 changes: 13 additions & 0 deletions dashboard/src/components/ide/IdeHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ReactElement } from 'react';
import { FiCommand, FiFolder, FiMinus, FiPlus, FiSave } from 'react-icons/fi';
import { PageDocsLinks } from '../common/PageDocsLinks';
import { type DocLink, getDocLinks } from '../../lib/docsLinks';
import { Badge } from '../ui/badge';
import { Button } from '../ui/button';

Expand All @@ -17,6 +19,14 @@ export interface IdeHeaderProps {
onDecreaseSidebarWidth: () => void;
}

function resolveIdeDocs(activeFileLabel: string | null): DocLink[] {
const normalizedLabel = activeFileLabel?.toLowerCase() ?? '';
if (normalizedLabel.endsWith('skill.md') || normalizedLabel.includes('/skills/')) {
return getDocLinks(['skills', 'mcp', 'dashboard']);
}
return getDocLinks(['dashboard', 'skills']);
}

export function IdeHeader({
activeFileLabel,
isMobileLayout,
Expand All @@ -30,13 +40,16 @@ export function IdeHeader({
onIncreaseSidebarWidth,
onDecreaseSidebarWidth,
}: IdeHeaderProps): ReactElement {
const ideDocs = resolveIdeDocs(activeFileLabel);

return (
<div className="section-header flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<h4 className="mb-1">IDE</h4>
<p className="text-sm text-muted-foreground">
Open a file, make a quick change, and save it back to the workspace.
</p>
<PageDocsLinks title="Workspace docs" docs={ideDocs} className="mt-3" />
{isMobileLayout && (
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
<span className="font-semibold uppercase tracking-[0.16em]">Current file</span>
Expand Down
21 changes: 19 additions & 2 deletions dashboard/src/components/layout/Topbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { useAuthStore } from '../../store/authStore';
import { useChatRuntimeStore } from '../../store/chatRuntimeStore';
import { useChatSessionStore } from '../../store/chatSessionStore';
Expand All @@ -7,8 +7,9 @@ import { useSidebarStore } from '../../store/sidebarStore';
import { useBackgroundSystemUpdateCheck } from '../../hooks/useBackgroundSystemUpdateCheck';
import { useSystemUpdateStatus } from '../../hooks/useSystem';
import { logout } from '../../api/auth';
import { getPrimaryDocForPath } from '../../lib/docsLinks';
import { type TopbarUpdateNotice, getTopbarUpdateNotice } from '../../utils/systemUpdateUi';
import { FiArrowUpCircle, FiLogOut, FiMenu, FiMoon, FiSun } from 'react-icons/fi';
import { FiArrowUpCircle, FiBookOpen, FiLogOut, FiMenu, FiMoon, FiSun } from 'react-icons/fi';

interface ChatStatusState {
trackedSessionId: string | null;
Expand Down Expand Up @@ -69,6 +70,7 @@ function TopbarUpdateShortcut({ notice, onClick }: { notice: TopbarUpdateNotice;
}

export default function Topbar() {
const location = useLocation();
const nav = useNavigate();
const doLogout = useAuthStore((s) => s.logout);
const activeSessionId = useChatSessionStore((s) => s.activeSessionId);
Expand All @@ -83,6 +85,7 @@ export default function Topbar() {
const { data: updateStatus } = useSystemUpdateStatus();
const chatStatus = resolveChatStatus(activeSessionId, activeSession, runningSessionId, connectionState);
const updateNotice = getTopbarUpdateNotice(updateStatus);
const primaryDoc = getPrimaryDocForPath(location.pathname);

useBackgroundSystemUpdateCheck(updateStatus);

Expand Down Expand Up @@ -135,6 +138,20 @@ export default function Topbar() {
)}
</div>
<div className="d-flex align-items-center gap-2">
{primaryDoc != null && (
<a
href={primaryDoc.url}
target="_blank"
rel="noreferrer"
className="topbar-action-btn text-decoration-none d-flex align-items-center px-3"
title={`Open ${primaryDoc.title} on docs.golemcore.me`}
aria-label={`Open ${primaryDoc.title} on docs.golemcore.me`}
>
<FiBookOpen size={16} className="me-0 me-lg-2" />
<span className="fw-medium small d-none d-lg-inline">{primaryDoc.shortLabel}</span>
<span className="fw-medium small d-lg-none">Docs</span>
</a>
)}
{updateNotice != null && (
<TopbarUpdateShortcut notice={updateNotice} onClick={handleOpenUpdates} />
)}
Expand Down
10 changes: 10 additions & 0 deletions dashboard/src/components/webhooks/ValidationIssuesAlert.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type { ReactElement } from 'react';
import { Alert } from '../ui/tailwind-components';

import type { WebhookValidationResult } from '../../api/webhooks';
import { DocsLinkAnchor } from '../common/DocsLinkAnchor';
import { getDocLink } from '../../lib/docsLinks';

interface ValidationIssuesAlertProps {
validation: WebhookValidationResult;
}

const WEBHOOKS_DOC = getDocLink('webhooks');

export function ValidationIssuesAlert({ validation }: ValidationIssuesAlertProps): ReactElement | null {
if (validation.valid) {
return null;
Expand All @@ -19,6 +24,11 @@ export function ValidationIssuesAlert({ validation }: ValidationIssuesAlertProps
<li key={issue}>{issue}</li>
))}
</ul>
<div className="mt-3">
<DocsLinkAnchor doc={WEBHOOKS_DOC} appearance="text">
Review the webhook guide
</DocsLinkAnchor>
</div>
</Alert>
);
}
8 changes: 7 additions & 1 deletion dashboard/src/components/webhooks/WebhooksPageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import type { ReactElement } from 'react';
import { Badge } from '../ui/tailwind-components';
import { FiGlobe } from 'react-icons/fi';

import { PageDocsLinks } from '../common/PageDocsLinks';
import { getDocLinks } from '../../lib/docsLinks';

export interface WebhookSummary {
total: number;
agent: number;
Expand All @@ -13,6 +16,8 @@ interface WebhooksPageHeaderProps {
summary: WebhookSummary;
}

const WEBHOOK_DOCS = getDocLinks(['webhooks', 'dashboard']);

export function WebhooksPageHeader({ enabled, summary }: WebhooksPageHeaderProps): ReactElement {
return (
<div className="page-header d-flex flex-wrap align-items-start justify-content-between gap-3">
Expand All @@ -24,8 +29,9 @@ export function WebhooksPageHeader({ enabled, summary }: WebhooksPageHeaderProps
<p className="text-body-secondary mb-0">
Configure inbound HTTP hooks, authentication, templates, and delivery routes.
</p>
<PageDocsLinks title="Relevant docs" docs={WEBHOOK_DOCS} className="mt-3" />
</div>
<div className="d-flex flex-wrap gap-2">
<div className="d-flex flex-wrap gap-2 align-content-start">
<Badge bg={enabled ? 'success' : 'secondary'}>
{enabled ? 'Enabled' : 'Disabled'}
</Badge>
Expand Down
Loading
Loading