@brainfish-ai/devdoc 0.1.21
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/LICENSE +33 -0
- package/README.md +415 -0
- package/bin/devdoc.js +13 -0
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +87 -0
- package/dist/cli/commands/check.d.ts +1 -0
- package/dist/cli/commands/check.js +143 -0
- package/dist/cli/commands/create.d.ts +24 -0
- package/dist/cli/commands/create.js +387 -0
- package/dist/cli/commands/deploy.d.ts +9 -0
- package/dist/cli/commands/deploy.js +433 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.js +139 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.js +238 -0
- package/dist/cli/commands/keys.d.ts +12 -0
- package/dist/cli/commands/keys.js +165 -0
- package/dist/cli/commands/start.d.ts +5 -0
- package/dist/cli/commands/start.js +56 -0
- package/dist/cli/commands/upload.d.ts +13 -0
- package/dist/cli/commands/upload.js +238 -0
- package/dist/cli/commands/whoami.d.ts +8 -0
- package/dist/cli/commands/whoami.js +91 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +106 -0
- package/dist/config/index.d.ts +80 -0
- package/dist/config/index.js +133 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +61 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.js +50 -0
- package/package.json +51 -0
- package/renderer/app/api/assets/[...path]/route.ts +123 -0
- package/renderer/app/api/assets/route.ts +124 -0
- package/renderer/app/api/assets/upload/route.ts +177 -0
- package/renderer/app/api/auth-schemes/route.ts +77 -0
- package/renderer/app/api/chat/route.ts +858 -0
- package/renderer/app/api/codegen/route.ts +72 -0
- package/renderer/app/api/collections/route.ts +1016 -0
- package/renderer/app/api/debug/route.ts +53 -0
- package/renderer/app/api/deploy/route.ts +234 -0
- package/renderer/app/api/device/route.ts +42 -0
- package/renderer/app/api/docs/route.ts +187 -0
- package/renderer/app/api/keys/regenerate/route.ts +80 -0
- package/renderer/app/api/openapi-spec/route.ts +151 -0
- package/renderer/app/api/projects/[slug]/route.ts +153 -0
- package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
- package/renderer/app/api/projects/register/route.ts +152 -0
- package/renderer/app/api/proxy/route.ts +149 -0
- package/renderer/app/api/proxy-stream/route.ts +168 -0
- package/renderer/app/api/redirects/route.ts +47 -0
- package/renderer/app/api/schema/route.ts +65 -0
- package/renderer/app/api/subdomains/check/route.ts +172 -0
- package/renderer/app/api/suggestions/route.ts +144 -0
- package/renderer/app/favicon.ico +0 -0
- package/renderer/app/globals.css +1103 -0
- package/renderer/app/layout.tsx +47 -0
- package/renderer/app/llms-full.txt/route.ts +346 -0
- package/renderer/app/llms.txt/route.ts +279 -0
- package/renderer/app/page.tsx +14 -0
- package/renderer/app/robots.txt/route.ts +84 -0
- package/renderer/app/sitemap.xml/route.ts +199 -0
- package/renderer/components/docs/index.ts +12 -0
- package/renderer/components/docs/mdx/accordion.tsx +169 -0
- package/renderer/components/docs/mdx/badge.tsx +132 -0
- package/renderer/components/docs/mdx/callouts.tsx +154 -0
- package/renderer/components/docs/mdx/cards.tsx +213 -0
- package/renderer/components/docs/mdx/changelog.tsx +120 -0
- package/renderer/components/docs/mdx/code-block.tsx +186 -0
- package/renderer/components/docs/mdx/code-group.tsx +421 -0
- package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
- package/renderer/components/docs/mdx/frame.tsx +112 -0
- package/renderer/components/docs/mdx/highlight.tsx +151 -0
- package/renderer/components/docs/mdx/iframe.tsx +134 -0
- package/renderer/components/docs/mdx/image.tsx +235 -0
- package/renderer/components/docs/mdx/index.ts +204 -0
- package/renderer/components/docs/mdx/mermaid.tsx +240 -0
- package/renderer/components/docs/mdx/param-field.tsx +200 -0
- package/renderer/components/docs/mdx/steps.tsx +113 -0
- package/renderer/components/docs/mdx/tabs.tsx +86 -0
- package/renderer/components/docs/mdx-renderer.tsx +100 -0
- package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
- package/renderer/components/docs/navigation/index.ts +8 -0
- package/renderer/components/docs/navigation/page-nav.tsx +64 -0
- package/renderer/components/docs/navigation/sidebar.tsx +515 -0
- package/renderer/components/docs/navigation/toc.tsx +113 -0
- package/renderer/components/docs/notice.tsx +105 -0
- package/renderer/components/docs-header.tsx +274 -0
- package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
- package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
- package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
- package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
- package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
- package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
- package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
- package/renderer/components/docs-viewer/agent/index.tsx +6 -0
- package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
- package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
- package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
- package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
- package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
- package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
- package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
- package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
- package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
- package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
- package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
- package/renderer/components/docs-viewer/content/index.tsx +29 -0
- package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
- package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
- package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
- package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
- package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
- package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
- package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
- package/renderer/components/docs-viewer/index.tsx +1466 -0
- package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
- package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
- package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
- package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
- package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
- package/renderer/components/docs-viewer/playground/index.tsx +671 -0
- package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
- package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
- package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
- package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
- package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
- package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
- package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
- package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
- package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
- package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
- package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
- package/renderer/components/docs-viewer/search/index.ts +2 -0
- package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
- package/renderer/components/docs-viewer/search/use-search.ts +117 -0
- package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
- package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
- package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
- package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
- package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
- package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
- package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
- package/renderer/components/theme-provider.tsx +11 -0
- package/renderer/components/theme-toggle.tsx +76 -0
- package/renderer/components/ui/badge.tsx +46 -0
- package/renderer/components/ui/button.tsx +59 -0
- package/renderer/components/ui/dialog.tsx +118 -0
- package/renderer/components/ui/dropdown-menu.tsx +257 -0
- package/renderer/components/ui/input.tsx +21 -0
- package/renderer/components/ui/label.tsx +24 -0
- package/renderer/components/ui/navigation-menu.tsx +168 -0
- package/renderer/components/ui/select.tsx +190 -0
- package/renderer/components/ui/spinner.tsx +114 -0
- package/renderer/components/ui/tabs.tsx +66 -0
- package/renderer/components/ui/tooltip.tsx +61 -0
- package/renderer/hooks/use-code-copy.ts +88 -0
- package/renderer/hooks/use-openapi-title.ts +44 -0
- package/renderer/lib/api-docs/agent/index.ts +6 -0
- package/renderer/lib/api-docs/agent/indexer.ts +323 -0
- package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
- package/renderer/lib/api-docs/agent/types.ts +116 -0
- package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
- package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
- package/renderer/lib/api-docs/auth/crypto.ts +89 -0
- package/renderer/lib/api-docs/auth/index.ts +4 -0
- package/renderer/lib/api-docs/code-editor/db.ts +164 -0
- package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
- package/renderer/lib/api-docs/code-editor/index.ts +6 -0
- package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
- package/renderer/lib/api-docs/code-editor/types.ts +105 -0
- package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
- package/renderer/lib/api-docs/codegen/har.ts +251 -0
- package/renderer/lib/api-docs/codegen/index.ts +159 -0
- package/renderer/lib/api-docs/factories.ts +151 -0
- package/renderer/lib/api-docs/index.ts +17 -0
- package/renderer/lib/api-docs/mobile-context.tsx +112 -0
- package/renderer/lib/api-docs/navigation-context.tsx +88 -0
- package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
- package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
- package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
- package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
- package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
- package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
- package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
- package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
- package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
- package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
- package/renderer/lib/api-docs/playground/context.tsx +107 -0
- package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
- package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
- package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
- package/renderer/lib/api-docs/playground/types.ts +35 -0
- package/renderer/lib/api-docs/types.ts +269 -0
- package/renderer/lib/api-docs/utils.ts +311 -0
- package/renderer/lib/cache.ts +193 -0
- package/renderer/lib/docs/config/index.ts +29 -0
- package/renderer/lib/docs/config/loader.ts +142 -0
- package/renderer/lib/docs/config/schema.ts +298 -0
- package/renderer/lib/docs/index.ts +12 -0
- package/renderer/lib/docs/mdx/compiler.ts +176 -0
- package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
- package/renderer/lib/docs/mdx/index.ts +26 -0
- package/renderer/lib/docs/navigation/generator.ts +348 -0
- package/renderer/lib/docs/navigation/index.ts +12 -0
- package/renderer/lib/docs/navigation/types.ts +123 -0
- package/renderer/lib/docs-navigation-context.tsx +80 -0
- package/renderer/lib/multi-tenant/context.ts +105 -0
- package/renderer/lib/storage/blob.ts +845 -0
- package/renderer/lib/utils.ts +6 -0
- package/renderer/next.config.ts +76 -0
- package/renderer/package.json +66 -0
- package/renderer/postcss.config.mjs +5 -0
- package/renderer/public/assets/images/screenshot.png +0 -0
- package/renderer/public/assets/logo/dark.svg +9 -0
- package/renderer/public/assets/logo/light.svg +9 -0
- package/renderer/public/assets/logo.svg +9 -0
- package/renderer/public/file.svg +1 -0
- package/renderer/public/globe.svg +1 -0
- package/renderer/public/icon.png +0 -0
- package/renderer/public/logo.svg +9 -0
- package/renderer/public/window.svg +1 -0
- package/renderer/tsconfig.json +28 -0
- package/templates/basic/README.md +139 -0
- package/templates/basic/assets/favicon.svg +4 -0
- package/templates/basic/assets/logo.svg +9 -0
- package/templates/basic/docs.json +47 -0
- package/templates/basic/guides/configuration.mdx +149 -0
- package/templates/basic/guides/overview.mdx +96 -0
- package/templates/basic/index.mdx +39 -0
- package/templates/basic/package.json +14 -0
- package/templates/basic/quickstart.mdx +92 -0
- package/templates/basic/vercel.json +6 -0
- package/templates/graphql/README.md +139 -0
- package/templates/graphql/api-reference/schema.graphql +305 -0
- package/templates/graphql/assets/favicon.svg +4 -0
- package/templates/graphql/assets/logo.svg +9 -0
- package/templates/graphql/docs.json +54 -0
- package/templates/graphql/guides/configuration.mdx +149 -0
- package/templates/graphql/guides/overview.mdx +96 -0
- package/templates/graphql/index.mdx +39 -0
- package/templates/graphql/package.json +14 -0
- package/templates/graphql/quickstart.mdx +92 -0
- package/templates/graphql/vercel.json +6 -0
- package/templates/openapi/README.md +139 -0
- package/templates/openapi/api-reference/openapi.json +419 -0
- package/templates/openapi/assets/favicon.svg +4 -0
- package/templates/openapi/assets/logo.svg +9 -0
- package/templates/openapi/docs.json +61 -0
- package/templates/openapi/guides/configuration.mdx +149 -0
- package/templates/openapi/guides/overview.mdx +96 -0
- package/templates/openapi/index.mdx +39 -0
- package/templates/openapi/package.json +14 -0
- package/templates/openapi/quickstart.mdx +92 -0
- package/templates/openapi/vercel.json +6 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { dbOperations } from './db'
|
|
5
|
+
import type { Workspace, NoteFile, EditorState } from './types'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook for managing the workspace (auto-created per API)
|
|
9
|
+
*/
|
|
10
|
+
export function useWorkspace(apiSpecUrl: string | null, apiName: string = 'API Notes') {
|
|
11
|
+
const [workspace, setWorkspace] = useState<Workspace | null>(null)
|
|
12
|
+
const [loading, setLoading] = useState(true)
|
|
13
|
+
const [error, setError] = useState<Error | null>(null)
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!apiSpecUrl) {
|
|
17
|
+
setWorkspace(null)
|
|
18
|
+
setLoading(false)
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const loadWorkspace = async () => {
|
|
23
|
+
try {
|
|
24
|
+
setLoading(true)
|
|
25
|
+
const ws = await dbOperations.getOrCreateWorkspace(apiSpecUrl, `${apiName} Notes`)
|
|
26
|
+
setWorkspace(ws)
|
|
27
|
+
setError(null)
|
|
28
|
+
} catch (err) {
|
|
29
|
+
setError(err instanceof Error ? err : new Error('Failed to load workspace'))
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
loadWorkspace()
|
|
36
|
+
}, [apiSpecUrl, apiName])
|
|
37
|
+
|
|
38
|
+
const clearWorkspace = useCallback(async () => {
|
|
39
|
+
if (!workspace) return
|
|
40
|
+
await dbOperations.clearWorkspace(workspace.id)
|
|
41
|
+
}, [workspace])
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
workspace,
|
|
45
|
+
loading,
|
|
46
|
+
error,
|
|
47
|
+
clearWorkspace,
|
|
48
|
+
workspaceId: workspace?.id || null,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Hook for managing notes/files in the workspace
|
|
54
|
+
*/
|
|
55
|
+
export function useNotes(workspaceId: string | null) {
|
|
56
|
+
const [notes, setNotes] = useState<NoteFile[]>([])
|
|
57
|
+
const [loading, setLoading] = useState(true)
|
|
58
|
+
const [error, setError] = useState<Error | null>(null)
|
|
59
|
+
|
|
60
|
+
const loadNotes = useCallback(async () => {
|
|
61
|
+
if (!workspaceId) {
|
|
62
|
+
setNotes([])
|
|
63
|
+
setLoading(false)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
setLoading(true)
|
|
69
|
+
const list = await dbOperations.listNotes(workspaceId)
|
|
70
|
+
setNotes(list)
|
|
71
|
+
setError(null)
|
|
72
|
+
} catch (err) {
|
|
73
|
+
setError(err instanceof Error ? err : new Error('Failed to load notes'))
|
|
74
|
+
} finally {
|
|
75
|
+
setLoading(false)
|
|
76
|
+
}
|
|
77
|
+
}, [workspaceId])
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
loadNotes()
|
|
81
|
+
}, [loadNotes])
|
|
82
|
+
|
|
83
|
+
const createNote = useCallback(async (path: string, content: string = ''): Promise<NoteFile | null> => {
|
|
84
|
+
if (!workspaceId) return null
|
|
85
|
+
|
|
86
|
+
const note = await dbOperations.createNote(workspaceId, path, content)
|
|
87
|
+
setNotes(prev => [...prev, note])
|
|
88
|
+
return note
|
|
89
|
+
}, [workspaceId])
|
|
90
|
+
|
|
91
|
+
const readNote = useCallback(async (path: string): Promise<string> => {
|
|
92
|
+
if (!workspaceId) return ''
|
|
93
|
+
|
|
94
|
+
const note = await dbOperations.getNote(workspaceId, path)
|
|
95
|
+
return note?.content || ''
|
|
96
|
+
}, [workspaceId])
|
|
97
|
+
|
|
98
|
+
const updateNote = useCallback(async (path: string, content: string): Promise<void> => {
|
|
99
|
+
if (!workspaceId) return
|
|
100
|
+
|
|
101
|
+
await dbOperations.updateNote(workspaceId, path, content)
|
|
102
|
+
setNotes(prev => prev.map(n =>
|
|
103
|
+
n.path === path
|
|
104
|
+
? { ...n, content, updatedAt: new Date() }
|
|
105
|
+
: n
|
|
106
|
+
))
|
|
107
|
+
}, [workspaceId])
|
|
108
|
+
|
|
109
|
+
const deleteNote = useCallback(async (path: string): Promise<void> => {
|
|
110
|
+
if (!workspaceId) return
|
|
111
|
+
|
|
112
|
+
await dbOperations.deleteNote(workspaceId, path)
|
|
113
|
+
setNotes(prev => prev.filter(n => n.path !== path))
|
|
114
|
+
}, [workspaceId])
|
|
115
|
+
|
|
116
|
+
const renameNote = useCallback(async (oldPath: string, newPath: string): Promise<void> => {
|
|
117
|
+
if (!workspaceId) return
|
|
118
|
+
|
|
119
|
+
await dbOperations.renameNote(workspaceId, oldPath, newPath)
|
|
120
|
+
setNotes(prev => prev.map(n =>
|
|
121
|
+
n.path === oldPath
|
|
122
|
+
? { ...n, path: newPath, updatedAt: new Date() }
|
|
123
|
+
: n
|
|
124
|
+
))
|
|
125
|
+
}, [workspaceId])
|
|
126
|
+
|
|
127
|
+
const deleteAllNotes = useCallback(async (): Promise<void> => {
|
|
128
|
+
if (!workspaceId) return
|
|
129
|
+
|
|
130
|
+
await dbOperations.clearWorkspace(workspaceId)
|
|
131
|
+
setNotes([])
|
|
132
|
+
}, [workspaceId])
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
notes,
|
|
136
|
+
loading,
|
|
137
|
+
error,
|
|
138
|
+
createNote,
|
|
139
|
+
readNote,
|
|
140
|
+
updateNote,
|
|
141
|
+
deleteNote,
|
|
142
|
+
deleteAllNotes,
|
|
143
|
+
renameNote,
|
|
144
|
+
refresh: loadNotes,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Hook for managing editor state (tabs, active file, etc.)
|
|
150
|
+
*/
|
|
151
|
+
export function useEditorState(workspaceId: string | null) {
|
|
152
|
+
const [state, setState] = useState<EditorState | null>(null)
|
|
153
|
+
|
|
154
|
+
// Load saved state
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!workspaceId) {
|
|
157
|
+
setState(null)
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const loadState = async () => {
|
|
162
|
+
const saved = await dbOperations.getEditorState(workspaceId)
|
|
163
|
+
if (saved) {
|
|
164
|
+
setState(saved)
|
|
165
|
+
} else {
|
|
166
|
+
// Initialize new state
|
|
167
|
+
setState({
|
|
168
|
+
workspaceId,
|
|
169
|
+
openTabs: [],
|
|
170
|
+
activeTab: null,
|
|
171
|
+
cursorPositions: {},
|
|
172
|
+
scrollPositions: {},
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
loadState()
|
|
178
|
+
}, [workspaceId])
|
|
179
|
+
|
|
180
|
+
// Save state when it changes
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (state && workspaceId) {
|
|
183
|
+
dbOperations.saveEditorState(state)
|
|
184
|
+
}
|
|
185
|
+
}, [state, workspaceId])
|
|
186
|
+
|
|
187
|
+
const openTab = useCallback(async (path: string) => {
|
|
188
|
+
setState(prev => {
|
|
189
|
+
if (!prev) return prev
|
|
190
|
+
|
|
191
|
+
const newTabs = prev.openTabs.includes(path)
|
|
192
|
+
? prev.openTabs
|
|
193
|
+
: [...prev.openTabs, path]
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
...prev,
|
|
197
|
+
openTabs: newTabs,
|
|
198
|
+
activeTab: path,
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
}, [])
|
|
202
|
+
|
|
203
|
+
const closeTab = useCallback((path: string) => {
|
|
204
|
+
setState(prev => {
|
|
205
|
+
if (!prev) return prev
|
|
206
|
+
|
|
207
|
+
const newTabs = prev.openTabs.filter(t => t !== path)
|
|
208
|
+
const newActive = prev.activeTab === path
|
|
209
|
+
? newTabs[newTabs.length - 1] || null
|
|
210
|
+
: prev.activeTab
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
...prev,
|
|
214
|
+
openTabs: newTabs,
|
|
215
|
+
activeTab: newActive,
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
}, [])
|
|
219
|
+
|
|
220
|
+
const setActiveTab = useCallback((path: string) => {
|
|
221
|
+
setState(prev => {
|
|
222
|
+
if (!prev) return prev
|
|
223
|
+
return { ...prev, activeTab: path }
|
|
224
|
+
})
|
|
225
|
+
}, [])
|
|
226
|
+
|
|
227
|
+
const saveCursorPosition = useCallback((path: string, line: number, column: number) => {
|
|
228
|
+
setState(prev => {
|
|
229
|
+
if (!prev) return prev
|
|
230
|
+
return {
|
|
231
|
+
...prev,
|
|
232
|
+
cursorPositions: {
|
|
233
|
+
...prev.cursorPositions,
|
|
234
|
+
[path]: { line, column },
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
}, [])
|
|
239
|
+
|
|
240
|
+
const clearAllTabs = useCallback(() => {
|
|
241
|
+
setState(prev => {
|
|
242
|
+
if (!prev) return prev
|
|
243
|
+
return {
|
|
244
|
+
...prev,
|
|
245
|
+
openTabs: [],
|
|
246
|
+
activeTab: null,
|
|
247
|
+
cursorPositions: {},
|
|
248
|
+
scrollPositions: {},
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
}, [])
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
state,
|
|
255
|
+
openTab,
|
|
256
|
+
closeTab,
|
|
257
|
+
setActiveTab,
|
|
258
|
+
saveCursorPosition,
|
|
259
|
+
clearAllTabs,
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Re-export types and helpers
|
|
264
|
+
export type { Workspace, NoteFile, EditorState } from './types'
|
|
265
|
+
export { getFileType, getMonacoLanguage, supportsPreview } from './types'
|
|
266
|
+
export { getWorkspaceId } from './db'
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useState, useCallback, useEffect, useRef, type ReactNode } from 'react'
|
|
4
|
+
import type { PlaygroundMode } from './types'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Streaming content for real-time code display
|
|
8
|
+
*/
|
|
9
|
+
interface StreamingContent {
|
|
10
|
+
path: string
|
|
11
|
+
content: string
|
|
12
|
+
isStreaming: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Simplified mode context - toggles between Docs, API Client, and Notes
|
|
17
|
+
* Uses URL hash for persistence: #notes or #notes/path/to/file.py
|
|
18
|
+
*/
|
|
19
|
+
interface ModeContextValue {
|
|
20
|
+
// Current mode
|
|
21
|
+
mode: PlaygroundMode
|
|
22
|
+
|
|
23
|
+
// Active file in notes mode (for navigation from agent)
|
|
24
|
+
activeFilePath: string | null
|
|
25
|
+
setActiveFilePath: (path: string | null) => void
|
|
26
|
+
|
|
27
|
+
// Streaming content for real-time display
|
|
28
|
+
streamingContent: StreamingContent | null
|
|
29
|
+
setStreamingContent: (content: StreamingContent | null) => void
|
|
30
|
+
appendStreamingContent: (delta: string) => void
|
|
31
|
+
|
|
32
|
+
// Mode switching
|
|
33
|
+
switchToDocs: () => void
|
|
34
|
+
switchToNotes: (openFile?: string) => void
|
|
35
|
+
switchToApiClient: () => void
|
|
36
|
+
|
|
37
|
+
// Notes refresh trigger - increment to signal notes-mode to refresh
|
|
38
|
+
notesRefreshTrigger: number
|
|
39
|
+
triggerNotesRefresh: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ModeContext = createContext<ModeContextValue | null>(null)
|
|
43
|
+
|
|
44
|
+
interface ModeProviderProps {
|
|
45
|
+
children: ReactNode
|
|
46
|
+
defaultMode?: PlaygroundMode
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Helper to parse notes path from URL hash
|
|
50
|
+
function parseNotesHash(): { isNotes: boolean; filePath: string | null } {
|
|
51
|
+
if (typeof window === 'undefined') return { isNotes: false, filePath: null }
|
|
52
|
+
|
|
53
|
+
const hash = window.location.hash.slice(1) // Remove #
|
|
54
|
+
if (hash === 'notes') {
|
|
55
|
+
return { isNotes: true, filePath: null }
|
|
56
|
+
}
|
|
57
|
+
if (hash.startsWith('notes/')) {
|
|
58
|
+
// Decode any encoded characters but path should mostly be plain text
|
|
59
|
+
const filePath = decodeURIComponent(hash.slice(6)) // Remove 'notes/'
|
|
60
|
+
return { isNotes: true, filePath: filePath || null }
|
|
61
|
+
}
|
|
62
|
+
return { isNotes: false, filePath: null }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Helper to update URL hash for notes
|
|
66
|
+
// Only encode special chars that would break the URL, keep slashes readable
|
|
67
|
+
function updateNotesHash(filePath: string | null): void {
|
|
68
|
+
if (typeof window === 'undefined') return
|
|
69
|
+
|
|
70
|
+
const newHash = filePath
|
|
71
|
+
? `#notes/${filePath.replace(/#/g, '%23').replace(/\?/g, '%3F')}`
|
|
72
|
+
: '#notes'
|
|
73
|
+
|
|
74
|
+
window.history.pushState(null, '', newHash)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function ModeProvider({ children, defaultMode = 'docs' }: ModeProviderProps) {
|
|
78
|
+
const [mode, setMode] = useState<PlaygroundMode>(defaultMode)
|
|
79
|
+
const [activeFilePath, setActiveFilePathState] = useState<string | null>(null)
|
|
80
|
+
const [streamingContent, setStreamingContent] = useState<StreamingContent | null>(null)
|
|
81
|
+
const [notesRefreshTrigger, setNotesRefreshTrigger] = useState(0)
|
|
82
|
+
|
|
83
|
+
// Use ref to track mode for callbacks (avoids stale closure issues)
|
|
84
|
+
const modeRef = useRef<PlaygroundMode>(defaultMode)
|
|
85
|
+
|
|
86
|
+
// Keep ref in sync with state
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
modeRef.current = mode
|
|
89
|
+
}, [mode])
|
|
90
|
+
|
|
91
|
+
// Initialize from URL hash on mount
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const { isNotes, filePath } = parseNotesHash()
|
|
94
|
+
|
|
95
|
+
if (isNotes) {
|
|
96
|
+
setMode('notes')
|
|
97
|
+
modeRef.current = 'notes'
|
|
98
|
+
setActiveFilePathState(filePath)
|
|
99
|
+
}
|
|
100
|
+
}, [])
|
|
101
|
+
|
|
102
|
+
// Handle browser back/forward navigation
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const handlePopState = () => {
|
|
105
|
+
const { isNotes, filePath } = parseNotesHash()
|
|
106
|
+
|
|
107
|
+
if (isNotes) {
|
|
108
|
+
setMode('notes')
|
|
109
|
+
modeRef.current = 'notes'
|
|
110
|
+
setActiveFilePathState(filePath)
|
|
111
|
+
} else {
|
|
112
|
+
// Default to docs mode when no notes hash
|
|
113
|
+
setMode('docs')
|
|
114
|
+
modeRef.current = 'docs'
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
window.addEventListener('popstate', handlePopState)
|
|
119
|
+
return () => window.removeEventListener('popstate', handlePopState)
|
|
120
|
+
}, [])
|
|
121
|
+
|
|
122
|
+
// Wrapper for setActiveFilePath that also updates URL
|
|
123
|
+
const setActiveFilePath = useCallback((path: string | null) => {
|
|
124
|
+
// Handle the ?t= timestamp suffix for forcing reloads
|
|
125
|
+
const cleanPath = path?.split('?')[0] || null
|
|
126
|
+
setActiveFilePathState(cleanPath)
|
|
127
|
+
|
|
128
|
+
// Update URL if we're in notes mode (use ref to avoid stale closure)
|
|
129
|
+
if (modeRef.current === 'notes') {
|
|
130
|
+
updateNotesHash(cleanPath)
|
|
131
|
+
}
|
|
132
|
+
}, []) // No dependencies - uses ref
|
|
133
|
+
|
|
134
|
+
const switchToNotes = useCallback((openFile?: string) => {
|
|
135
|
+
const filePath = openFile || null
|
|
136
|
+
setActiveFilePathState(filePath)
|
|
137
|
+
setMode('notes')
|
|
138
|
+
modeRef.current = 'notes'
|
|
139
|
+
updateNotesHash(filePath)
|
|
140
|
+
}, [])
|
|
141
|
+
|
|
142
|
+
const switchToApiClient = useCallback(() => {
|
|
143
|
+
setMode('api_client')
|
|
144
|
+
modeRef.current = 'api_client'
|
|
145
|
+
// Don't update URL here - let the main API docs component handle it
|
|
146
|
+
}, [])
|
|
147
|
+
|
|
148
|
+
const switchToDocs = useCallback(() => {
|
|
149
|
+
setMode('docs')
|
|
150
|
+
modeRef.current = 'docs'
|
|
151
|
+
// Only clear the hash if it's a notes hash, preserve doc/endpoint hashes
|
|
152
|
+
if (typeof window !== 'undefined') {
|
|
153
|
+
const hash = window.location.hash.slice(1)
|
|
154
|
+
if (hash === 'notes' || hash.startsWith('notes/')) {
|
|
155
|
+
window.history.pushState(null, '', window.location.pathname + window.location.search)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}, [])
|
|
159
|
+
|
|
160
|
+
const appendStreamingContent = useCallback((delta: string) => {
|
|
161
|
+
setStreamingContent(prev => {
|
|
162
|
+
if (!prev) return prev
|
|
163
|
+
return {
|
|
164
|
+
...prev,
|
|
165
|
+
content: prev.content + delta,
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
}, [])
|
|
169
|
+
|
|
170
|
+
const triggerNotesRefresh = useCallback(() => {
|
|
171
|
+
setNotesRefreshTrigger(prev => prev + 1)
|
|
172
|
+
}, [])
|
|
173
|
+
|
|
174
|
+
const value: ModeContextValue = {
|
|
175
|
+
mode,
|
|
176
|
+
activeFilePath,
|
|
177
|
+
setActiveFilePath,
|
|
178
|
+
streamingContent,
|
|
179
|
+
setStreamingContent,
|
|
180
|
+
appendStreamingContent,
|
|
181
|
+
switchToDocs,
|
|
182
|
+
switchToNotes,
|
|
183
|
+
switchToApiClient,
|
|
184
|
+
notesRefreshTrigger,
|
|
185
|
+
triggerNotesRefresh,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<ModeContext.Provider value={value}>
|
|
190
|
+
{children}
|
|
191
|
+
</ModeContext.Provider>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function useModeContext() {
|
|
196
|
+
const context = useContext(ModeContext)
|
|
197
|
+
if (!context) {
|
|
198
|
+
throw new Error('useModeContext must be used within a ModeProvider')
|
|
199
|
+
}
|
|
200
|
+
return context
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Hook for checking if we're in notes mode
|
|
204
|
+
export function useIsNotesMode() {
|
|
205
|
+
const { mode } = useModeContext()
|
|
206
|
+
return mode === 'notes'
|
|
207
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Types for the Notes/Scratchpad feature
|
|
2
|
+
// Simplified from full project management to a brainstorming workspace
|
|
3
|
+
|
|
4
|
+
export type PlaygroundMode = 'docs' | 'api_client' | 'notes'
|
|
5
|
+
|
|
6
|
+
// File types supported in the notes workspace
|
|
7
|
+
export type NoteFileType =
|
|
8
|
+
| 'javascript'
|
|
9
|
+
| 'typescript'
|
|
10
|
+
| 'python'
|
|
11
|
+
| 'go'
|
|
12
|
+
| 'ruby'
|
|
13
|
+
| 'php'
|
|
14
|
+
| 'shell' // curl, bash
|
|
15
|
+
| 'markdown'
|
|
16
|
+
| 'mermaid' // Diagrams
|
|
17
|
+
| 'json'
|
|
18
|
+
| 'yaml'
|
|
19
|
+
| 'text'
|
|
20
|
+
|
|
21
|
+
// Single workspace per API (not multiple projects)
|
|
22
|
+
export interface Workspace {
|
|
23
|
+
id: string // Hash of API spec URL
|
|
24
|
+
name: string // e.g., "Vrio API Notes"
|
|
25
|
+
apiSpecUrl?: string
|
|
26
|
+
createdAt: Date
|
|
27
|
+
updatedAt: Date
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A note/file in the workspace
|
|
31
|
+
export interface NoteFile {
|
|
32
|
+
id: string
|
|
33
|
+
workspaceId: string
|
|
34
|
+
path: string // e.g., "auth/oauth-example.py"
|
|
35
|
+
content: string
|
|
36
|
+
createdAt: Date
|
|
37
|
+
updatedAt: Date
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Editor state for the workspace
|
|
41
|
+
export interface EditorState {
|
|
42
|
+
workspaceId: string
|
|
43
|
+
openTabs: string[] // File paths
|
|
44
|
+
activeTab: string | null
|
|
45
|
+
cursorPositions: Record<string, { line: number; column: number }>
|
|
46
|
+
scrollPositions: Record<string, number>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Helper to detect file type from path
|
|
50
|
+
export function getFileType(path: string): NoteFileType {
|
|
51
|
+
const ext = path.split('.').pop()?.toLowerCase() || ''
|
|
52
|
+
|
|
53
|
+
const typeMap: Record<string, NoteFileType> = {
|
|
54
|
+
'js': 'javascript',
|
|
55
|
+
'mjs': 'javascript',
|
|
56
|
+
'jsx': 'javascript',
|
|
57
|
+
'ts': 'typescript',
|
|
58
|
+
'tsx': 'typescript',
|
|
59
|
+
'py': 'python',
|
|
60
|
+
'go': 'go',
|
|
61
|
+
'rb': 'ruby',
|
|
62
|
+
'php': 'php',
|
|
63
|
+
'sh': 'shell',
|
|
64
|
+
'bash': 'shell',
|
|
65
|
+
'curl': 'shell',
|
|
66
|
+
'md': 'markdown',
|
|
67
|
+
'mdx': 'markdown',
|
|
68
|
+
'mmd': 'mermaid',
|
|
69
|
+
'mermaid': 'mermaid',
|
|
70
|
+
'json': 'json',
|
|
71
|
+
'yaml': 'yaml',
|
|
72
|
+
'yml': 'yaml',
|
|
73
|
+
'txt': 'text',
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return typeMap[ext] || 'text'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Helper to get Monaco language from file type
|
|
80
|
+
export function getMonacoLanguage(path: string): string {
|
|
81
|
+
const type = getFileType(path)
|
|
82
|
+
|
|
83
|
+
const languageMap: Record<NoteFileType, string> = {
|
|
84
|
+
'javascript': 'javascript',
|
|
85
|
+
'typescript': 'typescript',
|
|
86
|
+
'python': 'python',
|
|
87
|
+
'go': 'go',
|
|
88
|
+
'ruby': 'ruby',
|
|
89
|
+
'php': 'php',
|
|
90
|
+
'shell': 'shell',
|
|
91
|
+
'markdown': 'markdown',
|
|
92
|
+
'mermaid': 'markdown', // Use markdown for mermaid editing
|
|
93
|
+
'json': 'json',
|
|
94
|
+
'yaml': 'yaml',
|
|
95
|
+
'text': 'plaintext',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return languageMap[type]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check if file type supports preview
|
|
102
|
+
export function supportsPreview(path: string): boolean {
|
|
103
|
+
const type = getFileType(path)
|
|
104
|
+
return type === 'markdown' || type === 'mermaid'
|
|
105
|
+
}
|