mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
fix(dashboard): make droid docs links always clickable
- render URL text as explicit anchors in docs cards - linkify raw URLs in docs notes - fallback to built-in Droid docs links when legacy API payload omits links
This commit is contained in:
1 parent
e7f3b3ffcf
commit
1704b802ee
1 file changed
+92
-6
+92
-6
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { type ReactNode, useMemo, useState } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
import {
|
import {
|
||||||
@@ -26,6 +26,83 @@ import { Separator } from '@/components/ui/separator';
|
|||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const DEFAULT_DROID_FACTORY_DOC_LINKS = [
|
||||||
|
{
|
||||||
|
id: 'droid-cli-overview',
|
||||||
|
label: 'Droid CLI Overview',
|
||||||
|
url: 'https://docs.factory.ai/cli/',
|
||||||
|
description: 'Primary entry docs for setup, auth, and core CLI usage.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'droid-byok-overview',
|
||||||
|
label: 'BYOK Overview',
|
||||||
|
url: 'https://docs.factory.ai/cli/byok/overview/',
|
||||||
|
description: 'BYOK model/provider shape, provider values, and migration notes.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'droid-settings-reference',
|
||||||
|
label: 'settings.json Reference',
|
||||||
|
url: 'https://docs.factory.ai/cli/configuration/settings/',
|
||||||
|
description: 'Supported settings keys, defaults, and allowed values.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_DROID_PROVIDER_DOC_LINKS = [
|
||||||
|
{
|
||||||
|
provider: 'anthropic',
|
||||||
|
label: 'Anthropic Messages API',
|
||||||
|
apiFormat: 'Messages API',
|
||||||
|
url: 'https://docs.anthropic.com/en/api/messages',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: 'openai',
|
||||||
|
label: 'OpenAI Responses API',
|
||||||
|
apiFormat: 'Responses API',
|
||||||
|
url: 'https://platform.openai.com/docs/api-reference/responses',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: 'generic-chat-completion-api',
|
||||||
|
label: 'OpenAI Chat Completions Spec',
|
||||||
|
apiFormat: 'Chat Completions API',
|
||||||
|
url: 'https://platform.openai.com/docs/api-reference/chat',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function renderTextWithLinks(text: string): ReactNode[] {
|
||||||
|
const urlPattern = /https?:\/\/[^\s)]+/g;
|
||||||
|
const nodes: ReactNode[] = [];
|
||||||
|
let cursor = 0;
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
while ((match = urlPattern.exec(text)) !== null) {
|
||||||
|
const [url] = match;
|
||||||
|
const index = match.index;
|
||||||
|
|
||||||
|
if (index > cursor) {
|
||||||
|
nodes.push(text.slice(cursor, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.push(
|
||||||
|
<a
|
||||||
|
key={`${url}-${index}`}
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="underline underline-offset-2 hover:text-foreground"
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
cursor = index + url.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor < text.length) {
|
||||||
|
nodes.push(text.slice(cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes.length > 0 ? nodes : [text];
|
||||||
|
}
|
||||||
|
|
||||||
function formatTimestamp(value: number | null | undefined): string {
|
function formatTimestamp(value: number | null | undefined): string {
|
||||||
if (!value || !Number.isFinite(value)) return 'N/A';
|
if (!value || !Number.isFinite(value)) return 'N/A';
|
||||||
return new Date(value).toLocaleString();
|
return new Date(value).toLocaleString();
|
||||||
@@ -211,8 +288,11 @@ export function DroidPage() {
|
|||||||
settingsHierarchy: [],
|
settingsHierarchy: [],
|
||||||
};
|
};
|
||||||
const docsNotes = docsReference.notes ?? [];
|
const docsNotes = docsReference.notes ?? [];
|
||||||
const docsLinks = docsReference.links ?? [];
|
const docsLinksRaw = docsReference.links ?? [];
|
||||||
const providerDocs = docsReference.providerDocs ?? [];
|
const providerDocsRaw = docsReference.providerDocs ?? [];
|
||||||
|
const docsLinks = docsLinksRaw.length > 0 ? docsLinksRaw : DEFAULT_DROID_FACTORY_DOC_LINKS;
|
||||||
|
const providerDocs =
|
||||||
|
providerDocsRaw.length > 0 ? providerDocsRaw : DEFAULT_DROID_PROVIDER_DOC_LINKS;
|
||||||
const providerValues = docsReference.providerValues ?? [];
|
const providerValues = docsReference.providerValues ?? [];
|
||||||
const settingsHierarchy = docsReference.settingsHierarchy ?? [];
|
const settingsHierarchy = docsReference.settingsHierarchy ?? [];
|
||||||
|
|
||||||
@@ -350,9 +430,9 @@ export function DroidPage() {
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2 text-sm">
|
<CardContent className="space-y-2 text-sm">
|
||||||
{docsNotes.map((note) => (
|
{docsNotes.map((note, index) => (
|
||||||
<p key={note} className="text-muted-foreground">
|
<p key={`${index}-${note}`} className="text-muted-foreground">
|
||||||
- {note}
|
- {renderTextWithLinks(note)}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
<Separator />
|
<Separator />
|
||||||
@@ -374,6 +454,9 @@ export function DroidPage() {
|
|||||||
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 text-[11px] text-muted-foreground">{link.description}</p>
|
<p className="mt-0.5 text-[11px] text-muted-foreground">{link.description}</p>
|
||||||
|
<p className="mt-1 break-all font-mono text-[11px] text-muted-foreground/90 underline underline-offset-2">
|
||||||
|
{link.url}
|
||||||
|
</p>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -399,6 +482,9 @@ export function DroidPage() {
|
|||||||
<p className="mt-0.5 text-[11px] text-muted-foreground">
|
<p className="mt-0.5 text-[11px] text-muted-foreground">
|
||||||
provider: {providerDoc.provider} | format: {providerDoc.apiFormat}
|
provider: {providerDoc.provider} | format: {providerDoc.apiFormat}
|
||||||
</p>
|
</p>
|
||||||
|
<p className="mt-1 break-all font-mono text-[11px] text-muted-foreground/90 underline underline-offset-2">
|
||||||
|
{providerDoc.url}
|
||||||
|
</p>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user