@inferencesh/ui 0.0.0-stage → 0.1.2
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 +22 -0
- package/README.md +68 -2
- package/components.json +22 -0
- package/package.json +231 -4
- package/src/components/audio-player.tsx +549 -0
- package/src/components/button.tsx +13 -0
- package/src/components/chart.tsx +403 -0
- package/src/components/circuit.tsx +276 -0
- package/src/components/code-block/code-block.tsx +260 -0
- package/src/components/code-block/index.ts +18 -0
- package/src/components/code-block/languages.ts +311 -0
- package/src/components/code-block/styles.ts +39 -0
- package/src/components/code-block/tokenizer.ts +290 -0
- package/src/components/code-block/types.ts +38 -0
- package/src/components/code-block/utils.ts +39 -0
- package/src/components/code.tsx +48 -0
- package/src/components/cookie-consent.tsx +137 -0
- package/src/components/copy-button.tsx +44 -0
- package/src/components/country-dropdown.tsx +188 -0
- package/src/components/glow-loader-fast.tsx +32 -0
- package/src/components/glow-loader-mono.tsx +33 -0
- package/src/components/glow-loader-sm-fast.tsx +32 -0
- package/src/components/glow-loader-sm-mono.tsx +34 -0
- package/src/components/glow-loader-sm.tsx +32 -0
- package/src/components/glow-loader.tsx +32 -0
- package/src/components/glow-logo.tsx +32 -0
- package/src/components/gradient.tsx +80 -0
- package/src/components/index.ts +45 -0
- package/src/components/inline-sidebar.tsx +351 -0
- package/src/components/input-color.tsx +349 -0
- package/src/components/logo-base.tsx +213 -0
- package/src/components/logo-color.tsx +23 -0
- package/src/components/logo-full.tsx +71 -0
- package/src/components/logo-svg.tsx +23 -0
- package/src/components/menu-navigation.tsx +359 -0
- package/src/components/multi-select.tsx +721 -0
- package/src/components/pastel-gradient.tsx +25 -0
- package/src/components/pixel-symmetry.tsx +151 -0
- package/src/components/progress.tsx +28 -0
- package/src/components/scroll-more.tsx +131 -0
- package/src/components/select.tsx +85 -0
- package/src/components/shimmering-text.tsx +108 -0
- package/src/components/sidebar-light.tsx +102 -0
- package/src/components/sonner.tsx +38 -0
- package/src/components/theme-switcher-sidebar.tsx +56 -0
- package/src/components/theme-switcher-simple.tsx +110 -0
- package/src/components/theme-switcher.tsx +47 -0
- package/src/components/toggle-group.tsx +55 -0
- package/src/components/tooltip-button.tsx +91 -0
- package/src/components/tooltip.tsx +71 -0
- package/src/components/tree-view.tsx +499 -0
- package/src/components/unglow-loader.tsx +33 -0
- package/src/components/waveform.tsx +1632 -0
- package/src/components/zoomable-image.tsx +76 -0
- package/src/docs/api-docs.tsx +763 -0
- package/src/docs/callout.tsx +160 -0
- package/src/docs/code-group.tsx +215 -0
- package/src/docs/copy-menu.tsx +143 -0
- package/src/docs/doc-tabs.tsx +84 -0
- package/src/docs/index.ts +16 -0
- package/src/docs/steps.tsx +105 -0
- package/src/docs/table-of-contents.tsx +174 -0
- package/src/examples/guide-markdown.ts +129 -0
- package/src/examples/index.ts +1153 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/use-copy-to-clipboard.ts +36 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/lib/display-name.ts +15 -0
- package/src/lib/example-input.ts +130 -0
- package/src/lib/plans.ts +44 -0
- package/src/lib/safe-content.ts +137 -0
- package/src/lib/schema.ts +320 -0
- package/src/lib/storage.ts +108 -0
- package/src/markdown/docs-markdown-renderer.tsx +518 -0
- package/src/markdown/helpers.ts +95 -0
- package/src/markdown/index.ts +24 -0
- package/src/markdown/markdown-renderer.tsx +232 -0
- package/src/markdown/youtube-embed.tsx +43 -0
- package/src/og/image.ts +312 -0
- package/src/og/index.tsx +430 -0
- package/src/og/query.ts +26 -0
- package/src/shadcn/accordion.tsx +71 -0
- package/src/shadcn/alert-dialog.tsx +187 -0
- package/src/shadcn/alert.tsx +75 -0
- package/src/shadcn/aspect-ratio.tsx +22 -0
- package/src/shadcn/attachment.tsx +207 -0
- package/src/shadcn/avatar.tsx +106 -0
- package/src/shadcn/badge.tsx +51 -0
- package/src/shadcn/breadcrumb.tsx +124 -0
- package/src/shadcn/bubble.tsx +127 -0
- package/src/shadcn/button-group.tsx +87 -0
- package/src/shadcn/button.tsx +57 -0
- package/src/shadcn/calendar.tsx +219 -0
- package/src/shadcn/card.tsx +102 -0
- package/src/shadcn/carousel.tsx +242 -0
- package/src/shadcn/chart.tsx +372 -0
- package/src/shadcn/checkbox.tsx +28 -0
- package/src/shadcn/collapsible.tsx +19 -0
- package/src/shadcn/combobox.tsx +297 -0
- package/src/shadcn/command.tsx +194 -0
- package/src/shadcn/context-menu.tsx +270 -0
- package/src/shadcn/dialog.tsx +158 -0
- package/src/shadcn/direction.tsx +4 -0
- package/src/shadcn/drawer.tsx +227 -0
- package/src/shadcn/dropdown-menu.tsx +265 -0
- package/src/shadcn/empty.tsx +103 -0
- package/src/shadcn/field.tsx +238 -0
- package/src/shadcn/form.tsx +173 -0
- package/src/shadcn/hover-card.tsx +50 -0
- package/src/shadcn/index.ts +53 -0
- package/src/shadcn/input-group.tsx +156 -0
- package/src/shadcn/input-otp.tsx +84 -0
- package/src/shadcn/input.tsx +19 -0
- package/src/shadcn/item.tsx +201 -0
- package/src/shadcn/kbd.tsx +26 -0
- package/src/shadcn/label.tsx +19 -0
- package/src/shadcn/marker.tsx +70 -0
- package/src/shadcn/menubar.tsx +278 -0
- package/src/shadcn/message-scroller.tsx +131 -0
- package/src/shadcn/message.tsx +91 -0
- package/src/shadcn/native-select.tsx +60 -0
- package/src/shadcn/navigation-menu.tsx +167 -0
- package/src/shadcn/pagination.tsx +130 -0
- package/src/shadcn/popover.tsx +87 -0
- package/src/shadcn/progress.tsx +82 -0
- package/src/shadcn/radio-group.tsx +35 -0
- package/src/shadcn/resizable.tsx +49 -0
- package/src/shadcn/scroll-area.tsx +52 -0
- package/src/shadcn/select.tsx +200 -0
- package/src/shadcn/separator.tsx +22 -0
- package/src/shadcn/sheet.tsx +136 -0
- package/src/shadcn/sidebar.tsx +721 -0
- package/src/shadcn/skeleton.tsx +13 -0
- package/src/shadcn/slider.tsx +51 -0
- package/src/shadcn/sonner.tsx +49 -0
- package/src/shadcn/spinner.tsx +10 -0
- package/src/shadcn/switch.tsx +29 -0
- package/src/shadcn/table.tsx +115 -0
- package/src/shadcn/tabs.tsx +79 -0
- package/src/shadcn/textarea.tsx +17 -0
- package/src/shadcn/toggle-group.tsx +89 -0
- package/src/shadcn/toggle.tsx +44 -0
- package/src/shadcn/tooltip.tsx +63 -0
- package/src/theme/config.ts +48 -0
- package/src/theme/css/base.css +92 -0
- package/src/theme/css/bitboy.css +75 -0
- package/src/theme/css/dark.css +47 -0
- package/src/theme/css/designer.css +122 -0
- package/src/theme/css/fonts.css +33 -0
- package/src/theme/css/globals.css +14 -0
- package/src/theme/css/hackintosh.css +57 -0
- package/src/theme/css/llama.css +103 -0
- package/src/theme/css/safelist.css +39 -0
- package/src/theme/css/shadcn.css +77 -0
- package/src/theme/css/theme.css +216 -0
- package/src/theme/embed-theme.ts +146 -0
- package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
- package/src/theme/fonts/Hack-Regular.woff2 +0 -0
- package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
- package/src/theme/fonts/LICENSE-Hack.txt +45 -0
- package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
- package/src/theme/fonts/Mona-Sans.woff2 +0 -0
- package/src/theme/index.ts +15 -0
- package/src/utils/cn.ts +3 -0
- package/src/utils/index.ts +2 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import type { Token, TokenType, LanguageDefinition } from './types'
|
|
2
|
+
import { getLanguage } from './languages'
|
|
3
|
+
|
|
4
|
+
export interface TokenizeContext {
|
|
5
|
+
/** Whether we're inside a multiline template literal */
|
|
6
|
+
inTemplateLiteral?: boolean
|
|
7
|
+
/** Whether we're inside a multiline comment */
|
|
8
|
+
inMultilineComment?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the keyword type for a word in a given language
|
|
13
|
+
*/
|
|
14
|
+
function getKeywordType(word: string, lang: LanguageDefinition): TokenType | null {
|
|
15
|
+
const { keywords } = lang
|
|
16
|
+
|
|
17
|
+
if (keywords.import?.has(word)) return 'keyword-import'
|
|
18
|
+
if (keywords.declaration?.has(word)) return 'keyword-declaration'
|
|
19
|
+
if (keywords.control?.has(word)) return 'keyword-control'
|
|
20
|
+
if (keywords.value?.has(word)) return 'keyword-value'
|
|
21
|
+
if (keywords.type?.has(word)) return 'type'
|
|
22
|
+
if (keywords.other?.has(word)) return 'keyword-other'
|
|
23
|
+
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Try to match a pattern at the start of the remaining code
|
|
29
|
+
*/
|
|
30
|
+
function tryMatch(remaining: string, patterns: RegExp[] | undefined): string | null {
|
|
31
|
+
if (!patterns) return null
|
|
32
|
+
|
|
33
|
+
for (const pattern of patterns) {
|
|
34
|
+
const match = remaining.match(pattern)
|
|
35
|
+
if (match) {
|
|
36
|
+
return match[0]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if language supports JSX
|
|
45
|
+
*/
|
|
46
|
+
function isJsxLanguage(languageName: string): boolean {
|
|
47
|
+
const jsxLangs = ['javascript', 'typescript', 'jsx', 'tsx', 'js', 'ts']
|
|
48
|
+
return jsxLangs.includes(languageName.toLowerCase())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if language is JSON
|
|
53
|
+
*/
|
|
54
|
+
function isJsonLanguage(languageName: string): boolean {
|
|
55
|
+
return ['json', 'jsonc'].includes(languageName.toLowerCase())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Heuristic: detect if a line likely starts inside a JSX tag
|
|
60
|
+
* This handles multi-line JSX where attributes are on separate lines
|
|
61
|
+
*
|
|
62
|
+
* Patterns that suggest we're inside a JSX tag:
|
|
63
|
+
* - Line starts with whitespace + attribute pattern (word followed by = and { or " or ')
|
|
64
|
+
* - Line starts with whitespace + />
|
|
65
|
+
* - Line starts with whitespace + > followed by content or closing tag
|
|
66
|
+
*/
|
|
67
|
+
function detectJsxContext(code: string): boolean {
|
|
68
|
+
// Check for: whitespace + /> (self-closing)
|
|
69
|
+
if (/^\s+\/>/.test(code)) {
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
// Check for: whitespace + > (closing bracket, but not >= operator)
|
|
73
|
+
if (/^\s+>(?!=)/.test(code)) {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
// Check for JSX attribute: whitespace + word + = + { or " or ' (not word=word which is normal code)
|
|
77
|
+
// This distinguishes " propName={" from " const x ="
|
|
78
|
+
if (/^\s+[a-zA-Z_][a-zA-Z0-9_-]*\s*=\s*[{{"']/.test(code)) {
|
|
79
|
+
return true
|
|
80
|
+
}
|
|
81
|
+
// Check for: whitespace + { (JSX expression in attribute value on its own line)
|
|
82
|
+
if (/^\s+\{\s*$/.test(code) || /^\s+\{[^}]*$/.test(code)) {
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Tokenize a line of code with context for multiline constructs
|
|
90
|
+
*/
|
|
91
|
+
export function tokenize(
|
|
92
|
+
code: string,
|
|
93
|
+
languageName: string,
|
|
94
|
+
context?: TokenizeContext
|
|
95
|
+
): { tokens: Token[]; context: TokenizeContext } {
|
|
96
|
+
const lang = getLanguage(languageName)
|
|
97
|
+
const tokens: Token[] = []
|
|
98
|
+
let remaining = code
|
|
99
|
+
const supportsJsx = isJsxLanguage(languageName)
|
|
100
|
+
const isJson = isJsonLanguage(languageName)
|
|
101
|
+
|
|
102
|
+
// Carry over context from previous line
|
|
103
|
+
let inTemplateLiteral = context?.inTemplateLiteral ?? false
|
|
104
|
+
|
|
105
|
+
// If we're inside a template literal from a previous line, treat the whole line as a string
|
|
106
|
+
// until we find a closing backtick
|
|
107
|
+
if (inTemplateLiteral) {
|
|
108
|
+
const closingIndex = remaining.indexOf('`')
|
|
109
|
+
if (closingIndex === -1) {
|
|
110
|
+
// No closing backtick, entire line is part of string
|
|
111
|
+
tokens.push({ type: 'string', content: remaining })
|
|
112
|
+
return { tokens, context: { inTemplateLiteral: true } }
|
|
113
|
+
} else {
|
|
114
|
+
// Found closing backtick
|
|
115
|
+
const stringPart = remaining.slice(0, closingIndex + 1)
|
|
116
|
+
tokens.push({ type: 'string', content: stringPart })
|
|
117
|
+
remaining = remaining.slice(closingIndex + 1)
|
|
118
|
+
inTemplateLiteral = false
|
|
119
|
+
// Continue tokenizing the rest of the line
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Track if we're inside a JSX tag (between < and >)
|
|
124
|
+
// Use heuristic to detect if line starts in JSX context
|
|
125
|
+
let inJsxTag = supportsJsx && detectJsxContext(code) && !inTemplateLiteral
|
|
126
|
+
|
|
127
|
+
while (remaining.length > 0) {
|
|
128
|
+
let matched = false
|
|
129
|
+
|
|
130
|
+
// Try to match comments (but not in JSX tag context)
|
|
131
|
+
if (!inJsxTag && lang?.patterns.comment) {
|
|
132
|
+
const comment = tryMatch(remaining, lang.patterns.comment)
|
|
133
|
+
if (comment) {
|
|
134
|
+
tokens.push({ type: 'comment', content: comment })
|
|
135
|
+
remaining = remaining.slice(comment.length)
|
|
136
|
+
matched = true
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// JSX handling for JS/TS languages
|
|
142
|
+
if (supportsJsx) {
|
|
143
|
+
// Opening tag: <Component or <div or </Component
|
|
144
|
+
const jsxOpenTag = remaining.match(/^<\/?([A-Z][a-zA-Z0-9]*|[a-z][a-z0-9-]*)/)
|
|
145
|
+
if (jsxOpenTag) {
|
|
146
|
+
// Push the < or </
|
|
147
|
+
const bracket = remaining[0] === '<' && remaining[1] === '/' ? '</' : '<'
|
|
148
|
+
tokens.push({ type: 'punctuation', content: bracket })
|
|
149
|
+
remaining = remaining.slice(bracket.length)
|
|
150
|
+
|
|
151
|
+
// Push the tag name
|
|
152
|
+
const tagName = jsxOpenTag[1]
|
|
153
|
+
tokens.push({ type: 'tag', content: tagName })
|
|
154
|
+
remaining = remaining.slice(tagName.length)
|
|
155
|
+
|
|
156
|
+
inJsxTag = true
|
|
157
|
+
matched = true
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Self-closing /> or closing >
|
|
162
|
+
if (inJsxTag) {
|
|
163
|
+
if (remaining.startsWith('/>')) {
|
|
164
|
+
tokens.push({ type: 'punctuation', content: '/>' })
|
|
165
|
+
remaining = remaining.slice(2)
|
|
166
|
+
inJsxTag = false
|
|
167
|
+
matched = true
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
if (remaining.startsWith('>')) {
|
|
171
|
+
tokens.push({ type: 'punctuation', content: '>' })
|
|
172
|
+
remaining = remaining.slice(1)
|
|
173
|
+
inJsxTag = false
|
|
174
|
+
matched = true
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// JSX attribute: propName= or propName (boolean) or propName (before />)
|
|
179
|
+
const jsxAttr = remaining.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)(?=\s*=|\s*\/>|\s*>|\s+[a-zA-Z_]|\s*$)/)
|
|
180
|
+
if (jsxAttr) {
|
|
181
|
+
tokens.push({ type: 'attribute', content: jsxAttr[1] })
|
|
182
|
+
remaining = remaining.slice(jsxAttr[1].length)
|
|
183
|
+
matched = true
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Try to match strings
|
|
190
|
+
if (lang?.patterns.string) {
|
|
191
|
+
const str = tryMatch(remaining, lang.patterns.string)
|
|
192
|
+
if (str) {
|
|
193
|
+
// In JSON, check if this string is a key (followed by :)
|
|
194
|
+
const afterStr = remaining.slice(str.length)
|
|
195
|
+
const isJsonKey = isJson && /^\s*:/.test(afterStr)
|
|
196
|
+
tokens.push({ type: isJsonKey ? 'property' : 'string', content: str })
|
|
197
|
+
remaining = remaining.slice(str.length)
|
|
198
|
+
matched = true
|
|
199
|
+
continue
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check for template literal that starts but doesn't end (multiline)
|
|
203
|
+
// Only trigger if we're AT a backtick and it doesn't close on this line
|
|
204
|
+
if (remaining[0] === '`') {
|
|
205
|
+
// Template literal starts but no closing backtick on this line
|
|
206
|
+
tokens.push({ type: 'string', content: remaining })
|
|
207
|
+
return { tokens, context: { inTemplateLiteral: true } }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Fallback string patterns for unknown languages
|
|
212
|
+
if (!lang) {
|
|
213
|
+
const doubleStr = remaining.match(/^"(?:[^"\\]|\\.)*"/)
|
|
214
|
+
if (doubleStr) {
|
|
215
|
+
tokens.push({ type: 'string', content: doubleStr[0] })
|
|
216
|
+
remaining = remaining.slice(doubleStr[0].length)
|
|
217
|
+
matched = true
|
|
218
|
+
continue
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const singleStr = remaining.match(/^'(?:[^'\\]|\\.)*'/)
|
|
222
|
+
if (singleStr) {
|
|
223
|
+
tokens.push({ type: 'string', content: singleStr[0] })
|
|
224
|
+
remaining = remaining.slice(singleStr[0].length)
|
|
225
|
+
matched = true
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Numbers
|
|
231
|
+
const number = remaining.match(/^-?\b\d+\.?\d*(?:e[+-]?\d+)?\b/i)
|
|
232
|
+
if (number) {
|
|
233
|
+
tokens.push({ type: 'number', content: number[0] })
|
|
234
|
+
remaining = remaining.slice(number[0].length)
|
|
235
|
+
matched = true
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Keywords and identifiers (when not in JSX tag)
|
|
240
|
+
const word = remaining.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/)
|
|
241
|
+
if (word) {
|
|
242
|
+
const afterWord = remaining.slice(word[0].length)
|
|
243
|
+
// Check if this is an object property key (word followed by : but not ::)
|
|
244
|
+
const isPropertyKey = /^\s*:(?!:)/.test(afterWord)
|
|
245
|
+
|
|
246
|
+
let tokenType: TokenType | null = null
|
|
247
|
+
if (isPropertyKey) {
|
|
248
|
+
tokenType = 'property'
|
|
249
|
+
} else if (lang && !inJsxTag) {
|
|
250
|
+
tokenType = getKeywordType(word[0], lang)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
tokens.push({
|
|
254
|
+
type: tokenType,
|
|
255
|
+
content: word[0],
|
|
256
|
+
})
|
|
257
|
+
remaining = remaining.slice(word[0].length)
|
|
258
|
+
matched = true
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Operators (common across most languages)
|
|
263
|
+
const operator = remaining.match(
|
|
264
|
+
/^(?:===|!==|==|!=|<=|>=|=>|->|::|\.\.\.?|\?\?|\?\.|&&|\|\||[+\-*/%<>=!&|^~?:])/
|
|
265
|
+
)
|
|
266
|
+
if (operator) {
|
|
267
|
+
tokens.push({ type: 'operator', content: operator[0] })
|
|
268
|
+
remaining = remaining.slice(operator[0].length)
|
|
269
|
+
matched = true
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Punctuation
|
|
274
|
+
const punct = remaining.match(/^[{}[\]();,.<>]/)
|
|
275
|
+
if (punct) {
|
|
276
|
+
tokens.push({ type: 'punctuation', content: punct[0] })
|
|
277
|
+
remaining = remaining.slice(punct[0].length)
|
|
278
|
+
matched = true
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Default: single character (whitespace, etc.)
|
|
283
|
+
if (!matched) {
|
|
284
|
+
tokens.push({ type: null, content: remaining[0] })
|
|
285
|
+
remaining = remaining.slice(1)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { tokens, context: { inTemplateLiteral } }
|
|
290
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type TokenType =
|
|
2
|
+
| 'comment'
|
|
3
|
+
| 'string'
|
|
4
|
+
| 'keyword-import' // import, export, from, as
|
|
5
|
+
| 'keyword-declaration' // const, let, var, function, class, type, interface
|
|
6
|
+
| 'keyword-control' // if, else, return, for, while
|
|
7
|
+
| 'keyword-value' // true, false, null, undefined
|
|
8
|
+
| 'keyword-other' // new, this, typeof, etc.
|
|
9
|
+
| 'number'
|
|
10
|
+
| 'function'
|
|
11
|
+
| 'property'
|
|
12
|
+
| 'operator'
|
|
13
|
+
| 'punctuation'
|
|
14
|
+
| 'type' // type annotations in TS
|
|
15
|
+
| 'tag' // JSX/HTML tag names
|
|
16
|
+
| 'attribute' // JSX/HTML attributes
|
|
17
|
+
|
|
18
|
+
export interface Token {
|
|
19
|
+
type: TokenType | null
|
|
20
|
+
content: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LanguageDefinition {
|
|
24
|
+
name: string
|
|
25
|
+
aliases: string[]
|
|
26
|
+
keywords: {
|
|
27
|
+
import?: Set<string>
|
|
28
|
+
declaration?: Set<string>
|
|
29
|
+
control?: Set<string>
|
|
30
|
+
value?: Set<string>
|
|
31
|
+
other?: Set<string>
|
|
32
|
+
type?: Set<string>
|
|
33
|
+
}
|
|
34
|
+
patterns: {
|
|
35
|
+
comment?: RegExp[]
|
|
36
|
+
string?: RegExp[]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract text content from React children
|
|
5
|
+
* Handles strings, numbers, arrays, and nested elements
|
|
6
|
+
*/
|
|
7
|
+
export function getTextContent(node: React.ReactNode): string {
|
|
8
|
+
if (typeof node === 'string') return node
|
|
9
|
+
if (typeof node === 'number') return String(node)
|
|
10
|
+
if (!node) return ''
|
|
11
|
+
if (Array.isArray(node)) return node.map(getTextContent).join('')
|
|
12
|
+
if (React.isValidElement(node)) {
|
|
13
|
+
return getTextContent((node.props as { children?: React.ReactNode }).children)
|
|
14
|
+
}
|
|
15
|
+
return ''
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Split code into lines, removing trailing empty line if present
|
|
20
|
+
*/
|
|
21
|
+
export function splitLines(code: string): string[] {
|
|
22
|
+
const lines = code.split('\n')
|
|
23
|
+
if (lines.length > 1 && lines[lines.length - 1] === '') {
|
|
24
|
+
lines.pop()
|
|
25
|
+
}
|
|
26
|
+
return lines
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Copy text to clipboard
|
|
31
|
+
*/
|
|
32
|
+
export async function copyToClipboard(text: string): Promise<boolean> {
|
|
33
|
+
try {
|
|
34
|
+
await navigator.clipboard.writeText(text)
|
|
35
|
+
return true
|
|
36
|
+
} catch {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { tokenize } from './code-block/tokenizer'
|
|
5
|
+
import { getTokenStyle } from './code-block/styles'
|
|
6
|
+
import { normalizeLanguage } from './code-block/languages'
|
|
7
|
+
|
|
8
|
+
function HighlightedCode({ code, language }: { code: string; language: string }) {
|
|
9
|
+
const tokens = useMemo(() => tokenize(code.trim(), language).tokens, [code, language])
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
{tokens.map((token, i) => {
|
|
14
|
+
const style = getTokenStyle(token.type)
|
|
15
|
+
return style ? (
|
|
16
|
+
<span key={i} className={style}>{token.content}</span>
|
|
17
|
+
) : (
|
|
18
|
+
<span key={i}>{token.content}</span>
|
|
19
|
+
)
|
|
20
|
+
})}
|
|
21
|
+
</>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CodeProps {
|
|
26
|
+
code: string
|
|
27
|
+
language: string
|
|
28
|
+
title?: string
|
|
29
|
+
className?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Code({ code, language, title, className = '' }: CodeProps) {
|
|
33
|
+
const normalizedLang = normalizeLanguage(language)
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className={`rounded-lg border border-border bg-muted/40 dark:bg-muted/30 overflow-hidden text-xs max-h-96 overflow-y-auto ${className}`}>
|
|
37
|
+
{title && (
|
|
38
|
+
<div className="flex items-center justify-between px-4 py-2 border-b border-border/30 bg-muted/50">
|
|
39
|
+
<span className="text-xs font-mono text-muted-foreground">{title}</span>
|
|
40
|
+
<span className="text-xs text-muted-foreground/50">{language}</span>
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
<pre className="whitespace-pre-wrap font-mono leading-relaxed p-4">
|
|
44
|
+
<HighlightedCode code={code} language={normalizedLang} />
|
|
45
|
+
</pre>
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { CookieIcon } from "lucide-react";
|
|
2
|
+
import { Button } from "../shadcn/button";
|
|
3
|
+
import { cn } from "../utils/cn";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
|
|
6
|
+
const cookieDomain =
|
|
7
|
+
typeof window !== "undefined" &&
|
|
8
|
+
window.location.hostname.endsWith(".inference.sh")
|
|
9
|
+
? "; domain=.inference.sh"
|
|
10
|
+
: "";
|
|
11
|
+
|
|
12
|
+
interface CookieConsentProps {
|
|
13
|
+
variant?: "default" | "small";
|
|
14
|
+
demo?: boolean;
|
|
15
|
+
onAccept?: () => void;
|
|
16
|
+
onDecline?: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function CookieConsent({
|
|
20
|
+
variant = "default",
|
|
21
|
+
demo = false,
|
|
22
|
+
onAccept,
|
|
23
|
+
onDecline,
|
|
24
|
+
}: CookieConsentProps) {
|
|
25
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
26
|
+
const [hide, setHide] = useState(false);
|
|
27
|
+
|
|
28
|
+
const accept = (): void => {
|
|
29
|
+
setIsOpen(false);
|
|
30
|
+
document.cookie = `cookieConsent=true; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/${cookieDomain}`;
|
|
31
|
+
setTimeout(() => setHide(true), 700);
|
|
32
|
+
onAccept?.();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const decline = (): void => {
|
|
36
|
+
setIsOpen(false);
|
|
37
|
+
setTimeout(() => setHide(true), 700);
|
|
38
|
+
onDecline?.();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
try {
|
|
43
|
+
if (document.cookie.includes("cookieConsent=true")) {
|
|
44
|
+
if (!demo) {
|
|
45
|
+
setHide(true);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
setIsOpen(true);
|
|
50
|
+
} catch {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
}, [demo]);
|
|
54
|
+
|
|
55
|
+
return variant !== "small" ? (
|
|
56
|
+
<div
|
|
57
|
+
className={cn(
|
|
58
|
+
"fixed z-[200] bottom-0 left-0 right-0 sm:left-4 sm:bottom-4 w-full sm:max-w-md duration-700",
|
|
59
|
+
!isOpen
|
|
60
|
+
? "transition-[opacity,transform] translate-y-8 opacity-0"
|
|
61
|
+
: "transition-[opacity,transform] translate-y-0 opacity-100",
|
|
62
|
+
hide && "hidden",
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
<div className="dark:bg-card bg-background rounded-md m-3 border shadow-lg">
|
|
66
|
+
<div className="grid gap-2">
|
|
67
|
+
<div className="border-b h-14 flex items-center justify-between p-4">
|
|
68
|
+
<p className="text-lg font-medium">we use cookies</p>
|
|
69
|
+
<CookieIcon className="h-[1.2rem] w-[1.2rem]" />
|
|
70
|
+
</div>
|
|
71
|
+
<div className="p-4">
|
|
72
|
+
<p className="text-sm font-normal text-start">
|
|
73
|
+
we use cookies to ensure you get the best experience on our
|
|
74
|
+
website. for more information on how we use cookies, please see our
|
|
75
|
+
cookie policy.
|
|
76
|
+
<br />
|
|
77
|
+
<br />
|
|
78
|
+
<span className="text-xs">
|
|
79
|
+
by clicking "
|
|
80
|
+
<span className="font-medium opacity-80">accept</span>",
|
|
81
|
+
you agree to our use of cookies.
|
|
82
|
+
</span>
|
|
83
|
+
<br />
|
|
84
|
+
<a href="#" className="text-xs underline">
|
|
85
|
+
learn more.
|
|
86
|
+
</a>
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="flex gap-2 p-4 py-5 border-t dark:bg-background/20">
|
|
90
|
+
<Button onClick={accept} className="flex-1">
|
|
91
|
+
accept
|
|
92
|
+
</Button>
|
|
93
|
+
<Button onClick={decline} className="flex-1" variant="secondary">
|
|
94
|
+
decline
|
|
95
|
+
</Button>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
) : (
|
|
101
|
+
<div
|
|
102
|
+
className={cn(
|
|
103
|
+
"fixed z-[200] bottom-0 left-0 right-0 sm:left-4 sm:bottom-4 w-full sm:max-w-md duration-700",
|
|
104
|
+
!isOpen
|
|
105
|
+
? "transition-[opacity,transform] translate-y-8 opacity-0"
|
|
106
|
+
: "transition-[opacity,transform] translate-y-0 opacity-100",
|
|
107
|
+
hide && "hidden",
|
|
108
|
+
)}
|
|
109
|
+
>
|
|
110
|
+
<div className="m-3 dark:bg-card bg-background border rounded-lg">
|
|
111
|
+
<div className="flex items-center justify-between p-3">
|
|
112
|
+
<p className="text-lg font-medium">we use cookies</p>
|
|
113
|
+
<CookieIcon className="h-[1.2rem] w-[1.2rem]" />
|
|
114
|
+
</div>
|
|
115
|
+
<div className="p-3 -mt-2">
|
|
116
|
+
<p className="text-sm text-left text-muted-foreground">
|
|
117
|
+
we use cookies to ensure you get the best experience on our website.
|
|
118
|
+
for more information on how we use cookies, please see our cookie
|
|
119
|
+
policy.
|
|
120
|
+
</p>
|
|
121
|
+
</div>
|
|
122
|
+
<div className="p-3 flex items-center gap-2 mt-2 border-t">
|
|
123
|
+
<Button onClick={accept} className="flex-1 h-9 rounded-full">
|
|
124
|
+
accept
|
|
125
|
+
</Button>
|
|
126
|
+
<Button
|
|
127
|
+
onClick={decline}
|
|
128
|
+
className="flex-1 h-9 rounded-full"
|
|
129
|
+
variant="outline"
|
|
130
|
+
>
|
|
131
|
+
decline
|
|
132
|
+
</Button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Check, Copy } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { Button } from "../shadcn/button"
|
|
6
|
+
import { useCopyToClipboard } from "../hooks/use-copy-to-clipboard"
|
|
7
|
+
import { cn } from "cn"
|
|
8
|
+
|
|
9
|
+
type CopyButtonProps = {
|
|
10
|
+
content: string
|
|
11
|
+
copyMessage?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function CopyButton({ content, copyMessage }: CopyButtonProps) {
|
|
15
|
+
const { isCopied, handleCopy } = useCopyToClipboard({
|
|
16
|
+
text: content,
|
|
17
|
+
copyMessage,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Button
|
|
22
|
+
variant="ghost"
|
|
23
|
+
size="icon"
|
|
24
|
+
className="relative h-6 w-6"
|
|
25
|
+
aria-label="Copy to clipboard"
|
|
26
|
+
onClick={handleCopy}
|
|
27
|
+
>
|
|
28
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
29
|
+
<Check
|
|
30
|
+
className={cn(
|
|
31
|
+
"h-4 w-4 transition-transform ease-in-out",
|
|
32
|
+
isCopied ? "scale-100" : "scale-0"
|
|
33
|
+
)}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
<Copy
|
|
37
|
+
className={cn(
|
|
38
|
+
"h-4 w-4 transition-transform ease-in-out",
|
|
39
|
+
isCopied ? "scale-0" : "scale-100"
|
|
40
|
+
)}
|
|
41
|
+
/>
|
|
42
|
+
</Button>
|
|
43
|
+
)
|
|
44
|
+
}
|