@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.
Files changed (165) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +68 -2
  3. package/components.json +22 -0
  4. package/package.json +231 -4
  5. package/src/components/audio-player.tsx +549 -0
  6. package/src/components/button.tsx +13 -0
  7. package/src/components/chart.tsx +403 -0
  8. package/src/components/circuit.tsx +276 -0
  9. package/src/components/code-block/code-block.tsx +260 -0
  10. package/src/components/code-block/index.ts +18 -0
  11. package/src/components/code-block/languages.ts +311 -0
  12. package/src/components/code-block/styles.ts +39 -0
  13. package/src/components/code-block/tokenizer.ts +290 -0
  14. package/src/components/code-block/types.ts +38 -0
  15. package/src/components/code-block/utils.ts +39 -0
  16. package/src/components/code.tsx +48 -0
  17. package/src/components/cookie-consent.tsx +137 -0
  18. package/src/components/copy-button.tsx +44 -0
  19. package/src/components/country-dropdown.tsx +188 -0
  20. package/src/components/glow-loader-fast.tsx +32 -0
  21. package/src/components/glow-loader-mono.tsx +33 -0
  22. package/src/components/glow-loader-sm-fast.tsx +32 -0
  23. package/src/components/glow-loader-sm-mono.tsx +34 -0
  24. package/src/components/glow-loader-sm.tsx +32 -0
  25. package/src/components/glow-loader.tsx +32 -0
  26. package/src/components/glow-logo.tsx +32 -0
  27. package/src/components/gradient.tsx +80 -0
  28. package/src/components/index.ts +45 -0
  29. package/src/components/inline-sidebar.tsx +351 -0
  30. package/src/components/input-color.tsx +349 -0
  31. package/src/components/logo-base.tsx +213 -0
  32. package/src/components/logo-color.tsx +23 -0
  33. package/src/components/logo-full.tsx +71 -0
  34. package/src/components/logo-svg.tsx +23 -0
  35. package/src/components/menu-navigation.tsx +359 -0
  36. package/src/components/multi-select.tsx +721 -0
  37. package/src/components/pastel-gradient.tsx +25 -0
  38. package/src/components/pixel-symmetry.tsx +151 -0
  39. package/src/components/progress.tsx +28 -0
  40. package/src/components/scroll-more.tsx +131 -0
  41. package/src/components/select.tsx +85 -0
  42. package/src/components/shimmering-text.tsx +108 -0
  43. package/src/components/sidebar-light.tsx +102 -0
  44. package/src/components/sonner.tsx +38 -0
  45. package/src/components/theme-switcher-sidebar.tsx +56 -0
  46. package/src/components/theme-switcher-simple.tsx +110 -0
  47. package/src/components/theme-switcher.tsx +47 -0
  48. package/src/components/toggle-group.tsx +55 -0
  49. package/src/components/tooltip-button.tsx +91 -0
  50. package/src/components/tooltip.tsx +71 -0
  51. package/src/components/tree-view.tsx +499 -0
  52. package/src/components/unglow-loader.tsx +33 -0
  53. package/src/components/waveform.tsx +1632 -0
  54. package/src/components/zoomable-image.tsx +76 -0
  55. package/src/docs/api-docs.tsx +763 -0
  56. package/src/docs/callout.tsx +160 -0
  57. package/src/docs/code-group.tsx +215 -0
  58. package/src/docs/copy-menu.tsx +143 -0
  59. package/src/docs/doc-tabs.tsx +84 -0
  60. package/src/docs/index.ts +16 -0
  61. package/src/docs/steps.tsx +105 -0
  62. package/src/docs/table-of-contents.tsx +174 -0
  63. package/src/examples/guide-markdown.ts +129 -0
  64. package/src/examples/index.ts +1153 -0
  65. package/src/global.d.ts +1 -0
  66. package/src/hooks/use-copy-to-clipboard.ts +36 -0
  67. package/src/hooks/use-mobile.ts +19 -0
  68. package/src/lib/display-name.ts +15 -0
  69. package/src/lib/example-input.ts +130 -0
  70. package/src/lib/plans.ts +44 -0
  71. package/src/lib/safe-content.ts +137 -0
  72. package/src/lib/schema.ts +320 -0
  73. package/src/lib/storage.ts +108 -0
  74. package/src/markdown/docs-markdown-renderer.tsx +518 -0
  75. package/src/markdown/helpers.ts +95 -0
  76. package/src/markdown/index.ts +24 -0
  77. package/src/markdown/markdown-renderer.tsx +232 -0
  78. package/src/markdown/youtube-embed.tsx +43 -0
  79. package/src/og/image.ts +312 -0
  80. package/src/og/index.tsx +430 -0
  81. package/src/og/query.ts +26 -0
  82. package/src/shadcn/accordion.tsx +71 -0
  83. package/src/shadcn/alert-dialog.tsx +187 -0
  84. package/src/shadcn/alert.tsx +75 -0
  85. package/src/shadcn/aspect-ratio.tsx +22 -0
  86. package/src/shadcn/attachment.tsx +207 -0
  87. package/src/shadcn/avatar.tsx +106 -0
  88. package/src/shadcn/badge.tsx +51 -0
  89. package/src/shadcn/breadcrumb.tsx +124 -0
  90. package/src/shadcn/bubble.tsx +127 -0
  91. package/src/shadcn/button-group.tsx +87 -0
  92. package/src/shadcn/button.tsx +57 -0
  93. package/src/shadcn/calendar.tsx +219 -0
  94. package/src/shadcn/card.tsx +102 -0
  95. package/src/shadcn/carousel.tsx +242 -0
  96. package/src/shadcn/chart.tsx +372 -0
  97. package/src/shadcn/checkbox.tsx +28 -0
  98. package/src/shadcn/collapsible.tsx +19 -0
  99. package/src/shadcn/combobox.tsx +297 -0
  100. package/src/shadcn/command.tsx +194 -0
  101. package/src/shadcn/context-menu.tsx +270 -0
  102. package/src/shadcn/dialog.tsx +158 -0
  103. package/src/shadcn/direction.tsx +4 -0
  104. package/src/shadcn/drawer.tsx +227 -0
  105. package/src/shadcn/dropdown-menu.tsx +265 -0
  106. package/src/shadcn/empty.tsx +103 -0
  107. package/src/shadcn/field.tsx +238 -0
  108. package/src/shadcn/form.tsx +173 -0
  109. package/src/shadcn/hover-card.tsx +50 -0
  110. package/src/shadcn/index.ts +53 -0
  111. package/src/shadcn/input-group.tsx +156 -0
  112. package/src/shadcn/input-otp.tsx +84 -0
  113. package/src/shadcn/input.tsx +19 -0
  114. package/src/shadcn/item.tsx +201 -0
  115. package/src/shadcn/kbd.tsx +26 -0
  116. package/src/shadcn/label.tsx +19 -0
  117. package/src/shadcn/marker.tsx +70 -0
  118. package/src/shadcn/menubar.tsx +278 -0
  119. package/src/shadcn/message-scroller.tsx +131 -0
  120. package/src/shadcn/message.tsx +91 -0
  121. package/src/shadcn/native-select.tsx +60 -0
  122. package/src/shadcn/navigation-menu.tsx +167 -0
  123. package/src/shadcn/pagination.tsx +130 -0
  124. package/src/shadcn/popover.tsx +87 -0
  125. package/src/shadcn/progress.tsx +82 -0
  126. package/src/shadcn/radio-group.tsx +35 -0
  127. package/src/shadcn/resizable.tsx +49 -0
  128. package/src/shadcn/scroll-area.tsx +52 -0
  129. package/src/shadcn/select.tsx +200 -0
  130. package/src/shadcn/separator.tsx +22 -0
  131. package/src/shadcn/sheet.tsx +136 -0
  132. package/src/shadcn/sidebar.tsx +721 -0
  133. package/src/shadcn/skeleton.tsx +13 -0
  134. package/src/shadcn/slider.tsx +51 -0
  135. package/src/shadcn/sonner.tsx +49 -0
  136. package/src/shadcn/spinner.tsx +10 -0
  137. package/src/shadcn/switch.tsx +29 -0
  138. package/src/shadcn/table.tsx +115 -0
  139. package/src/shadcn/tabs.tsx +79 -0
  140. package/src/shadcn/textarea.tsx +17 -0
  141. package/src/shadcn/toggle-group.tsx +89 -0
  142. package/src/shadcn/toggle.tsx +44 -0
  143. package/src/shadcn/tooltip.tsx +63 -0
  144. package/src/theme/config.ts +48 -0
  145. package/src/theme/css/base.css +92 -0
  146. package/src/theme/css/bitboy.css +75 -0
  147. package/src/theme/css/dark.css +47 -0
  148. package/src/theme/css/designer.css +122 -0
  149. package/src/theme/css/fonts.css +33 -0
  150. package/src/theme/css/globals.css +14 -0
  151. package/src/theme/css/hackintosh.css +57 -0
  152. package/src/theme/css/llama.css +103 -0
  153. package/src/theme/css/safelist.css +39 -0
  154. package/src/theme/css/shadcn.css +77 -0
  155. package/src/theme/css/theme.css +216 -0
  156. package/src/theme/embed-theme.ts +146 -0
  157. package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
  158. package/src/theme/fonts/Hack-Regular.woff2 +0 -0
  159. package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
  160. package/src/theme/fonts/LICENSE-Hack.txt +45 -0
  161. package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
  162. package/src/theme/fonts/Mona-Sans.woff2 +0 -0
  163. package/src/theme/index.ts +15 -0
  164. package/src/utils/cn.ts +3 -0
  165. 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 &quot;
80
+ <span className="font-medium opacity-80">accept</span>&quot;,
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
+ }