@brainfish-ai/devdoc 0.1.43 → 0.1.45
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/create.js +2 -2
- package/package.json +1 -1
- package/renderer/app/api/collections/route.js +35 -4
- package/renderer/app/api/docs/route.js +9 -4
- package/renderer/app/api/suggestions/route.js +33 -13
- package/renderer/app/globals.css +69 -0
- package/renderer/app/layout.js +2 -2
- package/renderer/app/llms-full.txt/route.js +10 -1
- package/renderer/app/llms.txt/route.js +10 -1
- package/renderer/app/sitemap.xml/route.js +11 -1
- package/renderer/components/docs/mdx/cards.js +1 -1
- package/renderer/components/docs/mdx/landing.js +7 -5
- package/renderer/components/docs-viewer/agent/agent-chat.js +13 -112
- package/renderer/components/docs-viewer/agent/agent-popup-button.js +99 -0
- package/renderer/components/docs-viewer/agent/index.js +3 -0
- package/renderer/components/docs-viewer/content/content-router.js +182 -0
- package/renderer/components/docs-viewer/content/doc-page.js +73 -37
- package/renderer/components/docs-viewer/content/index.js +2 -0
- package/renderer/components/docs-viewer/content/mdx-error-boundary.js +184 -0
- package/renderer/components/docs-viewer/index.js +381 -485
- package/renderer/components/docs-viewer/playground/graphql-playground.js +205 -3
- package/renderer/components/docs-viewer/sidebar/right-sidebar.js +35 -39
- package/renderer/components/theme-toggle.js +1 -21
- package/renderer/hooks/use-route-state.js +159 -0
- package/renderer/lib/api-docs/agent/use-suggestions.js +97 -0
- package/renderer/lib/api-docs/code-editor/mode-context.js +61 -89
- package/renderer/lib/api-docs/mobile-context.js +40 -3
- package/renderer/lib/docs/config/environment.js +38 -0
- package/renderer/lib/docs/config/index.js +1 -0
- package/renderer/lib/docs/config/schema.js +17 -5
- package/renderer/lib/docs/mdx/compiler.js +5 -2
- package/renderer/lib/docs/mdx/index.js +2 -0
- package/renderer/lib/docs/mdx/remark-mermaid.js +63 -0
- package/renderer/lib/docs/navigation/index.js +1 -2
- package/renderer/lib/docs/navigation/types.js +3 -1
- package/renderer/lib/docs-navigation.js +140 -0
- package/renderer/package.json +1 -0
|
@@ -7,6 +7,7 @@ import matter from 'gray-matter';
|
|
|
7
7
|
*
|
|
8
8
|
* Generated from docs.json configuration.
|
|
9
9
|
* Combines all documentation into a single file for LLM consumption.
|
|
10
|
+
* Private tabs and groups are excluded from the output.
|
|
10
11
|
*/ const DOCS_DIR = join(process.cwd(), 'templates', 'starter');
|
|
11
12
|
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
|
12
13
|
function loadDocsConfig() {
|
|
@@ -72,6 +73,10 @@ function generateDocsContent(tab, lines) {
|
|
|
72
73
|
lines.push('');
|
|
73
74
|
if (tab.groups) {
|
|
74
75
|
for (const group of tab.groups){
|
|
76
|
+
// Skip private groups
|
|
77
|
+
if (group.visibility === 'private') {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
75
80
|
lines.push(`## ${group.group}`);
|
|
76
81
|
lines.push('');
|
|
77
82
|
for (const page of group.pages){
|
|
@@ -201,9 +206,13 @@ function generateLlmsFullTxt(config) {
|
|
|
201
206
|
lines.push('');
|
|
202
207
|
lines.push('---');
|
|
203
208
|
lines.push('');
|
|
204
|
-
// Process each tab from config
|
|
209
|
+
// Process each tab from config (skip private tabs)
|
|
205
210
|
if (config.navigation?.tabs) {
|
|
206
211
|
for (const tab of config.navigation.tabs){
|
|
212
|
+
// Skip private tabs - they should not appear in LLM index
|
|
213
|
+
if (tab.visibility === 'private') {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
207
216
|
switch(tab.type){
|
|
208
217
|
case 'docs':
|
|
209
218
|
generateDocsContent(tab, lines);
|
|
@@ -7,6 +7,7 @@ import matter from 'gray-matter';
|
|
|
7
7
|
*
|
|
8
8
|
* Generated from docs.json configuration.
|
|
9
9
|
* Helps LLMs understand and index your documentation structure.
|
|
10
|
+
* Private tabs and groups are excluded from the index.
|
|
10
11
|
*/ const DOCS_DIR = join(process.cwd(), 'templates', 'starter');
|
|
11
12
|
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
|
12
13
|
function loadDocsConfig() {
|
|
@@ -73,6 +74,10 @@ function generateDocsSection(tab, lines) {
|
|
|
73
74
|
lines.push('');
|
|
74
75
|
if (tab.groups) {
|
|
75
76
|
for (const group of tab.groups){
|
|
77
|
+
// Skip private groups
|
|
78
|
+
if (group.visibility === 'private') {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
76
81
|
lines.push(`### ${group.group}`);
|
|
77
82
|
lines.push('');
|
|
78
83
|
for (const page of group.pages){
|
|
@@ -163,9 +168,13 @@ function generateLlmsTxt(config) {
|
|
|
163
168
|
lines.push('');
|
|
164
169
|
lines.push('> Documentation index for LLMs. For full content, see /llms-full.txt');
|
|
165
170
|
lines.push('');
|
|
166
|
-
// Process each tab from config
|
|
171
|
+
// Process each tab from config (skip private tabs)
|
|
167
172
|
if (config.navigation?.tabs) {
|
|
168
173
|
for (const tab of config.navigation.tabs){
|
|
174
|
+
// Skip private tabs - they should not appear in LLM index
|
|
175
|
+
if (tab.visibility === 'private') {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
169
178
|
switch(tab.type){
|
|
170
179
|
case 'docs':
|
|
171
180
|
generateDocsSection(tab, lines);
|
|
@@ -5,6 +5,7 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
|
5
5
|
* sitemap.xml - Auto-generated sitemap for search engines
|
|
6
6
|
*
|
|
7
7
|
* Generated from docs.json configuration.
|
|
8
|
+
* Private tabs and groups are excluded from the sitemap.
|
|
8
9
|
*/ const DOCS_DIR = join(process.cwd(), 'templates', 'starter');
|
|
9
10
|
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
|
|
10
11
|
function loadDocsConfig() {
|
|
@@ -53,11 +54,19 @@ function generateSitemap(config) {
|
|
|
53
54
|
changefreq: 'weekly',
|
|
54
55
|
priority: 1.0
|
|
55
56
|
});
|
|
56
|
-
// Process navigation tabs
|
|
57
|
+
// Process navigation tabs (skip private tabs)
|
|
57
58
|
if (config.navigation?.tabs) {
|
|
58
59
|
for (const tab of config.navigation.tabs){
|
|
60
|
+
// Skip private tabs - they should not appear in sitemap
|
|
61
|
+
if (tab.visibility === 'private') {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
59
64
|
if (tab.type === 'docs' && tab.groups) {
|
|
60
65
|
for (const group of tab.groups){
|
|
66
|
+
// Skip private groups
|
|
67
|
+
if (group.visibility === 'private') {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
61
70
|
for (const page of group.pages){
|
|
62
71
|
const lastmod = getFileLastModified(page);
|
|
63
72
|
const urlPath = page === 'index' ? '' : page;
|
|
@@ -93,6 +102,7 @@ function generateSitemap(config) {
|
|
|
93
102
|
});
|
|
94
103
|
}
|
|
95
104
|
}
|
|
105
|
+
// Note: graphql tabs are skipped since they don't have individual pages
|
|
96
106
|
}
|
|
97
107
|
}
|
|
98
108
|
// Generate XML
|
|
@@ -81,7 +81,7 @@ export function Card({ title, children, icon, iconSize = 'md', href, horizontal
|
|
|
81
81
|
};
|
|
82
82
|
const showArrow = arrow ?? isExternal;
|
|
83
83
|
const cardContent = /*#__PURE__*/ _jsxs("div", {
|
|
84
|
-
className: cn('docs-card group relative rounded-lg border border-border bg-
|
|
84
|
+
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),
|
|
85
85
|
children: [
|
|
86
86
|
img && !horizontal && /*#__PURE__*/ _jsx("div", {
|
|
87
87
|
className: "mb-4 -mx-4 -mt-4 overflow-hidden rounded-t-lg",
|
|
@@ -12,13 +12,14 @@ const iconMap = {
|
|
|
12
12
|
'arrow-up-right': ArrowUpRight,
|
|
13
13
|
'rocket-launch': RocketLaunch
|
|
14
14
|
};
|
|
15
|
-
export function Hero({ children, className, variant = '
|
|
15
|
+
export function Hero({ children, className, variant = 'transparent', align = 'center', minHeight = 'md' }) {
|
|
16
16
|
const variantStyles = {
|
|
17
17
|
default: 'bg-background text-foreground',
|
|
18
18
|
gradient: 'bg-gradient-to-b from-background via-background to-muted/30 text-foreground',
|
|
19
19
|
dark: 'bg-zinc-950 text-white [&_.landing-tagline]:text-zinc-400 [&_.landing-headline]:text-white [&_.landing-description]:text-zinc-300 [&_.landing-button]:border-zinc-600',
|
|
20
20
|
light: 'bg-white text-zinc-900 [&_.landing-tagline]:text-zinc-500 [&_.landing-headline]:text-zinc-900 [&_.landing-description]:text-zinc-600 [&_.landing-button]:border-zinc-300',
|
|
21
|
-
pattern: 'bg-background bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-primary/10 via-background to-background text-foreground'
|
|
21
|
+
pattern: 'bg-background bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-primary/10 via-background to-background text-foreground',
|
|
22
|
+
transparent: 'bg-transparent text-foreground'
|
|
22
23
|
};
|
|
23
24
|
const alignStyles = {
|
|
24
25
|
top: 'items-start pt-12',
|
|
@@ -189,13 +190,14 @@ export function CommandBox({ command, prefix = '$', className, variant = 'defaul
|
|
|
189
190
|
]
|
|
190
191
|
});
|
|
191
192
|
}
|
|
192
|
-
export function Section({ children, className, id, variant = '
|
|
193
|
+
export function Section({ children, className, id, variant = 'transparent', padding = 'lg' }) {
|
|
193
194
|
const variantStyles = {
|
|
194
195
|
default: 'bg-background text-foreground',
|
|
195
196
|
muted: 'bg-muted/30 text-foreground',
|
|
196
197
|
dark: 'bg-zinc-950 text-white [&_.landing-tagline]:text-zinc-400 [&_.landing-headline]:text-white [&_.landing-description]:text-zinc-300 [&_.landing-feature-title]:text-white [&_.landing-feature-description]:text-zinc-400 [&_.landing-button]:border-zinc-600',
|
|
197
198
|
light: 'bg-white text-zinc-900 [&_.landing-tagline]:text-zinc-500 [&_.landing-headline]:text-zinc-900 [&_.landing-description]:text-zinc-600 [&_.landing-feature-title]:text-zinc-900 [&_.landing-feature-description]:text-zinc-600 [&_.landing-button]:border-zinc-300',
|
|
198
|
-
accent: 'bg-primary/5 text-foreground'
|
|
199
|
+
accent: 'bg-primary/5 text-foreground',
|
|
200
|
+
transparent: 'bg-transparent text-foreground'
|
|
199
201
|
};
|
|
200
202
|
const paddingStyles = {
|
|
201
203
|
none: '',
|
|
@@ -238,7 +240,7 @@ export function FeatureGrid({ children, className, cols = 3 }) {
|
|
|
238
240
|
}
|
|
239
241
|
export function FeatureItem({ children, title, icon, className }) {
|
|
240
242
|
return /*#__PURE__*/ _jsxs("div", {
|
|
241
|
-
className: cn('landing-feature-item p-6 rounded-xl border border-border/50 bg-
|
|
243
|
+
className: cn('landing-feature-item p-6 rounded-xl border border-border/50 bg-transparent', 'hover:border-primary/30 hover:bg-muted/30 transition-colors', className),
|
|
242
244
|
children: [
|
|
243
245
|
icon && /*#__PURE__*/ _jsx("div", {
|
|
244
246
|
className: "landing-feature-icon mb-4 text-primary text-2xl",
|
|
@@ -12,6 +12,7 @@ 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
|
+
import { useSuggestions } from '@/lib/api-docs/agent/use-suggestions';
|
|
15
16
|
/**
|
|
16
17
|
* Custom transport that intercepts streaming events for write_file tool
|
|
17
18
|
* Wraps DefaultChatTransport and intercepts the stream to extract write_file content
|
|
@@ -126,35 +127,6 @@ import { useCurrentRequestPayload, useSendTrigger } from '@/lib/api-docs/playgro
|
|
|
126
127
|
};
|
|
127
128
|
return transport;
|
|
128
129
|
}
|
|
129
|
-
// Client-side cache for suggestions
|
|
130
|
-
const SUGGESTIONS_CACHE_KEY = 'brainfish-suggestions-cache';
|
|
131
|
-
function getSuggestionsFromCache(key) {
|
|
132
|
-
try {
|
|
133
|
-
const cached = localStorage.getItem(SUGGESTIONS_CACHE_KEY);
|
|
134
|
-
if (cached) {
|
|
135
|
-
const data = JSON.parse(cached);
|
|
136
|
-
if (data[key] && Date.now() - data[key].timestamp < 1000 * 60 * 60) {
|
|
137
|
-
return data[key].suggestions;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
} catch {
|
|
141
|
-
// Ignore cache errors
|
|
142
|
-
}
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
function saveSuggestionsToCache(key, suggestions) {
|
|
146
|
-
try {
|
|
147
|
-
const cached = localStorage.getItem(SUGGESTIONS_CACHE_KEY);
|
|
148
|
-
const data = cached ? JSON.parse(cached) : {};
|
|
149
|
-
data[key] = {
|
|
150
|
-
suggestions,
|
|
151
|
-
timestamp: Date.now()
|
|
152
|
-
};
|
|
153
|
-
localStorage.setItem(SUGGESTIONS_CACHE_KEY, JSON.stringify(data));
|
|
154
|
-
} catch {
|
|
155
|
-
// Ignore cache errors
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
130
|
// Storage key for chat history
|
|
159
131
|
const CHAT_STORAGE_KEY = 'brainfish-agent-chat';
|
|
160
132
|
export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill, apiSummary, debugContext, onDebugContextConsumed, explainContext, onExplainContextConsumed, onOpenGlobalAuth, onNavigateToAuthTab, onNavigateToParamsTab, onNavigateToBodyTab, onNavigateToHeadersTab, onNavigateToDocSection, onNavigateToDocPage, onHasMessagesChange }) {
|
|
@@ -172,18 +144,23 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
172
144
|
const apiSpecUrl = process.env.NEXT_PUBLIC_OPENAPI_URL || collection.name || 'default';
|
|
173
145
|
const { workspace } = useWorkspace(apiSpecUrl, collection.name);
|
|
174
146
|
const { notes, createNote, updateNote, deleteNote, deleteAllNotes, refresh: refreshNotes } = useNotes(workspace?.id || null);
|
|
175
|
-
//
|
|
176
|
-
const
|
|
177
|
-
|
|
147
|
+
// Build endpoint index for search
|
|
148
|
+
const endpointIndex = useMemo(()=>buildEndpointIndex(collection), [
|
|
149
|
+
collection
|
|
150
|
+
]);
|
|
151
|
+
// Suggestions - use shared hook
|
|
152
|
+
const { suggestions, isLoading: loadingSuggestions } = useSuggestions({
|
|
153
|
+
currentEndpoint: currentEndpoint ? {
|
|
154
|
+
id: currentEndpoint.id,
|
|
155
|
+
name: currentEndpoint.name
|
|
156
|
+
} : null,
|
|
157
|
+
endpointIndex
|
|
158
|
+
});
|
|
178
159
|
const [showSuggestions, setShowSuggestions] = useState(true);
|
|
179
160
|
// Current request payload from playground
|
|
180
161
|
const { currentRequestPayload } = useCurrentRequestPayload();
|
|
181
162
|
// Send trigger - to invoke playground's send button
|
|
182
163
|
const { requestSend } = useSendTrigger();
|
|
183
|
-
// Build endpoint index for search
|
|
184
|
-
const endpointIndex = useMemo(()=>buildEndpointIndex(collection), [
|
|
185
|
-
collection
|
|
186
|
-
]);
|
|
187
164
|
// Handler to open a file from chat (when user clicks on file name in tool display)
|
|
188
165
|
const handleOpenFile = useCallback((path)=>{
|
|
189
166
|
switchToNotes(path); // Pass path directly to avoid URL reset
|
|
@@ -1263,82 +1240,6 @@ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill,
|
|
|
1263
1240
|
}, [
|
|
1264
1241
|
messages
|
|
1265
1242
|
]);
|
|
1266
|
-
// Cache key for suggestions
|
|
1267
|
-
const suggestionsCacheKey = currentEndpoint?.id ? `endpoint:${currentEndpoint.id}` : `general:${endpointIndex.length}`;
|
|
1268
|
-
// Load dynamic suggestions from API
|
|
1269
|
-
useEffect(()=>{
|
|
1270
|
-
// Check cache first
|
|
1271
|
-
const cached = getSuggestionsFromCache(suggestionsCacheKey);
|
|
1272
|
-
if (cached) {
|
|
1273
|
-
setSuggestions(cached);
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
// Fetch from API
|
|
1277
|
-
let cancelled = false;
|
|
1278
|
-
setLoadingSuggestions(true);
|
|
1279
|
-
fetch('/api/suggestions', {
|
|
1280
|
-
method: 'POST',
|
|
1281
|
-
headers: {
|
|
1282
|
-
'Content-Type': 'application/json'
|
|
1283
|
-
},
|
|
1284
|
-
body: JSON.stringify({
|
|
1285
|
-
endpointIndex,
|
|
1286
|
-
currentEndpointId: currentEndpoint?.id
|
|
1287
|
-
})
|
|
1288
|
-
}).then((res)=>res.json()).then((data)=>{
|
|
1289
|
-
if (cancelled) return;
|
|
1290
|
-
if (data.suggestions) {
|
|
1291
|
-
setSuggestions(data.suggestions);
|
|
1292
|
-
saveSuggestionsToCache(suggestionsCacheKey, data.suggestions);
|
|
1293
|
-
}
|
|
1294
|
-
}).catch((err)=>{
|
|
1295
|
-
if (cancelled) return;
|
|
1296
|
-
console.warn('[AgentChat] Failed to fetch suggestions:', err);
|
|
1297
|
-
// Fallback suggestions
|
|
1298
|
-
const fallback = currentEndpoint ? [
|
|
1299
|
-
{
|
|
1300
|
-
title: 'What does this',
|
|
1301
|
-
label: 'endpoint do?',
|
|
1302
|
-
prompt: `What does ${currentEndpoint.name} do?`
|
|
1303
|
-
},
|
|
1304
|
-
{
|
|
1305
|
-
title: 'What parameters',
|
|
1306
|
-
label: 'are required?',
|
|
1307
|
-
prompt: 'What parameters are required?'
|
|
1308
|
-
},
|
|
1309
|
-
{
|
|
1310
|
-
title: 'Python example',
|
|
1311
|
-
label: 'Show code',
|
|
1312
|
-
prompt: 'Show me a Python example'
|
|
1313
|
-
}
|
|
1314
|
-
] : [
|
|
1315
|
-
{
|
|
1316
|
-
title: 'Find an endpoint',
|
|
1317
|
-
label: 'to create resources',
|
|
1318
|
-
prompt: 'Find endpoints for creating resources'
|
|
1319
|
-
},
|
|
1320
|
-
{
|
|
1321
|
-
title: 'How do I',
|
|
1322
|
-
label: 'authenticate?',
|
|
1323
|
-
prompt: 'How do I authenticate?'
|
|
1324
|
-
},
|
|
1325
|
-
{
|
|
1326
|
-
title: 'Overview',
|
|
1327
|
-
label: 'What can I do?',
|
|
1328
|
-
prompt: 'What can I do with this API?'
|
|
1329
|
-
}
|
|
1330
|
-
];
|
|
1331
|
-
setSuggestions(fallback);
|
|
1332
|
-
}).finally(()=>{
|
|
1333
|
-
if (!cancelled) setLoadingSuggestions(false);
|
|
1334
|
-
});
|
|
1335
|
-
return ()=>{
|
|
1336
|
-
cancelled = true;
|
|
1337
|
-
};
|
|
1338
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1339
|
-
}, [
|
|
1340
|
-
suggestionsCacheKey
|
|
1341
|
-
]);
|
|
1342
1243
|
// Track pending debug error type for showing options after AI response
|
|
1343
1244
|
const [pendingErrorType, setPendingErrorType] = useState(null);
|
|
1344
1245
|
const prevStatus = useRef(status);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { Sparkle, X } from '@phosphor-icons/react';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
import { useSuggestions } from '@/lib/api-docs/agent/use-suggestions';
|
|
8
|
+
export function AgentPopupButton({ onClick, onQuestionClick, className, currentEndpoint, endpointIndex = [] }) {
|
|
9
|
+
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
10
|
+
const [isDismissed, setIsDismissed] = useState(false);
|
|
11
|
+
// Use shared suggestions hook - limit to 3 for popup
|
|
12
|
+
const { suggestions, isLoading } = useSuggestions({
|
|
13
|
+
currentEndpoint,
|
|
14
|
+
endpointIndex,
|
|
15
|
+
limit: 3
|
|
16
|
+
});
|
|
17
|
+
// Show suggestions after a delay (only if not dismissed and we have suggestions)
|
|
18
|
+
useEffect(()=>{
|
|
19
|
+
if (isDismissed || isLoading || suggestions.length === 0) return;
|
|
20
|
+
const timer = setTimeout(()=>{
|
|
21
|
+
setShowSuggestions(true);
|
|
22
|
+
}, 1500) // 1.5 second delay
|
|
23
|
+
;
|
|
24
|
+
return ()=>clearTimeout(timer);
|
|
25
|
+
}, [
|
|
26
|
+
isDismissed,
|
|
27
|
+
isLoading,
|
|
28
|
+
suggestions.length
|
|
29
|
+
]);
|
|
30
|
+
// Reset dismissed state when endpoint changes
|
|
31
|
+
useEffect(()=>{
|
|
32
|
+
setIsDismissed(false);
|
|
33
|
+
setShowSuggestions(false);
|
|
34
|
+
}, [
|
|
35
|
+
currentEndpoint?.id
|
|
36
|
+
]);
|
|
37
|
+
const handleDismiss = (e)=>{
|
|
38
|
+
e.stopPropagation();
|
|
39
|
+
setShowSuggestions(false);
|
|
40
|
+
setIsDismissed(true);
|
|
41
|
+
};
|
|
42
|
+
const handleQuestionClick = (prompt)=>{
|
|
43
|
+
if (onQuestionClick) {
|
|
44
|
+
onQuestionClick(prompt);
|
|
45
|
+
} else {
|
|
46
|
+
onClick();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
50
|
+
className: "fixed bottom-6 right-6 z-50 flex flex-col items-end gap-2",
|
|
51
|
+
children: [
|
|
52
|
+
showSuggestions && suggestions.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
53
|
+
children: [
|
|
54
|
+
/*#__PURE__*/ _jsx("button", {
|
|
55
|
+
onClick: handleDismiss,
|
|
56
|
+
className: "size-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-muted transition-colors agent-suggestions-enter",
|
|
57
|
+
"aria-label": "Dismiss suggestions",
|
|
58
|
+
children: /*#__PURE__*/ _jsx(X, {
|
|
59
|
+
className: "size-3 text-muted-foreground",
|
|
60
|
+
weight: "bold"
|
|
61
|
+
})
|
|
62
|
+
}),
|
|
63
|
+
suggestions.map((suggestion, index)=>/*#__PURE__*/ _jsxs("button", {
|
|
64
|
+
onClick: ()=>handleQuestionClick(suggestion.prompt),
|
|
65
|
+
className: cn("text-sm px-4 py-2 rounded-full", "bg-background border border-border shadow-md", "hover:bg-muted hover:shadow-lg transition-all", "text-foreground", "agent-suggestion-item"),
|
|
66
|
+
style: {
|
|
67
|
+
animationDelay: `${(suggestions.length - index) * 80}ms`
|
|
68
|
+
},
|
|
69
|
+
children: [
|
|
70
|
+
/*#__PURE__*/ _jsx("span", {
|
|
71
|
+
className: "font-medium",
|
|
72
|
+
children: suggestion.title
|
|
73
|
+
}),
|
|
74
|
+
' ',
|
|
75
|
+
/*#__PURE__*/ _jsx("span", {
|
|
76
|
+
className: "text-muted-foreground",
|
|
77
|
+
children: suggestion.label
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
}, index))
|
|
81
|
+
]
|
|
82
|
+
}),
|
|
83
|
+
/*#__PURE__*/ _jsxs(Button, {
|
|
84
|
+
onClick: onClick,
|
|
85
|
+
className: cn("h-10 px-4 rounded-full", "bg-primary hover:bg-primary/90 text-primary-foreground", "shadow-md shadow-primary/20 hover:shadow-lg hover:shadow-primary/30", "transition-all duration-200", "agent-popup-enter", "gap-2 font-medium", className),
|
|
86
|
+
"aria-label": "Open AI Assistant",
|
|
87
|
+
children: [
|
|
88
|
+
/*#__PURE__*/ _jsx(Sparkle, {
|
|
89
|
+
className: "size-4",
|
|
90
|
+
weight: "fill"
|
|
91
|
+
}),
|
|
92
|
+
/*#__PURE__*/ _jsx("span", {
|
|
93
|
+
children: "Ask AI"
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -2,3 +2,6 @@
|
|
|
2
2
|
* Agent Components Exports
|
|
3
3
|
*/ export { AgentChat } from './agent-chat';
|
|
4
4
|
export { ChatMessage, TypingIndicator } from './chat-message';
|
|
5
|
+
export { AgentPopupButton } from './agent-popup-button';
|
|
6
|
+
// Re-export shared hook and types
|
|
7
|
+
export { useSuggestions } from '@/lib/api-docs/agent/use-suggestions';
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo, useRef, useEffect } from 'react';
|
|
4
|
+
import { Book } from '@phosphor-icons/react';
|
|
5
|
+
import { cn } from '@/lib/utils';
|
|
6
|
+
import { RequestDetails } from './request-details';
|
|
7
|
+
import { DocPage } from './doc-page';
|
|
8
|
+
import { ChangelogPage } from './changelog-page';
|
|
9
|
+
import { NotFoundPage } from './not-found-page';
|
|
10
|
+
import { GraphQLPlayground } from '../playground/graphql-playground';
|
|
11
|
+
/**
|
|
12
|
+
* Find a request by ID in the collection
|
|
13
|
+
*/ function findRequestById(collection, id) {
|
|
14
|
+
// Check direct requests
|
|
15
|
+
if (collection.requests) {
|
|
16
|
+
const found = collection.requests.find((r)=>r.id === id);
|
|
17
|
+
if (found) return found;
|
|
18
|
+
}
|
|
19
|
+
// Check in folders recursively
|
|
20
|
+
function searchFolders(folders) {
|
|
21
|
+
if (!folders) return null;
|
|
22
|
+
for (const folder of folders){
|
|
23
|
+
if (folder.requests) {
|
|
24
|
+
const found = folder.requests.find((r)=>r.id === id);
|
|
25
|
+
if (found) return found;
|
|
26
|
+
}
|
|
27
|
+
if (folder.folders) {
|
|
28
|
+
const found = searchFolders(folder.folders);
|
|
29
|
+
if (found) return found;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return searchFolders(collection.folders);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* ContentRouter - Renders content based on URL route state
|
|
38
|
+
*
|
|
39
|
+
* This component is the single place that determines what content to show
|
|
40
|
+
* based on the URL hash. It derives all state from the route.
|
|
41
|
+
*/ export function ContentRouter({ routeState, collection, activeTabConfig, isGraphQLTab, isChangelogTab, graphqlOperations, graphqlSchemaSDL, graphqlEndpoint, onSearch, contentRef }) {
|
|
42
|
+
const { contentType, contentId } = routeState;
|
|
43
|
+
const localContentRef = useRef(null);
|
|
44
|
+
const actualRef = contentRef || localContentRef;
|
|
45
|
+
// Derive content from route state
|
|
46
|
+
const derivedContent = useMemo(()=>{
|
|
47
|
+
// GraphQL operation
|
|
48
|
+
if (isGraphQLTab && contentType === 'endpoint' && contentId) {
|
|
49
|
+
const operation = graphqlOperations.find((op)=>op.id === contentId);
|
|
50
|
+
if (operation) {
|
|
51
|
+
return {
|
|
52
|
+
type: 'graphql',
|
|
53
|
+
operation
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// REST endpoint
|
|
58
|
+
if (contentType === 'endpoint' && contentId) {
|
|
59
|
+
const request = findRequestById(collection, contentId);
|
|
60
|
+
if (request) {
|
|
61
|
+
return {
|
|
62
|
+
type: 'endpoint',
|
|
63
|
+
request
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Endpoint not found
|
|
67
|
+
return {
|
|
68
|
+
type: 'not-found',
|
|
69
|
+
slug: `endpoint/${contentId}`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Doc page
|
|
73
|
+
if (contentType === 'page' && contentId) {
|
|
74
|
+
return {
|
|
75
|
+
type: 'page',
|
|
76
|
+
slug: contentId
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Section scroll (handled elsewhere, show page if we have one)
|
|
80
|
+
if (contentType === 'section' && contentId) {
|
|
81
|
+
// Section navigation usually means scroll within current page
|
|
82
|
+
// For now, treat as a doc lookup
|
|
83
|
+
return {
|
|
84
|
+
type: 'section',
|
|
85
|
+
sectionId: contentId
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Changelog tab
|
|
89
|
+
if (isChangelogTab) {
|
|
90
|
+
return {
|
|
91
|
+
type: 'changelog'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// Default empty state
|
|
95
|
+
return {
|
|
96
|
+
type: 'empty'
|
|
97
|
+
};
|
|
98
|
+
}, [
|
|
99
|
+
contentType,
|
|
100
|
+
contentId,
|
|
101
|
+
isGraphQLTab,
|
|
102
|
+
isChangelogTab,
|
|
103
|
+
graphqlOperations,
|
|
104
|
+
collection
|
|
105
|
+
]);
|
|
106
|
+
// Handle section scrolling
|
|
107
|
+
useEffect(()=>{
|
|
108
|
+
if (derivedContent.type === 'section' && 'sectionId' in derivedContent) {
|
|
109
|
+
const element = document.getElementById(derivedContent.sectionId);
|
|
110
|
+
if (element) {
|
|
111
|
+
setTimeout(()=>{
|
|
112
|
+
element.scrollIntoView({
|
|
113
|
+
behavior: 'smooth',
|
|
114
|
+
block: 'start'
|
|
115
|
+
});
|
|
116
|
+
}, 100);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, [
|
|
120
|
+
derivedContent
|
|
121
|
+
]);
|
|
122
|
+
// Determine if content needs flex layout (for playgrounds)
|
|
123
|
+
const needsFlexLayout = derivedContent.type === 'graphql' || derivedContent.type === 'changelog';
|
|
124
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
125
|
+
ref: actualRef,
|
|
126
|
+
className: cn("docs-content-area flex-1 bg-background min-w-0", needsFlexLayout ? "overflow-hidden flex flex-col" : "overflow-y-auto scroll-smooth"),
|
|
127
|
+
children: derivedContent.type === 'graphql' && 'operation' in derivedContent ? /*#__PURE__*/ _jsx("div", {
|
|
128
|
+
className: "flex-1 flex flex-col h-full",
|
|
129
|
+
children: /*#__PURE__*/ _jsx(GraphQLPlayground, {
|
|
130
|
+
endpoint: graphqlEndpoint || '',
|
|
131
|
+
defaultQuery: derivedContent.operation.query,
|
|
132
|
+
operations: graphqlOperations,
|
|
133
|
+
selectedOperationId: derivedContent.operation.id,
|
|
134
|
+
hideExplorer: true,
|
|
135
|
+
headers: {},
|
|
136
|
+
theme: "dark",
|
|
137
|
+
schemaSDL: graphqlSchemaSDL
|
|
138
|
+
})
|
|
139
|
+
}) : derivedContent.type === 'changelog' ? /*#__PURE__*/ _jsx(ChangelogPage, {
|
|
140
|
+
releases: collection.changelogReleases || [],
|
|
141
|
+
tabName: activeTabConfig?.tab
|
|
142
|
+
}) : derivedContent.type === 'endpoint' && 'request' in derivedContent ? /*#__PURE__*/ _jsx(RequestDetails, {
|
|
143
|
+
request: derivedContent.request
|
|
144
|
+
}) : derivedContent.type === 'page' && 'slug' in derivedContent ? /*#__PURE__*/ _jsx(DocPage, {
|
|
145
|
+
slug: derivedContent.slug,
|
|
146
|
+
onSearch: onSearch
|
|
147
|
+
}) : derivedContent.type === 'not-found' && 'slug' in derivedContent ? /*#__PURE__*/ _jsx(NotFoundPage, {
|
|
148
|
+
slug: derivedContent.slug,
|
|
149
|
+
onSearch: onSearch
|
|
150
|
+
}) : derivedContent.type === 'section' ? // Section navigation - show empty state while scrolling to section
|
|
151
|
+
/*#__PURE__*/ _jsx("div", {
|
|
152
|
+
className: "flex-1 flex items-center justify-center bg-background",
|
|
153
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
154
|
+
className: "text-center text-muted-foreground",
|
|
155
|
+
children: [
|
|
156
|
+
/*#__PURE__*/ _jsx(Book, {
|
|
157
|
+
className: "h-12 w-12 mx-auto mb-3 opacity-40"
|
|
158
|
+
}),
|
|
159
|
+
/*#__PURE__*/ _jsx("p", {
|
|
160
|
+
className: "text-sm",
|
|
161
|
+
children: "Scrolling to section..."
|
|
162
|
+
})
|
|
163
|
+
]
|
|
164
|
+
})
|
|
165
|
+
}) : /*#__PURE__*/ _jsx("div", {
|
|
166
|
+
className: "flex-1 flex items-center justify-center bg-background",
|
|
167
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
168
|
+
className: "text-center text-muted-foreground",
|
|
169
|
+
children: [
|
|
170
|
+
/*#__PURE__*/ _jsx(Book, {
|
|
171
|
+
className: "h-12 w-12 mx-auto mb-3 opacity-40"
|
|
172
|
+
}),
|
|
173
|
+
/*#__PURE__*/ _jsx("p", {
|
|
174
|
+
className: "text-sm",
|
|
175
|
+
children: "Select an endpoint from the sidebar"
|
|
176
|
+
})
|
|
177
|
+
]
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export { findRequestById };
|