@brainfish-ai/devdoc 0.1.47 → 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 +19 -13
- 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 +31 -8
- 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 +89 -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 +225 -59
- 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/mobile-context.js +10 -1
- 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,13 +6,14 @@ 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';
|
|
13
13
|
import { useModeContext, useNotes, useWorkspace } from '@/lib/api-docs/code-editor';
|
|
14
14
|
import { useCurrentRequestPayload, useSendTrigger } from '@/lib/api-docs/playground/context';
|
|
15
15
|
import { useSuggestions } from '@/lib/api-docs/agent/use-suggestions';
|
|
16
|
+
import { useMobile } from '@/lib/api-docs/mobile-context';
|
|
16
17
|
/**
|
|
17
18
|
* Custom transport that intercepts streaming events for write_file tool
|
|
18
19
|
* Wraps DefaultChatTransport and intercepts the stream to extract write_file content
|
|
@@ -129,7 +130,7 @@ import { useSuggestions } from '@/lib/api-docs/agent/use-suggestions';
|
|
|
129
130
|
}
|
|
130
131
|
// Storage key for chat history
|
|
131
132
|
const CHAT_STORAGE_KEY = 'brainfish-agent-chat';
|
|
132
|
-
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 }) {
|
|
133
134
|
const messagesEndRef = useRef(null);
|
|
134
135
|
const textareaRef = useRef(null);
|
|
135
136
|
const processedDebugRef = useRef(null);
|
|
@@ -144,16 +145,21 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
144
145
|
const apiSpecUrl = process.env.NEXT_PUBLIC_OPENAPI_URL || collection.name || 'default';
|
|
145
146
|
const { workspace } = useWorkspace(apiSpecUrl, collection.name);
|
|
146
147
|
const { notes, createNote, updateNote, deleteNote, deleteAllNotes, refresh: refreshNotes } = useNotes(workspace?.id || null);
|
|
147
|
-
//
|
|
148
|
-
const endpointIndex = useMemo(()=>
|
|
149
|
-
collection
|
|
148
|
+
// Use endpoint index from collection (pre-built by Collections API)
|
|
149
|
+
const endpointIndex = useMemo(()=>collection.endpointIndex || [], [
|
|
150
|
+
collection.endpointIndex
|
|
150
151
|
]);
|
|
151
|
-
//
|
|
152
|
-
const
|
|
153
|
-
currentEndpoint: currentEndpoint ? {
|
|
152
|
+
// Memoize currentEndpoint for suggestions to prevent unnecessary re-fetches
|
|
153
|
+
const currentEndpointForSuggestions = useMemo(()=>currentEndpoint ? {
|
|
154
154
|
id: currentEndpoint.id,
|
|
155
155
|
name: currentEndpoint.name
|
|
156
|
-
} : null,
|
|
156
|
+
} : null, [
|
|
157
|
+
currentEndpoint?.id,
|
|
158
|
+
currentEndpoint?.name
|
|
159
|
+
]);
|
|
160
|
+
// Suggestions - use shared hook
|
|
161
|
+
const { suggestions, isLoading: loadingSuggestions } = useSuggestions({
|
|
162
|
+
currentEndpoint: currentEndpointForSuggestions,
|
|
157
163
|
endpointIndex
|
|
158
164
|
});
|
|
159
165
|
const [showSuggestions, setShowSuggestions] = useState(true);
|
|
@@ -161,6 +167,8 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
161
167
|
const { currentRequestPayload } = useCurrentRequestPayload();
|
|
162
168
|
// Send trigger - to invoke playground's send button
|
|
163
169
|
const { requestSend } = useSendTrigger();
|
|
170
|
+
// Initial prompt from suggestion click
|
|
171
|
+
const { initialPrompt, clearInitialPrompt } = useMobile();
|
|
164
172
|
// Handler to open a file from chat (when user clicks on file name in tool display)
|
|
165
173
|
const handleOpenFile = useCallback((path)=>{
|
|
166
174
|
switchToNotes(path); // Pass path directly to avoid URL reset
|
|
@@ -353,6 +361,9 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
353
361
|
const nameLower = ep.name.toLowerCase();
|
|
354
362
|
const pathLower = ep.path.toLowerCase();
|
|
355
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';
|
|
356
367
|
if (nameLower.includes(queryLower)) score += 100;
|
|
357
368
|
if (pathLower.includes(queryLower)) score += 80;
|
|
358
369
|
if (descLower.includes(queryLower)) score += 60;
|
|
@@ -361,15 +372,29 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
361
372
|
if (pathLower.includes(token)) score += 25;
|
|
362
373
|
if (descLower.includes(token)) score += 15;
|
|
363
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;
|
|
364
386
|
}
|
|
365
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;
|
|
366
391
|
results.push({
|
|
367
392
|
type: 'endpoint',
|
|
368
393
|
id: ep.id,
|
|
369
394
|
title: ep.name,
|
|
370
395
|
description: ep.description || undefined,
|
|
371
|
-
method:
|
|
372
|
-
path:
|
|
396
|
+
method: displayMethod,
|
|
397
|
+
path: displayPath,
|
|
373
398
|
score
|
|
374
399
|
});
|
|
375
400
|
}
|
|
@@ -648,6 +673,20 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
648
673
|
}
|
|
649
674
|
});
|
|
650
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
|
+
}
|
|
651
690
|
if (toolName === 'get_endpoint_details') {
|
|
652
691
|
const { endpointId } = toolInput;
|
|
653
692
|
const fullRequest = findRequestById(collection, endpointId);
|
|
@@ -697,6 +736,34 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
697
736
|
});
|
|
698
737
|
}
|
|
699
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
|
+
}
|
|
700
767
|
// Handle switch_to_notes tool
|
|
701
768
|
if (toolName === 'switch_to_notes') {
|
|
702
769
|
switchToNotes();
|
|
@@ -1510,6 +1577,17 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
1510
1577
|
}, [
|
|
1511
1578
|
sendMessage
|
|
1512
1579
|
]);
|
|
1580
|
+
// Handle initial prompt from popup suggestion click
|
|
1581
|
+
useEffect(()=>{
|
|
1582
|
+
if (initialPrompt) {
|
|
1583
|
+
handleSuggestionClick(initialPrompt);
|
|
1584
|
+
clearInitialPrompt();
|
|
1585
|
+
}
|
|
1586
|
+
}, [
|
|
1587
|
+
initialPrompt,
|
|
1588
|
+
handleSuggestionClick,
|
|
1589
|
+
clearInitialPrompt
|
|
1590
|
+
]);
|
|
1513
1591
|
// Handle delete single message
|
|
1514
1592
|
const handleDeleteMessage = useCallback((messageId)=>{
|
|
1515
1593
|
setMessages((prevMessages)=>{
|
|
@@ -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,
|