@brainfish-ai/devdoc 0.1.48 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/deploy.js +16 -11
- package/package.json +1 -1
- package/renderer/app/[...slug]/client.js +17 -0
- package/renderer/app/[...slug]/page.js +125 -0
- package/renderer/app/api/assets/[...path]/route.js +23 -4
- package/renderer/app/api/chat/route.js +188 -25
- package/renderer/app/api/collections/route.js +95 -2
- package/renderer/app/api/deploy/route.js +4 -0
- package/renderer/app/api/suggestions/route.js +98 -10
- package/renderer/app/globals.css +33 -0
- package/renderer/app/layout.js +83 -8
- package/renderer/components/docs/mdx/cards.js +16 -45
- package/renderer/components/docs/mdx/file-tree.js +102 -0
- package/renderer/components/docs/mdx/index.js +7 -0
- package/renderer/components/docs-viewer/agent/agent-chat.js +75 -11
- package/renderer/components/docs-viewer/agent/messages/assistant-message.js +67 -3
- package/renderer/components/docs-viewer/agent/messages/tool-call-display.js +49 -4
- package/renderer/components/docs-viewer/content/content-router.js +1 -1
- package/renderer/components/docs-viewer/content/doc-page.js +36 -28
- package/renderer/components/docs-viewer/index.js +223 -58
- package/renderer/components/docs-viewer/playground/graphql-playground.js +131 -33
- package/renderer/components/docs-viewer/shared/method-badge.js +11 -2
- package/renderer/components/docs-viewer/sidebar/collection-tree.js +44 -6
- package/renderer/components/docs-viewer/sidebar/index.js +2 -1
- package/renderer/components/docs-viewer/sidebar/right-sidebar.js +3 -1
- package/renderer/components/docs-viewer/sidebar/sidebar-item.js +5 -7
- package/renderer/hooks/use-route-state.js +44 -56
- package/renderer/lib/api-docs/agent/indexer.js +73 -12
- package/renderer/lib/api-docs/agent/use-suggestions.js +26 -16
- package/renderer/lib/api-docs/code-editor/mode-context.js +16 -18
- package/renderer/lib/api-docs/parsers/openapi/transformer.js +8 -1
- package/renderer/lib/cache/purge.js +98 -0
- package/renderer/lib/docs-link-utils.js +146 -0
- package/renderer/lib/docs-navigation-context.js +3 -2
- package/renderer/lib/docs-navigation.js +50 -41
- package/renderer/lib/rate-limit.js +203 -0
package/renderer/app/globals.css
CHANGED
|
@@ -1266,4 +1266,37 @@ code[data-line-numbers] > [data-line]::before {
|
|
|
1266
1266
|
.agent-suggestion-item {
|
|
1267
1267
|
animation: suggestion-fade-in 0.2s ease-out forwards;
|
|
1268
1268
|
animation-fill-mode: both;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/* Sandbox tabs styling */
|
|
1272
|
+
.sandbox-tabs {
|
|
1273
|
+
background-color: hsl(240 10% 20%);
|
|
1274
|
+
border-bottom: 1px solid hsl(240 10% 25%);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.sandbox-tab-active {
|
|
1278
|
+
background-color: hsl(240 10% 14%);
|
|
1279
|
+
color: hsl(0 0% 95%);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.sandbox-tab-inactive {
|
|
1283
|
+
color: hsl(240 5% 65%);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
.sandbox-tab-inactive:hover {
|
|
1287
|
+
color: hsl(0 0% 90%);
|
|
1288
|
+
background-color: hsl(240 10% 18%);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.sandbox-tab-indicator {
|
|
1292
|
+
background-color: hsl(var(--primary));
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
.sandbox-tab-close {
|
|
1296
|
+
color: hsl(240 5% 55%);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
.sandbox-tab-close:hover {
|
|
1300
|
+
color: hsl(0 0% 95%);
|
|
1301
|
+
background-color: hsl(240 10% 25%);
|
|
1269
1302
|
}
|
package/renderer/app/layout.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { headers } from "next/headers";
|
|
2
3
|
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
4
|
import { ThemeProvider } from "@/components/theme-provider";
|
|
5
|
+
import { extractProjectContext } from "@/lib/multi-tenant/context";
|
|
6
|
+
import { getProjectContent } from "@/lib/storage/blob";
|
|
4
7
|
import "./globals.css";
|
|
5
8
|
const geistSans = Geist({
|
|
6
9
|
variable: "--font-geist-sans",
|
|
@@ -14,15 +17,87 @@ const geistMono = Geist_Mono({
|
|
|
14
17
|
"latin"
|
|
15
18
|
]
|
|
16
19
|
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// Dynamic metadata for multi-tenant projects
|
|
21
|
+
export async function generateMetadata() {
|
|
22
|
+
const headersList = await headers();
|
|
23
|
+
const hostname = headersList.get("host") || "localhost";
|
|
24
|
+
const context = extractProjectContext(hostname);
|
|
25
|
+
// Default metadata for non-multi-tenant (local development)
|
|
26
|
+
if (!context.isMultiTenant || !context.slug) {
|
|
27
|
+
return {
|
|
28
|
+
title: "Documentation",
|
|
29
|
+
description: "API Documentation and Reference",
|
|
30
|
+
icons: {
|
|
31
|
+
icon: '/icon.png',
|
|
32
|
+
shortcut: '/icon.png',
|
|
33
|
+
apple: '/icon.png'
|
|
34
|
+
}
|
|
35
|
+
};
|
|
24
36
|
}
|
|
25
|
-
|
|
37
|
+
// Fetch project data for multi-tenant sites
|
|
38
|
+
try {
|
|
39
|
+
const projectContent = await getProjectContent(context.slug);
|
|
40
|
+
if (projectContent) {
|
|
41
|
+
const docsConfig = JSON.parse(projectContent.docsJson);
|
|
42
|
+
const themeConfig = projectContent.themeJson ? JSON.parse(projectContent.themeJson) : null;
|
|
43
|
+
const title = docsConfig.name || "Documentation";
|
|
44
|
+
const description = docsConfig.description || `${title} - API Documentation and Reference`;
|
|
45
|
+
const logoUrl = themeConfig?.logo?.url ? `https://${context.slug}.devdoc.sh/api/assets/${context.slug}${themeConfig.logo.url.replace('/assets', '')}` : null;
|
|
46
|
+
const faviconUrl = docsConfig.favicon ? `https://${context.slug}.devdoc.sh/api/assets/${context.slug}${docsConfig.favicon.replace('/assets', '')}` : '/icon.png';
|
|
47
|
+
return {
|
|
48
|
+
title: {
|
|
49
|
+
default: title,
|
|
50
|
+
template: `%s | ${title}`
|
|
51
|
+
},
|
|
52
|
+
description,
|
|
53
|
+
icons: {
|
|
54
|
+
icon: faviconUrl,
|
|
55
|
+
shortcut: faviconUrl,
|
|
56
|
+
apple: faviconUrl
|
|
57
|
+
},
|
|
58
|
+
openGraph: {
|
|
59
|
+
title,
|
|
60
|
+
description,
|
|
61
|
+
siteName: title,
|
|
62
|
+
type: "website",
|
|
63
|
+
url: `https://${context.slug}.devdoc.sh`,
|
|
64
|
+
...logoUrl && {
|
|
65
|
+
images: [
|
|
66
|
+
{
|
|
67
|
+
url: logoUrl,
|
|
68
|
+
width: 1200,
|
|
69
|
+
height: 630,
|
|
70
|
+
alt: title
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
twitter: {
|
|
76
|
+
card: logoUrl ? "summary_large_image" : "summary",
|
|
77
|
+
title,
|
|
78
|
+
description,
|
|
79
|
+
...logoUrl && {
|
|
80
|
+
images: [
|
|
81
|
+
logoUrl
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("[Metadata] Error fetching project:", error);
|
|
89
|
+
}
|
|
90
|
+
// Fallback metadata
|
|
91
|
+
return {
|
|
92
|
+
title: "Documentation",
|
|
93
|
+
description: "API Documentation and Reference",
|
|
94
|
+
icons: {
|
|
95
|
+
icon: '/icon.png',
|
|
96
|
+
shortcut: '/icon.png',
|
|
97
|
+
apple: '/icon.png'
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
26
101
|
export const viewport = {
|
|
27
102
|
width: "device-width",
|
|
28
103
|
initialScale: 1,
|
|
@@ -6,6 +6,7 @@ import { cn } from '@/lib/utils';
|
|
|
6
6
|
import { ArrowRight, ArrowSquareOut } from '@phosphor-icons/react';
|
|
7
7
|
import * as PhosphorIcons from '@phosphor-icons/react';
|
|
8
8
|
import { useDocsNavigation } from '@/lib/docs-navigation-context';
|
|
9
|
+
import { resolveLink, isExternalLink } from '@/lib/docs-link-utils';
|
|
9
10
|
// Dynamic icon resolver for Phosphor icons
|
|
10
11
|
function getIcon(iconName) {
|
|
11
12
|
if (!iconName) return null;
|
|
@@ -44,41 +45,20 @@ export function Card({ title, children, icon, iconSize = 'md', href, horizontal
|
|
|
44
45
|
const Icon = isIconString ? getIcon(icon) : null;
|
|
45
46
|
const hasCustomIcon = !isIconString && icon != null;
|
|
46
47
|
const sizeConfig = ICON_SIZES[iconSize];
|
|
47
|
-
const isExternal = href
|
|
48
|
+
const isExternal = href ? isExternalLink(href) : false;
|
|
48
49
|
const docsNav = useDocsNavigation();
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return slug;
|
|
62
|
-
};
|
|
63
|
-
// Get tab ID from href
|
|
64
|
-
const getTabIdFromHref = (path)=>{
|
|
65
|
-
if (path === '/api-reference') return 'api-reference';
|
|
66
|
-
if (path === '/changelog') return 'changelog';
|
|
67
|
-
return 'docs';
|
|
68
|
-
};
|
|
69
|
-
// Handle click for internal navigation
|
|
70
|
-
const handleClick = (e)=>{
|
|
71
|
-
if (!href || !docsNav) return;
|
|
72
|
-
if (isTabSwitchLink) {
|
|
73
|
-
e.preventDefault();
|
|
74
|
-
const tabId = getTabIdFromHref(href);
|
|
75
|
-
docsNav.switchToTab(tabId);
|
|
76
|
-
} else if (isInternalDocsLink) {
|
|
77
|
-
e.preventDefault();
|
|
78
|
-
const slug = getSlugFromHref(href);
|
|
79
|
-
docsNav.navigateToPage(slug);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
50
|
+
// Resolve link using shared utility
|
|
51
|
+
const resolvedHref = React.useMemo(()=>{
|
|
52
|
+
if (!href) return href;
|
|
53
|
+
return resolveLink(href, {
|
|
54
|
+
activeTab: docsNav?.activeTab,
|
|
55
|
+
tabIds: docsNav?.tabIds
|
|
56
|
+
});
|
|
57
|
+
}, [
|
|
58
|
+
href,
|
|
59
|
+
docsNav?.activeTab,
|
|
60
|
+
docsNav?.tabIds
|
|
61
|
+
]);
|
|
82
62
|
const showArrow = arrow ?? isExternal;
|
|
83
63
|
const cardContent = /*#__PURE__*/ _jsxs("div", {
|
|
84
64
|
className: cn('docs-card group relative rounded-lg border border-border bg-transparent p-4 transition-all h-full', href && 'hover:border-primary/50 hover:bg-muted/30 cursor-pointer', horizontal ? 'docs-card-horizontal flex items-start gap-4' : 'flex flex-col justify-start', className),
|
|
@@ -141,18 +121,9 @@ export function Card({ title, children, icon, iconSize = 'md', href, horizontal
|
|
|
141
121
|
children: cardContent
|
|
142
122
|
});
|
|
143
123
|
}
|
|
144
|
-
//
|
|
145
|
-
if (isInternalDocsLink || isTabSwitchLink) {
|
|
146
|
-
return /*#__PURE__*/ _jsx("a", {
|
|
147
|
-
href: href,
|
|
148
|
-
onClick: handleClick,
|
|
149
|
-
className: "docs-card-link block h-full cursor-pointer",
|
|
150
|
-
children: cardContent
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
// Standard Next.js Link for all other internal links
|
|
124
|
+
// Standard Next.js Link for all internal links (using resolved href)
|
|
154
125
|
return /*#__PURE__*/ _jsx(Link, {
|
|
155
|
-
href: href,
|
|
126
|
+
href: resolvedHref || href,
|
|
156
127
|
className: "docs-card-link block h-full",
|
|
157
128
|
children: cardContent
|
|
158
129
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
import { File as FileIcon, Folder as FolderIcon, FolderOpen as FolderOpenIcon } from '@phosphor-icons/react';
|
|
6
|
+
// Context to track nesting depth
|
|
7
|
+
const DepthContext = /*#__PURE__*/ React.createContext(0);
|
|
8
|
+
export function FileTree({ children, className }) {
|
|
9
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
10
|
+
className: cn('file-tree my-4 rounded-lg border border-border bg-muted/30 p-4 font-mono text-sm', className),
|
|
11
|
+
children: /*#__PURE__*/ _jsx(DepthContext.Provider, {
|
|
12
|
+
value: 0,
|
|
13
|
+
children: children
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function Folder({ name, children, defaultOpen = true, comment }) {
|
|
18
|
+
const [isOpen, setIsOpen] = React.useState(defaultOpen);
|
|
19
|
+
const depth = React.useContext(DepthContext);
|
|
20
|
+
const childArray = React.Children.toArray(children);
|
|
21
|
+
const hasChildren = childArray.length > 0;
|
|
22
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
23
|
+
className: "file-tree-folder",
|
|
24
|
+
children: [
|
|
25
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
26
|
+
className: cn('flex items-center gap-2 py-0.5 hover:bg-muted/50 rounded cursor-pointer', depth > 0 && 'ml-4'),
|
|
27
|
+
style: {
|
|
28
|
+
paddingLeft: depth > 0 ? `${(depth - 1) * 16}px` : 0
|
|
29
|
+
},
|
|
30
|
+
onClick: ()=>hasChildren && setIsOpen(!isOpen),
|
|
31
|
+
children: [
|
|
32
|
+
depth > 0 && /*#__PURE__*/ _jsx("span", {
|
|
33
|
+
className: "text-muted-foreground/50 select-none",
|
|
34
|
+
children: hasChildren ? isOpen ? '▼' : '▶' : '─'
|
|
35
|
+
}),
|
|
36
|
+
isOpen && hasChildren ? /*#__PURE__*/ _jsx(FolderOpenIcon, {
|
|
37
|
+
className: "h-4 w-4 text-primary shrink-0",
|
|
38
|
+
weight: "fill"
|
|
39
|
+
}) : /*#__PURE__*/ _jsx(FolderIcon, {
|
|
40
|
+
className: "h-4 w-4 text-primary shrink-0",
|
|
41
|
+
weight: "fill"
|
|
42
|
+
}),
|
|
43
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
44
|
+
className: "text-foreground",
|
|
45
|
+
children: [
|
|
46
|
+
name,
|
|
47
|
+
"/"
|
|
48
|
+
]
|
|
49
|
+
}),
|
|
50
|
+
comment && /*#__PURE__*/ _jsxs("span", {
|
|
51
|
+
className: "text-muted-foreground ml-2",
|
|
52
|
+
children: [
|
|
53
|
+
"# ",
|
|
54
|
+
comment
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
}),
|
|
59
|
+
isOpen && hasChildren && /*#__PURE__*/ _jsx(DepthContext.Provider, {
|
|
60
|
+
value: depth + 1,
|
|
61
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
62
|
+
className: "file-tree-children border-l border-border/50 ml-2",
|
|
63
|
+
children: children
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// Alias for MDX usage
|
|
70
|
+
export { Folder as TreeFolder };
|
|
71
|
+
export function File({ name, comment }) {
|
|
72
|
+
const depth = React.useContext(DepthContext);
|
|
73
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
74
|
+
className: cn('flex items-center gap-2 py-0.5 hover:bg-muted/50 rounded', depth > 0 && 'ml-4'),
|
|
75
|
+
style: {
|
|
76
|
+
paddingLeft: depth > 0 ? `${(depth - 1) * 16}px` : 0
|
|
77
|
+
},
|
|
78
|
+
children: [
|
|
79
|
+
depth > 0 && /*#__PURE__*/ _jsx("span", {
|
|
80
|
+
className: "text-muted-foreground/50 select-none",
|
|
81
|
+
children: "─"
|
|
82
|
+
}),
|
|
83
|
+
/*#__PURE__*/ _jsx(FileIcon, {
|
|
84
|
+
className: "h-4 w-4 text-muted-foreground shrink-0",
|
|
85
|
+
weight: "regular"
|
|
86
|
+
}),
|
|
87
|
+
/*#__PURE__*/ _jsx("span", {
|
|
88
|
+
className: "text-foreground",
|
|
89
|
+
children: name
|
|
90
|
+
}),
|
|
91
|
+
comment && /*#__PURE__*/ _jsxs("span", {
|
|
92
|
+
className: "text-muted-foreground ml-2",
|
|
93
|
+
children: [
|
|
94
|
+
"# ",
|
|
95
|
+
comment
|
|
96
|
+
]
|
|
97
|
+
})
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Alias for MDX usage
|
|
102
|
+
export { File as TreeFile };
|
|
@@ -31,6 +31,8 @@ export { Badge } from './badge';
|
|
|
31
31
|
export { Mermaid } from './mermaid';
|
|
32
32
|
// File Embeds
|
|
33
33
|
export { PDF, Audio, Download } from './file-embeds';
|
|
34
|
+
// File Tree
|
|
35
|
+
export { FileTree, TreeFolder, TreeFile } from './file-tree';
|
|
34
36
|
// Changelog
|
|
35
37
|
export { ChangelogEntry, ChangelogTitle, ChangelogDescription, ChangelogImage, BreakingChange, MoreUpdates, UpdateItem } from './changelog';
|
|
36
38
|
// Landing Page Components
|
|
@@ -53,6 +55,7 @@ import { Highlight, Marker, Underline } from './highlight';
|
|
|
53
55
|
import { Badge } from './badge';
|
|
54
56
|
import { Mermaid } from './mermaid';
|
|
55
57
|
import { PDF, Audio, Download } from './file-embeds';
|
|
58
|
+
import { FileTree, TreeFolder, TreeFile } from './file-tree';
|
|
56
59
|
import { ChangelogEntry, ChangelogTitle, ChangelogDescription, ChangelogImage, BreakingChange, MoreUpdates, UpdateItem } from './changelog';
|
|
57
60
|
import { Hero, Pre, Tagline, Headline, Description, CommandBox, Section, Center, FeatureGrid, FeatureItem, ButtonLink, Spacer, Divider } from './landing';
|
|
58
61
|
export const mdxComponents = {
|
|
@@ -108,6 +111,10 @@ export const mdxComponents = {
|
|
|
108
111
|
PDF,
|
|
109
112
|
Audio,
|
|
110
113
|
Download,
|
|
114
|
+
// File Tree
|
|
115
|
+
FileTree,
|
|
116
|
+
Folder: TreeFolder,
|
|
117
|
+
File: TreeFile,
|
|
111
118
|
// Changelog
|
|
112
119
|
ChangelogEntry,
|
|
113
120
|
ChangelogTitle,
|
|
@@ -6,7 +6,7 @@ import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } fro
|
|
|
6
6
|
import { ArrowUp, Square, Image as ImageIcon, X } from '@phosphor-icons/react';
|
|
7
7
|
import { Button } from '@/components/ui/button';
|
|
8
8
|
import { encodeCardMessage, detectErrorType } from './cards';
|
|
9
|
-
import {
|
|
9
|
+
import { searchEndpoints, findRequestById, formatRequestForAI } from '@/lib/api-docs/agent/indexer';
|
|
10
10
|
import { ChatMessage, TypingIndicator } from './chat-message';
|
|
11
11
|
import { cn } from '@/lib/utils';
|
|
12
12
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
|
@@ -130,7 +130,7 @@ import { useMobile } from '@/lib/api-docs/mobile-context';
|
|
|
130
130
|
}
|
|
131
131
|
// Storage key for chat history
|
|
132
132
|
const CHAT_STORAGE_KEY = 'brainfish-agent-chat';
|
|
133
|
-
export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill, apiSummary, debugContext, onDebugContextConsumed, explainContext, onExplainContextConsumed, onOpenGlobalAuth, onNavigateToAuthTab, onNavigateToParamsTab, onNavigateToBodyTab, onNavigateToHeadersTab, onNavigateToDocSection, onNavigateToDocPage, onHasMessagesChange }) {
|
|
133
|
+
export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill, onPrefillGraphQLVariables, graphqlPlaygroundState, apiSummary, debugContext, onDebugContextConsumed, explainContext, onExplainContextConsumed, onOpenGlobalAuth, onNavigateToAuthTab, onNavigateToParamsTab, onNavigateToBodyTab, onNavigateToHeadersTab, onNavigateToDocSection, onNavigateToDocPage, onHasMessagesChange }) {
|
|
134
134
|
const messagesEndRef = useRef(null);
|
|
135
135
|
const textareaRef = useRef(null);
|
|
136
136
|
const processedDebugRef = useRef(null);
|
|
@@ -145,16 +145,21 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
145
145
|
const apiSpecUrl = process.env.NEXT_PUBLIC_OPENAPI_URL || collection.name || 'default';
|
|
146
146
|
const { workspace } = useWorkspace(apiSpecUrl, collection.name);
|
|
147
147
|
const { notes, createNote, updateNote, deleteNote, deleteAllNotes, refresh: refreshNotes } = useNotes(workspace?.id || null);
|
|
148
|
-
//
|
|
149
|
-
const endpointIndex = useMemo(()=>
|
|
150
|
-
collection
|
|
148
|
+
// Use endpoint index from collection (pre-built by Collections API)
|
|
149
|
+
const endpointIndex = useMemo(()=>collection.endpointIndex || [], [
|
|
150
|
+
collection.endpointIndex
|
|
151
151
|
]);
|
|
152
|
-
//
|
|
153
|
-
const
|
|
154
|
-
currentEndpoint: currentEndpoint ? {
|
|
152
|
+
// Memoize currentEndpoint for suggestions to prevent unnecessary re-fetches
|
|
153
|
+
const currentEndpointForSuggestions = useMemo(()=>currentEndpoint ? {
|
|
155
154
|
id: currentEndpoint.id,
|
|
156
155
|
name: currentEndpoint.name
|
|
157
|
-
} : null,
|
|
156
|
+
} : null, [
|
|
157
|
+
currentEndpoint?.id,
|
|
158
|
+
currentEndpoint?.name
|
|
159
|
+
]);
|
|
160
|
+
// Suggestions - use shared hook
|
|
161
|
+
const { suggestions, isLoading: loadingSuggestions } = useSuggestions({
|
|
162
|
+
currentEndpoint: currentEndpointForSuggestions,
|
|
158
163
|
endpointIndex
|
|
159
164
|
});
|
|
160
165
|
const [showSuggestions, setShowSuggestions] = useState(true);
|
|
@@ -356,6 +361,9 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
356
361
|
const nameLower = ep.name.toLowerCase();
|
|
357
362
|
const pathLower = ep.path.toLowerCase();
|
|
358
363
|
const descLower = (ep.description || '').toLowerCase();
|
|
364
|
+
const paramsLower = ep.parameters.map((p)=>p.toLowerCase());
|
|
365
|
+
const tagsLower = ep.tags.map((t)=>t.toLowerCase());
|
|
366
|
+
const isGraphQL = ep.type === 'graphql';
|
|
359
367
|
if (nameLower.includes(queryLower)) score += 100;
|
|
360
368
|
if (pathLower.includes(queryLower)) score += 80;
|
|
361
369
|
if (descLower.includes(queryLower)) score += 60;
|
|
@@ -364,15 +372,29 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
364
372
|
if (pathLower.includes(token)) score += 25;
|
|
365
373
|
if (descLower.includes(token)) score += 15;
|
|
366
374
|
if (ep.method.toLowerCase() === token) score += 30;
|
|
375
|
+
// GraphQL-specific scoring: match operation type keywords
|
|
376
|
+
if (isGraphQL) {
|
|
377
|
+
const opType = ep.operationType?.toLowerCase();
|
|
378
|
+
if (opType === token || token === 'queries' && opType === 'query' || token === 'mutations' && opType === 'mutation' || token === 'subscriptions' && opType === 'subscription') {
|
|
379
|
+
score += 40;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Match parameters/variables
|
|
383
|
+
if (paramsLower.some((p)=>p.includes(token))) score += 25;
|
|
384
|
+
// Match tags
|
|
385
|
+
if (tagsLower.some((t)=>t.includes(token))) score += 20;
|
|
367
386
|
}
|
|
368
387
|
if (score > 0) {
|
|
388
|
+
// For GraphQL, show operation type instead of HTTP method
|
|
389
|
+
const displayMethod = isGraphQL ? ep.operationType?.toUpperCase() || 'GRAPHQL' : ep.method;
|
|
390
|
+
const displayPath = isGraphQL ? `${ep.operationType}:${ep.name}` : ep.path;
|
|
369
391
|
results.push({
|
|
370
392
|
type: 'endpoint',
|
|
371
393
|
id: ep.id,
|
|
372
394
|
title: ep.name,
|
|
373
395
|
description: ep.description || undefined,
|
|
374
|
-
method:
|
|
375
|
-
path:
|
|
396
|
+
method: displayMethod,
|
|
397
|
+
path: displayPath,
|
|
376
398
|
score
|
|
377
399
|
});
|
|
378
400
|
}
|
|
@@ -651,6 +673,20 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
651
673
|
}
|
|
652
674
|
});
|
|
653
675
|
}
|
|
676
|
+
if (toolName === 'prefill_graphql_variables') {
|
|
677
|
+
const { variables } = toolInput;
|
|
678
|
+
if (onPrefillGraphQLVariables) {
|
|
679
|
+
onPrefillGraphQLVariables(variables);
|
|
680
|
+
}
|
|
681
|
+
addToolOutput({
|
|
682
|
+
tool: 'prefill_graphql_variables',
|
|
683
|
+
toolCallId,
|
|
684
|
+
output: {
|
|
685
|
+
success: true,
|
|
686
|
+
variables
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
654
690
|
if (toolName === 'get_endpoint_details') {
|
|
655
691
|
const { endpointId } = toolInput;
|
|
656
692
|
const fullRequest = findRequestById(collection, endpointId);
|
|
@@ -700,6 +736,34 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
700
736
|
});
|
|
701
737
|
}
|
|
702
738
|
}
|
|
739
|
+
// Handle get_current_graphql_state tool - returns current GraphQL playground state
|
|
740
|
+
if (toolName === 'get_current_graphql_state') {
|
|
741
|
+
if (graphqlPlaygroundState) {
|
|
742
|
+
addToolOutput({
|
|
743
|
+
tool: 'get_current_graphql_state',
|
|
744
|
+
toolCallId,
|
|
745
|
+
output: {
|
|
746
|
+
success: true,
|
|
747
|
+
query: graphqlPlaygroundState.query,
|
|
748
|
+
variables: graphqlPlaygroundState.variables,
|
|
749
|
+
response: graphqlPlaygroundState.response,
|
|
750
|
+
responseTime: graphqlPlaygroundState.responseTime,
|
|
751
|
+
error: graphqlPlaygroundState.error,
|
|
752
|
+
isLoading: graphqlPlaygroundState.isLoading,
|
|
753
|
+
hasResponse: graphqlPlaygroundState.response !== null
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
} else {
|
|
757
|
+
addToolOutput({
|
|
758
|
+
tool: 'get_current_graphql_state',
|
|
759
|
+
toolCallId,
|
|
760
|
+
output: {
|
|
761
|
+
success: false,
|
|
762
|
+
error: 'No GraphQL playground state available. Navigate to a GraphQL operation first.'
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
703
767
|
// Handle switch_to_notes tool
|
|
704
768
|
if (toolName === 'switch_to_notes') {
|
|
705
769
|
switchToNotes();
|
|
@@ -1,16 +1,80 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback } from 'react';
|
|
3
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
4
4
|
import { Copy, Check, Trash } from '@phosphor-icons/react';
|
|
5
5
|
import { cn } from '@/lib/utils';
|
|
6
6
|
import { MarkdownRenderer } from '../../shared/markdown-renderer';
|
|
7
7
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
|
8
8
|
import { ToolCallDisplay } from './tool-call-display';
|
|
9
|
+
// Primary tools: search, navigate, endpoint operations - shown first
|
|
10
|
+
const PRIMARY_TOOLS = new Set([
|
|
11
|
+
'search',
|
|
12
|
+
'search_endpoints',
|
|
13
|
+
'search_docs',
|
|
14
|
+
'navigate',
|
|
15
|
+
'navigate_to_endpoint',
|
|
16
|
+
'navigate_to_doc_page',
|
|
17
|
+
'navigate_to_doc_section',
|
|
18
|
+
'scroll_to_section',
|
|
19
|
+
'get_endpoint_details',
|
|
20
|
+
'prefill_parameters',
|
|
21
|
+
'prefill_graphql_variables',
|
|
22
|
+
'check_request_validity',
|
|
23
|
+
'send_request',
|
|
24
|
+
'switch_to_docs',
|
|
25
|
+
'switch_to_api_client'
|
|
26
|
+
]);
|
|
27
|
+
// Secondary tools: notes/file operations - shown after primary tools
|
|
28
|
+
const SECONDARY_TOOLS = new Set([
|
|
29
|
+
'switch_to_notes',
|
|
30
|
+
'list_notes',
|
|
31
|
+
'create_folder',
|
|
32
|
+
'create_file',
|
|
33
|
+
'open_file',
|
|
34
|
+
'write_file',
|
|
35
|
+
'delete_file'
|
|
36
|
+
]);
|
|
9
37
|
export function AssistantMessage({ content, messageId, toolInvocations, isStreaming, onDeleteMessage, onNavigate, onNavigateToDocPage, onOpenFile }) {
|
|
38
|
+
// Separate tool invocations into primary (query/navigate) and secondary (notes) groups
|
|
39
|
+
const { primaryTools, secondaryTools } = useMemo(()=>{
|
|
40
|
+
if (!toolInvocations || toolInvocations.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
primaryTools: [],
|
|
43
|
+
secondaryTools: []
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const primary = [];
|
|
47
|
+
const secondary = [];
|
|
48
|
+
for (const tool of toolInvocations){
|
|
49
|
+
if (PRIMARY_TOOLS.has(tool.toolName)) {
|
|
50
|
+
primary.push(tool);
|
|
51
|
+
} else if (SECONDARY_TOOLS.has(tool.toolName)) {
|
|
52
|
+
secondary.push(tool);
|
|
53
|
+
} else {
|
|
54
|
+
// Unknown tools go to primary by default
|
|
55
|
+
primary.push(tool);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
primaryTools: primary,
|
|
60
|
+
secondaryTools: secondary
|
|
61
|
+
};
|
|
62
|
+
}, [
|
|
63
|
+
toolInvocations
|
|
64
|
+
]);
|
|
10
65
|
return /*#__PURE__*/ _jsxs("div", {
|
|
11
66
|
className: cn("aui-assistant-message-root", "fade-in slide-in-from-bottom-1 animate-in duration-150", "relative mx-auto w-full max-w-[var(--thread-max-width)] py-3"),
|
|
12
67
|
"data-role": "assistant",
|
|
13
68
|
children: [
|
|
69
|
+
primaryTools.length > 0 && /*#__PURE__*/ _jsx("div", {
|
|
70
|
+
className: "mb-2 px-2 space-y-1",
|
|
71
|
+
children: primaryTools.map((tool)=>/*#__PURE__*/ _jsx(ToolCallDisplay, {
|
|
72
|
+
tool: tool,
|
|
73
|
+
onNavigate: onNavigate,
|
|
74
|
+
onNavigateToDocPage: onNavigateToDocPage,
|
|
75
|
+
onOpenFile: onOpenFile
|
|
76
|
+
}, tool.toolCallId))
|
|
77
|
+
}),
|
|
14
78
|
content && /*#__PURE__*/ _jsx("div", {
|
|
15
79
|
className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed",
|
|
16
80
|
children: /*#__PURE__*/ _jsx("div", {
|
|
@@ -20,9 +84,9 @@ export function AssistantMessage({ content, messageId, toolInvocations, isStream
|
|
|
20
84
|
})
|
|
21
85
|
})
|
|
22
86
|
}),
|
|
23
|
-
|
|
87
|
+
secondaryTools.length > 0 && /*#__PURE__*/ _jsx("div", {
|
|
24
88
|
className: "mt-2 px-2 space-y-1",
|
|
25
|
-
children:
|
|
89
|
+
children: secondaryTools.map((tool)=>/*#__PURE__*/ _jsx(ToolCallDisplay, {
|
|
26
90
|
tool: tool,
|
|
27
91
|
onNavigate: onNavigate,
|
|
28
92
|
onNavigateToDocPage: onNavigateToDocPage,
|