skrypt-ai 0.4.2 → 0.6.0
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/auth/index.d.ts +13 -3
- package/dist/auth/index.js +101 -9
- package/dist/auth/keychain.d.ts +5 -0
- package/dist/auth/keychain.js +82 -0
- package/dist/auth/notices.d.ts +3 -0
- package/dist/auth/notices.js +42 -0
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +10 -24
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +20 -3
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +125 -7
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/import.d.ts +2 -0
- package/dist/commands/import.js +157 -0
- package/dist/commands/init.js +19 -7
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +63 -34
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +108 -92
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +109 -0
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +36 -7
- package/dist/importers/confluence.d.ts +5 -0
- package/dist/importers/confluence.js +137 -0
- package/dist/importers/detect.d.ts +20 -0
- package/dist/importers/detect.js +121 -0
- package/dist/importers/docusaurus.d.ts +5 -0
- package/dist/importers/docusaurus.js +279 -0
- package/dist/importers/gitbook.d.ts +5 -0
- package/dist/importers/gitbook.js +189 -0
- package/dist/importers/github.d.ts +8 -0
- package/dist/importers/github.js +99 -0
- package/dist/importers/index.d.ts +15 -0
- package/dist/importers/index.js +30 -0
- package/dist/importers/markdown.d.ts +6 -0
- package/dist/importers/markdown.js +105 -0
- package/dist/importers/mintlify.d.ts +5 -0
- package/dist/importers/mintlify.js +172 -0
- package/dist/importers/notion.d.ts +5 -0
- package/dist/importers/notion.js +174 -0
- package/dist/importers/readme.d.ts +5 -0
- package/dist/importers/readme.js +184 -0
- package/dist/importers/transform.d.ts +90 -0
- package/dist/importers/transform.js +457 -0
- package/dist/importers/types.d.ts +37 -0
- package/dist/importers/types.js +1 -0
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/plugins/index.js +7 -0
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +53 -26
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/python.js +17 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +149 -13
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/lib/search-types.ts +4 -1
- package/dist/template/src/lib/search.ts +30 -7
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/files.d.ts +9 -1
- package/dist/utils/files.js +59 -10
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +5 -1
|
@@ -1,60 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Skrypt Documentation"
|
|
3
|
+
description: "AI-powered documentation generation for your codebase"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skrypt Documentation
|
|
7
|
+
|
|
8
|
+
Skrypt scans your source code and generates beautiful, comprehensive documentation automatically. One command, zero boilerplate.
|
|
9
|
+
|
|
10
|
+
<Info>
|
|
11
|
+
**130** documented API elements across 5 categories — all generated by Skrypt itself.
|
|
12
|
+
</Info>
|
|
1
13
|
|
|
2
14
|
<CardGroup cols={2}>
|
|
3
|
-
<Card title="
|
|
4
|
-
|
|
15
|
+
<Card title="Core API" icon="Code" href="/docs/core">
|
|
16
|
+
Main exports, plugin system, auth, and documentation generation (31 items)
|
|
17
|
+
</Card>
|
|
18
|
+
<Card title="Tools & Utilities" icon="Zap" href="/docs/tools">
|
|
19
|
+
File discovery, frontmatter parsing, validation helpers (7 items)
|
|
20
|
+
</Card>
|
|
21
|
+
<Card title="Types & Interfaces" icon="FileText" href="/docs/types">
|
|
22
|
+
TypeScript scanner and type definitions (3 items)
|
|
23
|
+
</Card>
|
|
24
|
+
<Card title="Integrations" icon="Globe" href="/docs/integrations">
|
|
25
|
+
Anthropic and OpenAI-compatible LLM clients (8 items)
|
|
5
26
|
</Card>
|
|
6
|
-
<Card title="
|
|
7
|
-
|
|
27
|
+
<Card title="Other" icon="Package" href="/docs/other">
|
|
28
|
+
Language scanners, PR analysis, and utilities (81 items)
|
|
8
29
|
</Card>
|
|
9
30
|
</CardGroup>
|
|
10
31
|
|
|
11
|
-
##
|
|
32
|
+
## Quick Start
|
|
12
33
|
|
|
13
34
|
<Steps>
|
|
14
|
-
<Step title="
|
|
15
|
-
|
|
35
|
+
<Step title="Install">
|
|
36
|
+
```bash
|
|
37
|
+
npx skrypt-ai generate ./src
|
|
38
|
+
```
|
|
16
39
|
</Step>
|
|
17
|
-
<Step title="
|
|
18
|
-
Set
|
|
40
|
+
<Step title="Configure">
|
|
41
|
+
Set your API key for AI-powered documentation:
|
|
42
|
+
```bash
|
|
43
|
+
skrypt auth login
|
|
44
|
+
```
|
|
19
45
|
</Step>
|
|
20
|
-
<Step title="
|
|
21
|
-
|
|
46
|
+
<Step title="Generate">
|
|
47
|
+
Skrypt scans your codebase, extracts exports, and generates MDX documentation grouped by topic.
|
|
22
48
|
</Step>
|
|
23
49
|
</Steps>
|
|
24
50
|
|
|
25
|
-
##
|
|
51
|
+
## Install Methods
|
|
26
52
|
|
|
27
53
|
<CodeGroup>
|
|
28
54
|
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
import { Client } from 'your-package'
|
|
32
|
-
|
|
33
|
-
const client = new Client({
|
|
34
|
-
apiKey: process.env.API_KEY
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const result = await client.getData()
|
|
55
|
+
```bash npx (no install)
|
|
56
|
+
npx skrypt-ai generate ./src
|
|
38
57
|
```
|
|
39
58
|
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
client = Client(api_key=os.environ["API_KEY"])
|
|
59
|
+
```bash npm
|
|
60
|
+
npm install -g skrypt-ai
|
|
61
|
+
skrypt generate ./src
|
|
62
|
+
```
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
```bash Homebrew
|
|
65
|
+
brew tap debgotwired/skrypt
|
|
66
|
+
brew install skrypt
|
|
67
|
+
skrypt generate ./src
|
|
47
68
|
```
|
|
48
69
|
|
|
49
70
|
</CodeGroup>
|
|
50
|
-
|
|
51
|
-
## Need Help?
|
|
52
|
-
|
|
53
|
-
<AccordionGroup>
|
|
54
|
-
<Accordion title="Where can I get support?">
|
|
55
|
-
Check the GitHub repository for issues and discussions.
|
|
56
|
-
</Accordion>
|
|
57
|
-
<Accordion title="How do I report bugs?">
|
|
58
|
-
Open an issue on GitHub with reproduction steps.
|
|
59
|
-
</Accordion>
|
|
60
|
-
</AccordionGroup>
|
|
@@ -58,11 +58,13 @@ export const metadata: Metadata = {
|
|
|
58
58
|
siteName,
|
|
59
59
|
title: siteName,
|
|
60
60
|
description: siteDescription,
|
|
61
|
+
images: ['/og-image.png'],
|
|
61
62
|
},
|
|
62
63
|
twitter: {
|
|
63
64
|
card: 'summary_large_image',
|
|
64
65
|
title: siteName,
|
|
65
66
|
description: siteDescription,
|
|
67
|
+
images: ['/og-image.png'],
|
|
66
68
|
},
|
|
67
69
|
icons: {
|
|
68
70
|
icon: '/favicon.svg',
|
|
@@ -110,7 +112,7 @@ export default function RootLayout({
|
|
|
110
112
|
</>
|
|
111
113
|
)}
|
|
112
114
|
</head>
|
|
113
|
-
<body className={inter.className}
|
|
115
|
+
<body className={inter.className}>
|
|
114
116
|
<a href="#main-content" className="skip-to-content">Skip to content</a>
|
|
115
117
|
<SyntaxThemeProvider>
|
|
116
118
|
{children}
|
|
@@ -2,6 +2,7 @@ import Link from 'next/link'
|
|
|
2
2
|
import { readFileSync } from 'fs'
|
|
3
3
|
import { join } from 'path'
|
|
4
4
|
import { ArrowRight, BookOpen, Zap, Code } from 'lucide-react'
|
|
5
|
+
import { hasTabs, getAllPagesFlat, type Navigation } from '@/lib/navigation'
|
|
5
6
|
|
|
6
7
|
function getDocsConfig() {
|
|
7
8
|
const configPath = join(process.cwd(), 'docs.json')
|
|
@@ -9,17 +10,30 @@ function getDocsConfig() {
|
|
|
9
10
|
return JSON.parse(content)
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
/** Get flat groups for display on the home page (works with both formats) */
|
|
14
|
+
function getDisplayGroups(navigation: Navigation): Array<{ group: string; pages: Array<{ title: string; path: string }> }> {
|
|
15
|
+
if (hasTabs(navigation)) {
|
|
16
|
+
const groups: Array<{ group: string; pages: Array<{ title: string; path: string }> }> = []
|
|
17
|
+
for (const tab of navigation) {
|
|
18
|
+
if (tab.groups) {
|
|
19
|
+
for (const g of tab.groups) {
|
|
20
|
+
groups.push(g as any)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return groups
|
|
25
|
+
}
|
|
26
|
+
return navigation as any
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
export default function Home() {
|
|
13
30
|
const docsConfig = getDocsConfig()
|
|
14
|
-
const
|
|
31
|
+
const allPagesFlat = getAllPagesFlat(docsConfig.navigation || [])
|
|
32
|
+
const firstPage = allPagesFlat[0]?.path || '/docs'
|
|
15
33
|
|
|
16
34
|
// Collect first 6 pages for quick links
|
|
17
|
-
const allPages
|
|
18
|
-
|
|
19
|
-
for (const page of group.pages || []) {
|
|
20
|
-
if (allPages.length < 6) allPages.push(page)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
35
|
+
const allPages = allPagesFlat.slice(0, 6)
|
|
36
|
+
const displayGroups = getDisplayGroups(docsConfig.navigation || [])
|
|
23
37
|
|
|
24
38
|
return (
|
|
25
39
|
<main id="main-content" className="min-h-screen flex flex-col">
|
|
@@ -64,7 +78,7 @@ export default function Home() {
|
|
|
64
78
|
|
|
65
79
|
{/* Feature cards — show first page title + description from each group */}
|
|
66
80
|
<div className="flex flex-wrap justify-center gap-4 max-w-2xl mx-auto mt-16 w-full">
|
|
67
|
-
{
|
|
81
|
+
{displayGroups.slice(0, 3).map((group: { group: string; pages: Array<{ title: string; path: string }> }, i: number) => {
|
|
68
82
|
const icons = [BookOpen, Code, Zap]
|
|
69
83
|
const Icon = icons[i % icons.length]
|
|
70
84
|
const firstGroupPage = group.pages?.[0]
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
import { useChat } from '@ai-sdk/react'
|
|
5
|
+
import { Streamdown } from 'streamdown'
|
|
6
|
+
import { MessageSquare, X, Send, ExternalLink } from 'lucide-react'
|
|
7
|
+
|
|
8
|
+
interface Citation {
|
|
9
|
+
title: string
|
|
10
|
+
path: string
|
|
11
|
+
snippet: string
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
interface AIChatProps {
|
|
@@ -22,60 +23,49 @@ export function AIChat({
|
|
|
22
23
|
placeholder = 'Ask a question...',
|
|
23
24
|
}: AIChatProps) {
|
|
24
25
|
const [isOpen, setIsOpen] = useState(false)
|
|
25
|
-
const [
|
|
26
|
-
const [input, setInput] = useState('')
|
|
27
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
26
|
+
const [citations, setCitations] = useState<Map<string, Citation[]>>(new Map())
|
|
28
27
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
const { messages, input, handleInputChange, handleSubmit, status, error } = useChat({
|
|
30
|
+
api: apiEndpoint,
|
|
31
|
+
onResponse(response: Response) {
|
|
32
|
+
// Extract citations from response header
|
|
33
|
+
const citationsHeader = response.headers.get('X-Citations')
|
|
34
|
+
if (citationsHeader) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(atob(citationsHeader)) as Citation[]
|
|
37
|
+
// Associate citations with the next assistant message
|
|
38
|
+
setCitations(prev => {
|
|
39
|
+
const next = new Map(prev)
|
|
40
|
+
// Use a temporary key that will be replaced when the message arrives
|
|
41
|
+
next.set('_pending', parsed)
|
|
42
|
+
return next
|
|
43
|
+
})
|
|
44
|
+
} catch {
|
|
45
|
+
// Skip invalid citations
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
onFinish(message: { id: string }) {
|
|
50
|
+
// Move pending citations to the actual message ID
|
|
51
|
+
setCitations(prev => {
|
|
52
|
+
const next = new Map(prev)
|
|
53
|
+
const pending = next.get('_pending')
|
|
54
|
+
if (pending) {
|
|
55
|
+
next.delete('_pending')
|
|
56
|
+
next.set(message.id, pending)
|
|
57
|
+
}
|
|
58
|
+
return next
|
|
50
59
|
})
|
|
60
|
+
},
|
|
61
|
+
})
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
63
|
+
const isStreaming = status === 'streaming'
|
|
64
|
+
const isLoading = status === 'submitted'
|
|
55
65
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{
|
|
60
|
-
id: Date.now().toString(),
|
|
61
|
-
role: 'assistant',
|
|
62
|
-
content: data.content,
|
|
63
|
-
citations: data.citations,
|
|
64
|
-
},
|
|
65
|
-
])
|
|
66
|
-
} catch (err) {
|
|
67
|
-
setMessages(prev => [
|
|
68
|
-
...prev,
|
|
69
|
-
{
|
|
70
|
-
id: Date.now().toString(),
|
|
71
|
-
role: 'assistant',
|
|
72
|
-
content: 'Sorry, I encountered an error. Please try again.',
|
|
73
|
-
},
|
|
74
|
-
])
|
|
75
|
-
} finally {
|
|
76
|
-
setIsLoading(false)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
68
|
+
}, [messages, isStreaming])
|
|
79
69
|
|
|
80
70
|
if (!isOpen) {
|
|
81
71
|
return (
|
|
@@ -116,7 +106,7 @@ export function AIChat({
|
|
|
116
106
|
{messages.length === 0 && (
|
|
117
107
|
<div className="text-center text-sm text-[var(--color-text-tertiary)] py-8">
|
|
118
108
|
<p>Ask me anything about {projectName}.</p>
|
|
119
|
-
<p className="mt-2 text-xs">I
|
|
109
|
+
<p className="mt-2 text-xs">I'll search the docs and give you an answer with sources.</p>
|
|
120
110
|
</div>
|
|
121
111
|
)}
|
|
122
112
|
|
|
@@ -132,14 +122,25 @@ export function AIChat({
|
|
|
132
122
|
: 'bg-[var(--color-bg-tertiary)] text-[var(--color-text)]'
|
|
133
123
|
}`}
|
|
134
124
|
>
|
|
135
|
-
|
|
125
|
+
{message.role === 'assistant' ? (
|
|
126
|
+
<div className="chat-markdown prose prose-sm max-w-none">
|
|
127
|
+
<Streamdown
|
|
128
|
+
shikiTheme={['github-light', 'github-dark']}
|
|
129
|
+
isAnimating={isStreaming && message.id === messages[messages.length - 1]?.id}
|
|
130
|
+
>
|
|
131
|
+
{message.content}
|
|
132
|
+
</Streamdown>
|
|
133
|
+
</div>
|
|
134
|
+
) : (
|
|
135
|
+
<p className="whitespace-pre-wrap">{message.content}</p>
|
|
136
|
+
)}
|
|
136
137
|
|
|
137
138
|
{/* Citations */}
|
|
138
|
-
{
|
|
139
|
+
{citations.get(message.id) && (
|
|
139
140
|
<div className="mt-3 border-t border-[var(--color-border)] pt-2">
|
|
140
141
|
<p className="text-xs font-medium text-[var(--color-text-tertiary)] mb-1">Sources:</p>
|
|
141
142
|
<div className="space-y-1">
|
|
142
|
-
{message.
|
|
143
|
+
{citations.get(message.id)!.map((citation, j) => (
|
|
143
144
|
<a
|
|
144
145
|
key={j}
|
|
145
146
|
href={citation.path}
|
|
@@ -159,12 +160,20 @@ export function AIChat({
|
|
|
159
160
|
{isLoading && (
|
|
160
161
|
<div className="flex justify-start">
|
|
161
162
|
<div className="flex items-center gap-2 rounded-2xl bg-[var(--color-bg-tertiary)] px-4 py-2 text-sm text-[var(--color-text-tertiary)]">
|
|
162
|
-
<
|
|
163
|
+
<span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
163
164
|
Searching docs...
|
|
164
165
|
</div>
|
|
165
166
|
</div>
|
|
166
167
|
)}
|
|
167
168
|
|
|
169
|
+
{error && (
|
|
170
|
+
<div className="flex justify-start">
|
|
171
|
+
<div className="rounded-2xl bg-red-50 px-4 py-2 text-sm text-red-600">
|
|
172
|
+
Something went wrong. Please try again.
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
|
|
168
177
|
<div ref={messagesEndRef} />
|
|
169
178
|
</div>
|
|
170
179
|
|
|
@@ -177,14 +186,14 @@ export function AIChat({
|
|
|
177
186
|
<input
|
|
178
187
|
type="text"
|
|
179
188
|
value={input}
|
|
180
|
-
onChange={
|
|
189
|
+
onChange={handleInputChange}
|
|
181
190
|
placeholder={placeholder}
|
|
182
|
-
disabled={isLoading}
|
|
191
|
+
disabled={isStreaming || isLoading}
|
|
183
192
|
className="flex-1 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-secondary)] px-4 py-2 text-sm text-[var(--color-text)] outline-none transition-colors placeholder:text-[var(--color-text-tertiary)] focus:border-[var(--color-border-strong)]"
|
|
184
193
|
/>
|
|
185
194
|
<button
|
|
186
195
|
type="submit"
|
|
187
|
-
disabled={!input.trim() || isLoading}
|
|
196
|
+
disabled={!input.trim() || isStreaming || isLoading}
|
|
188
197
|
className="flex h-10 w-10 items-center justify-center rounded-xl bg-[var(--color-text)] text-[var(--color-bg)] transition-colors hover:opacity-90 disabled:opacity-50"
|
|
189
198
|
aria-label="Send message"
|
|
190
199
|
>
|
|
@@ -3,41 +3,39 @@
|
|
|
3
3
|
import Link from 'next/link'
|
|
4
4
|
import { usePathname } from 'next/navigation'
|
|
5
5
|
import { ChevronRight } from 'lucide-react'
|
|
6
|
+
import { hasTabs, isNavGroup, type Navigation, type NavPage, type NavGroup } from '@/lib/navigation'
|
|
6
7
|
|
|
7
|
-
interface
|
|
8
|
-
interface NavGroup { group: string; pages: (NavPage | NavGroup)[] }
|
|
9
|
-
interface DocsConfig { navigation: NavGroup[] }
|
|
8
|
+
interface DocsConfig { navigation: Navigation }
|
|
10
9
|
|
|
11
|
-
function
|
|
12
|
-
return 'group' in item && !('path' in item)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function buildTitleMap(docsConfig?: DocsConfig): Map<string, string> {
|
|
10
|
+
function buildTitleMap(navigation: Navigation): Map<string, string> {
|
|
16
11
|
const map = new Map<string, string>()
|
|
17
|
-
if (!docsConfig) return map
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
for (const item of
|
|
13
|
+
function fillFromPages(pages: (NavPage | NavGroup)[]) {
|
|
14
|
+
for (const item of pages) {
|
|
21
15
|
if (isNavGroup(item)) {
|
|
22
|
-
|
|
16
|
+
fillFromPages(item.pages)
|
|
23
17
|
} else {
|
|
24
18
|
map.set(item.path, item.title)
|
|
25
|
-
if (item.pages)
|
|
19
|
+
if (item.pages) fillFromPages(item.pages)
|
|
26
20
|
}
|
|
27
21
|
}
|
|
28
22
|
}
|
|
29
|
-
return map
|
|
30
|
-
}
|
|
31
23
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
if (hasTabs(navigation)) {
|
|
25
|
+
for (const tab of navigation) {
|
|
26
|
+
if (tab.groups) {
|
|
27
|
+
for (const group of tab.groups) {
|
|
28
|
+
fillFromPages(group.pages)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
for (const group of navigation) {
|
|
34
|
+
fillFromPages(group.pages)
|
|
39
35
|
}
|
|
40
36
|
}
|
|
37
|
+
|
|
38
|
+
return map
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
interface BreadcrumbsProps {
|
|
@@ -50,7 +48,7 @@ export function Breadcrumbs({ docsConfig }: BreadcrumbsProps) {
|
|
|
50
48
|
|
|
51
49
|
if (segments.length <= 1) return null
|
|
52
50
|
|
|
53
|
-
const titleMap = buildTitleMap(docsConfig)
|
|
51
|
+
const titleMap = docsConfig ? buildTitleMap(docsConfig.navigation) : new Map<string, string>()
|
|
54
52
|
|
|
55
53
|
const crumbs = segments.map((segment, index) => {
|
|
56
54
|
const href = '/' + segments.slice(0, index + 1).join('/')
|
|
@@ -17,15 +17,19 @@ export function CopyButton({ text, className = '' }: CopyButtonProps) {
|
|
|
17
17
|
setCopied(true)
|
|
18
18
|
setTimeout(() => setCopied(false), 2000)
|
|
19
19
|
} catch {
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
// Deprecated fallback for older browsers that don't support navigator.clipboard
|
|
21
|
+
try {
|
|
22
|
+
const textarea = document.createElement('textarea')
|
|
23
|
+
textarea.value = text
|
|
24
|
+
textarea.style.position = 'fixed'
|
|
25
|
+
textarea.style.opacity = '0'
|
|
26
|
+
document.body.appendChild(textarea)
|
|
27
|
+
textarea.select()
|
|
28
|
+
document.execCommand('copy') // deprecated but kept for old browser compat
|
|
29
|
+
document.body.removeChild(textarea)
|
|
30
|
+
} catch {
|
|
31
|
+
// Copy failed in both methods — silently ignore
|
|
32
|
+
}
|
|
29
33
|
setCopied(true)
|
|
30
34
|
setTimeout(() => setCopied(false), 2000)
|
|
31
35
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Copy, Check } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
interface CopyPageButtonProps {
|
|
7
|
+
content: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function CopyPageButton({ content }: CopyPageButtonProps) {
|
|
11
|
+
const [copied, setCopied] = useState(false)
|
|
12
|
+
|
|
13
|
+
async function handleCopy() {
|
|
14
|
+
try {
|
|
15
|
+
await navigator.clipboard.writeText(content)
|
|
16
|
+
} catch {
|
|
17
|
+
// Deprecated fallback for older browsers that don't support navigator.clipboard
|
|
18
|
+
try {
|
|
19
|
+
const textarea = document.createElement('textarea')
|
|
20
|
+
textarea.value = content
|
|
21
|
+
textarea.style.position = 'fixed'
|
|
22
|
+
textarea.style.opacity = '0'
|
|
23
|
+
document.body.appendChild(textarea)
|
|
24
|
+
textarea.select()
|
|
25
|
+
document.execCommand('copy') // deprecated but kept for old browser compat
|
|
26
|
+
document.body.removeChild(textarea)
|
|
27
|
+
} catch {
|
|
28
|
+
// Copy failed in both methods — silently ignore
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
setCopied(true)
|
|
32
|
+
setTimeout(() => setCopied(false), 2000)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<button
|
|
37
|
+
onClick={handleCopy}
|
|
38
|
+
className="flex items-center gap-1.5 shrink-0 mt-1 px-2.5 py-1.5 text-[0.75rem] text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] border border-[var(--color-border)] rounded-lg hover:bg-[var(--color-bg-secondary)] transition-colors"
|
|
39
|
+
title={copied ? 'Copied!' : 'Copy page content'}
|
|
40
|
+
>
|
|
41
|
+
{copied ? (
|
|
42
|
+
<>
|
|
43
|
+
<Check size={12} />
|
|
44
|
+
Copied
|
|
45
|
+
</>
|
|
46
|
+
) : (
|
|
47
|
+
<>
|
|
48
|
+
<Copy size={12} />
|
|
49
|
+
Copy page
|
|
50
|
+
</>
|
|
51
|
+
)}
|
|
52
|
+
</button>
|
|
53
|
+
)
|
|
54
|
+
}
|