@nuasite/cms 0.1.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/README.md +237 -0
- package/dist/src/build-processor.d.ts +20 -0
- package/dist/src/build-processor.d.ts.map +1 -0
- package/dist/src/collection-scanner.d.ts +6 -0
- package/dist/src/collection-scanner.d.ts.map +1 -0
- package/dist/src/component-registry.d.ts +63 -0
- package/dist/src/component-registry.d.ts.map +1 -0
- package/dist/src/config.d.ts +24 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/dev-middleware.d.ts +20 -0
- package/dist/src/dev-middleware.d.ts.map +1 -0
- package/dist/src/editor/ai.d.ts +60 -0
- package/dist/src/editor/ai.d.ts.map +1 -0
- package/dist/src/editor/api.d.ts +140 -0
- package/dist/src/editor/api.d.ts.map +1 -0
- package/dist/src/editor/color-utils.d.ts +106 -0
- package/dist/src/editor/color-utils.d.ts.map +1 -0
- package/dist/src/editor/components/ai-chat.d.ts +11 -0
- package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
- package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
- package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
- package/dist/src/editor/components/attribute-editor.d.ts +5 -0
- package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
- package/dist/src/editor/components/block-editor.d.ts +12 -0
- package/dist/src/editor/components/block-editor.d.ts.map +1 -0
- package/dist/src/editor/components/collections-browser.d.ts +2 -0
- package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
- package/dist/src/editor/components/color-toolbar.d.ts +12 -0
- package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
- package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
- package/dist/src/editor/components/create-page-modal.d.ts +2 -0
- package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
- package/dist/src/editor/components/editable-highlights.d.ts +9 -0
- package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
- package/dist/src/editor/components/error-boundary.d.ts +32 -0
- package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
- package/dist/src/editor/components/fields.d.ts +75 -0
- package/dist/src/editor/components/fields.d.ts.map +1 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
- package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
- package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/image-overlay.d.ts +12 -0
- package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
- package/dist/src/editor/components/media-library.d.ts +2 -0
- package/dist/src/editor/components/media-library.d.ts.map +1 -0
- package/dist/src/editor/components/outline.d.ts +21 -0
- package/dist/src/editor/components/outline.d.ts.map +1 -0
- package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
- package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
- package/dist/src/editor/components/seo-editor.d.ts +2 -0
- package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
- package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast.d.ts +7 -0
- package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
- package/dist/src/editor/components/toast/types.d.ts +7 -0
- package/dist/src/editor/components/toast/types.d.ts.map +1 -0
- package/dist/src/editor/components/toolbar.d.ts +21 -0
- package/dist/src/editor/components/toolbar.d.ts.map +1 -0
- package/dist/src/editor/config.d.ts +4 -0
- package/dist/src/editor/config.d.ts.map +1 -0
- package/dist/src/editor/constants.d.ts +101 -0
- package/dist/src/editor/constants.d.ts.map +1 -0
- package/dist/src/editor/context.d.ts +14 -0
- package/dist/src/editor/context.d.ts.map +1 -0
- package/dist/src/editor/dom.d.ts +77 -0
- package/dist/src/editor/dom.d.ts.map +1 -0
- package/dist/src/editor/editor.d.ts +64 -0
- package/dist/src/editor/editor.d.ts.map +1 -0
- package/dist/src/editor/history.d.ts +20 -0
- package/dist/src/editor/history.d.ts.map +1 -0
- package/dist/src/editor/hooks/index.d.ts +14 -0
- package/dist/src/editor/hooks/index.d.ts.map +1 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
- package/dist/src/editor/hooks/utils.d.ts +32 -0
- package/dist/src/editor/hooks/utils.d.ts.map +1 -0
- package/dist/src/editor/index.d.ts +12 -0
- package/dist/src/editor/index.d.ts.map +1 -0
- package/dist/src/editor/lib/cn.d.ts +3 -0
- package/dist/src/editor/lib/cn.d.ts.map +1 -0
- package/dist/src/editor/manifest.d.ts +19 -0
- package/dist/src/editor/manifest.d.ts.map +1 -0
- package/dist/src/editor/markdown-api.d.ts +36 -0
- package/dist/src/editor/markdown-api.d.ts.map +1 -0
- package/dist/src/editor/signals.d.ts +242 -0
- package/dist/src/editor/signals.d.ts.map +1 -0
- package/dist/src/editor/storage.d.ts +27 -0
- package/dist/src/editor/storage.d.ts.map +1 -0
- package/dist/src/editor/text-styling.d.ts +350 -0
- package/dist/src/editor/text-styling.d.ts.map +1 -0
- package/dist/src/editor/themes.d.ts +38 -0
- package/dist/src/editor/themes.d.ts.map +1 -0
- package/dist/src/editor/types.d.ts +454 -0
- package/dist/src/editor/types.d.ts.map +1 -0
- package/dist/src/error-collector.d.ts +56 -0
- package/dist/src/error-collector.d.ts.map +1 -0
- package/dist/src/handlers/component-ops.d.ts +34 -0
- package/dist/src/handlers/component-ops.d.ts.map +1 -0
- package/dist/src/handlers/markdown-ops.d.ts +41 -0
- package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
- package/dist/src/handlers/request-utils.d.ts +20 -0
- package/dist/src/handlers/request-utils.d.ts.map +1 -0
- package/dist/src/handlers/source-writer.d.ts +51 -0
- package/dist/src/handlers/source-writer.d.ts.map +1 -0
- package/dist/src/html-processor.d.ts +63 -0
- package/dist/src/html-processor.d.ts.map +1 -0
- package/dist/src/index.d.ts +41 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/manifest-writer.d.ts +111 -0
- package/dist/src/manifest-writer.d.ts.map +1 -0
- package/dist/src/media/contember.d.ts +15 -0
- package/dist/src/media/contember.d.ts.map +1 -0
- package/dist/src/media/local.d.ts +9 -0
- package/dist/src/media/local.d.ts.map +1 -0
- package/dist/src/media/s3.d.ts +12 -0
- package/dist/src/media/s3.d.ts.map +1 -0
- package/dist/src/media/types.d.ts +40 -0
- package/dist/src/media/types.d.ts.map +1 -0
- package/dist/src/preview-generator.d.ts +19 -0
- package/dist/src/preview-generator.d.ts.map +1 -0
- package/dist/src/seo-processor.d.ts +23 -0
- package/dist/src/seo-processor.d.ts.map +1 -0
- package/dist/src/source-finder/ast-extractors.d.ts +35 -0
- package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
- package/dist/src/source-finder/ast-parser.d.ts +16 -0
- package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
- package/dist/src/source-finder/cache.d.ts +18 -0
- package/dist/src/source-finder/cache.d.ts.map +1 -0
- package/dist/src/source-finder/collection-finder.d.ts +29 -0
- package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
- package/dist/src/source-finder/element-finder.d.ts +42 -0
- package/dist/src/source-finder/element-finder.d.ts.map +1 -0
- package/dist/src/source-finder/image-finder.d.ts +24 -0
- package/dist/src/source-finder/image-finder.d.ts.map +1 -0
- package/dist/src/source-finder/index.d.ts +9 -0
- package/dist/src/source-finder/index.d.ts.map +1 -0
- package/dist/src/source-finder/search-index.d.ts +27 -0
- package/dist/src/source-finder/search-index.d.ts.map +1 -0
- package/dist/src/source-finder/snippet-utils.d.ts +90 -0
- package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
- package/dist/src/source-finder/source-lookup.d.ts +16 -0
- package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
- package/dist/src/source-finder/types.d.ts +167 -0
- package/dist/src/source-finder/types.d.ts.map +1 -0
- package/dist/src/source-finder/variable-extraction.d.ts +37 -0
- package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
- package/dist/src/tailwind-colors.d.ts +54 -0
- package/dist/src/tailwind-colors.d.ts.map +1 -0
- package/dist/src/tsconfig.tsbuildinfo +1 -0
- package/dist/src/types.d.ts +367 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +61 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/vite-plugin.d.ts +14 -0
- package/dist/src/vite-plugin.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +80 -0
- package/src/build-processor.ts +784 -0
- package/src/collection-scanner.ts +304 -0
- package/src/component-registry.ts +393 -0
- package/src/config.ts +74 -0
- package/src/dev-middleware.ts +525 -0
- package/src/dist/src/tsconfig.tsbuildinfo +1 -0
- package/src/editor/ai.ts +185 -0
- package/src/editor/api.ts +513 -0
- package/src/editor/color-utils.ts +556 -0
- package/src/editor/components/ai-chat.tsx +632 -0
- package/src/editor/components/ai-tooltip.tsx +179 -0
- package/src/editor/components/attribute-editor.tsx +596 -0
- package/src/editor/components/block-editor.tsx +546 -0
- package/src/editor/components/collections-browser.tsx +248 -0
- package/src/editor/components/color-toolbar.tsx +314 -0
- package/src/editor/components/confirm-dialog.tsx +69 -0
- package/src/editor/components/create-page-modal.tsx +163 -0
- package/src/editor/components/editable-highlights.tsx +260 -0
- package/src/editor/components/error-boundary.tsx +87 -0
- package/src/editor/components/fields.tsx +387 -0
- package/src/editor/components/frontmatter-fields.tsx +469 -0
- package/src/editor/components/highlight-overlay.ts +229 -0
- package/src/editor/components/image-overlay.tsx +230 -0
- package/src/editor/components/markdown-editor-overlay.tsx +505 -0
- package/src/editor/components/markdown-inline-editor.tsx +780 -0
- package/src/editor/components/media-library.tsx +297 -0
- package/src/editor/components/outline.tsx +402 -0
- package/src/editor/components/redirect-countdown.tsx +45 -0
- package/src/editor/components/seo-editor.tsx +498 -0
- package/src/editor/components/text-style-toolbar.tsx +362 -0
- package/src/editor/components/toast/toast-container.tsx +15 -0
- package/src/editor/components/toast/toast.tsx +49 -0
- package/src/editor/components/toast/types.ts +7 -0
- package/src/editor/components/toolbar.tsx +366 -0
- package/src/editor/config.ts +12 -0
- package/src/editor/constants.ts +106 -0
- package/src/editor/context.tsx +38 -0
- package/src/editor/dom.ts +357 -0
- package/src/editor/editor.ts +1510 -0
- package/src/editor/env.d.ts +4 -0
- package/src/editor/history.ts +355 -0
- package/src/editor/hooks/index.ts +19 -0
- package/src/editor/hooks/useAIHandlers.ts +345 -0
- package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
- package/src/editor/hooks/useElementDetection.ts +284 -0
- package/src/editor/hooks/useImageHoverDetection.ts +102 -0
- package/src/editor/hooks/useTextSelection.ts +187 -0
- package/src/editor/hooks/useTooltipState.ts +126 -0
- package/src/editor/hooks/utils.ts +101 -0
- package/src/editor/index.tsx +481 -0
- package/src/editor/lib/cn.ts +4 -0
- package/src/editor/manifest.ts +25 -0
- package/src/editor/markdown-api.ts +209 -0
- package/src/editor/signals.ts +1351 -0
- package/src/editor/storage.ts +266 -0
- package/src/editor/styles.css +465 -0
- package/src/editor/text-styling.ts +773 -0
- package/src/editor/themes.ts +210 -0
- package/src/editor/types.ts +591 -0
- package/src/error-collector.ts +106 -0
- package/src/handlers/component-ops.ts +463 -0
- package/src/handlers/markdown-ops.ts +202 -0
- package/src/handlers/request-utils.ts +151 -0
- package/src/handlers/source-writer.ts +649 -0
- package/src/html-processor.ts +1108 -0
- package/src/index.ts +284 -0
- package/src/manifest-writer.ts +371 -0
- package/src/media/contember.ts +84 -0
- package/src/media/local.ts +114 -0
- package/src/media/s3.ts +133 -0
- package/src/media/types.ts +33 -0
- package/src/preview-generator.ts +293 -0
- package/src/seo-processor.ts +567 -0
- package/src/source-finder/ast-extractors.ts +185 -0
- package/src/source-finder/ast-parser.ts +150 -0
- package/src/source-finder/cache.ts +76 -0
- package/src/source-finder/collection-finder.ts +335 -0
- package/src/source-finder/cross-file-tracker.ts +741 -0
- package/src/source-finder/element-finder.ts +387 -0
- package/src/source-finder/image-finder.ts +283 -0
- package/src/source-finder/index.ts +37 -0
- package/src/source-finder/search-index.ts +525 -0
- package/src/source-finder/snippet-utils.ts +668 -0
- package/src/source-finder/source-lookup.ts +200 -0
- package/src/source-finder/types.ts +210 -0
- package/src/source-finder/variable-extraction.ts +406 -0
- package/src/tailwind-colors.ts +874 -0
- package/src/tsconfig.json +25 -0
- package/src/types.ts +406 -0
- package/src/utils.ts +186 -0
- package/src/vite-plugin.ts +42 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { marked } from 'marked'
|
|
2
|
+
import { useEffect, useRef, useState } from 'preact/hooks'
|
|
3
|
+
import { CSS } from '../constants'
|
|
4
|
+
import { getComponentInstance } from '../manifest'
|
|
5
|
+
import * as signals from '../signals'
|
|
6
|
+
|
|
7
|
+
// Configure marked for safe HTML output
|
|
8
|
+
marked.setOptions({
|
|
9
|
+
breaks: true, // Convert \n to <br>
|
|
10
|
+
gfm: true, // GitHub Flavored Markdown
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sanitize HTML output to prevent XSS from AI-generated content.
|
|
15
|
+
* Strips dangerous tags and event handler attributes.
|
|
16
|
+
*/
|
|
17
|
+
function sanitizeHtml(html: string): string {
|
|
18
|
+
const doc = new DOMParser().parseFromString(html, 'text/html')
|
|
19
|
+
for (const el of doc.querySelectorAll('script,style,iframe,object,embed,form')) {
|
|
20
|
+
el.remove()
|
|
21
|
+
}
|
|
22
|
+
for (const el of doc.querySelectorAll('*')) {
|
|
23
|
+
for (const attr of Array.from(el.attributes)) {
|
|
24
|
+
if (attr.name.startsWith('on') || attr.name === 'srcdoc') {
|
|
25
|
+
el.removeAttribute(attr.name)
|
|
26
|
+
}
|
|
27
|
+
if (
|
|
28
|
+
['href', 'src', 'action'].includes(attr.name)
|
|
29
|
+
&& attr.value.trim().toLowerCase().startsWith('javascript:')
|
|
30
|
+
) {
|
|
31
|
+
el.removeAttribute(attr.name)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return doc.body.innerHTML
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Escape HTML entities for safe rendering as text
|
|
40
|
+
*/
|
|
41
|
+
function escapeHtml(text: string): string {
|
|
42
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Render markdown to sanitized HTML
|
|
47
|
+
*/
|
|
48
|
+
function renderMarkdown(content: string): string {
|
|
49
|
+
try {
|
|
50
|
+
const html = marked.parse(content, { async: false }) as string
|
|
51
|
+
return sanitizeHtml(html)
|
|
52
|
+
} catch {
|
|
53
|
+
return escapeHtml(content)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface AIChatCallbacks {
|
|
58
|
+
onSend: (message: string, elementId?: string) => void
|
|
59
|
+
onClose: () => void
|
|
60
|
+
onCancel: () => void
|
|
61
|
+
onApplyToElement: (content: string, elementId: string) => void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface AIChatProps {
|
|
65
|
+
callbacks: AIChatCallbacks
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get a friendly label for the context element
|
|
70
|
+
*/
|
|
71
|
+
function getContextLabel(elementId: string): string {
|
|
72
|
+
const manifest = signals.manifest.value
|
|
73
|
+
const instance = getComponentInstance(manifest, elementId)
|
|
74
|
+
if (instance) {
|
|
75
|
+
return instance.componentName
|
|
76
|
+
}
|
|
77
|
+
return elementId
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const AIChat = ({ callbacks }: AIChatProps) => {
|
|
81
|
+
const [message, setMessage] = useState('')
|
|
82
|
+
const [appliedMessages, setAppliedMessages] = useState<Set<string>>(
|
|
83
|
+
new Set(),
|
|
84
|
+
)
|
|
85
|
+
const [position, setPosition] = useState<'left' | 'right'>('right')
|
|
86
|
+
const [isMinimized, setIsMinimized] = useState(false)
|
|
87
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
88
|
+
const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null)
|
|
89
|
+
const dragOffsetRef = useRef({ x: 0, y: 0 })
|
|
90
|
+
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
91
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
|
92
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
93
|
+
|
|
94
|
+
const open = signals.isChatOpen.value
|
|
95
|
+
const messages = signals.chatMessages.value
|
|
96
|
+
const contextElementId = signals.chatContextElementId.value
|
|
97
|
+
const inputDisabled = signals.isAIProcessing.value
|
|
98
|
+
const currentStatus = signals.currentStatus.value
|
|
99
|
+
const statusMessage = signals.statusMessage.value
|
|
100
|
+
|
|
101
|
+
// Reset applied messages when chat opens (history may have changed)
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (open) {
|
|
104
|
+
setAppliedMessages(new Set())
|
|
105
|
+
}
|
|
106
|
+
}, [open])
|
|
107
|
+
|
|
108
|
+
// Handle drag start
|
|
109
|
+
const handleDragStart = (e: MouseEvent) => {
|
|
110
|
+
if (!containerRef.current) return
|
|
111
|
+
const rect = containerRef.current.getBoundingClientRect()
|
|
112
|
+
dragOffsetRef.current = {
|
|
113
|
+
x: e.clientX - rect.left,
|
|
114
|
+
y: e.clientY - rect.top,
|
|
115
|
+
}
|
|
116
|
+
setDragPosition({ x: rect.left, y: rect.top })
|
|
117
|
+
setIsDragging(true)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Handle drag move and end
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!isDragging) return
|
|
123
|
+
|
|
124
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
125
|
+
setDragPosition({
|
|
126
|
+
x: e.clientX - dragOffsetRef.current.x,
|
|
127
|
+
y: e.clientY - dragOffsetRef.current.y,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const handleMouseUp = (e: MouseEvent) => {
|
|
132
|
+
setIsDragging(false)
|
|
133
|
+
// Snap to left or right based on position
|
|
134
|
+
const windowCenter = window.innerWidth / 2
|
|
135
|
+
const currentX = e.clientX - dragOffsetRef.current.x
|
|
136
|
+
const containerWidth = containerRef.current?.offsetWidth || 400
|
|
137
|
+
const containerCenter = currentX + containerWidth / 2
|
|
138
|
+
|
|
139
|
+
if (containerCenter < windowCenter) {
|
|
140
|
+
setPosition('left')
|
|
141
|
+
} else {
|
|
142
|
+
setPosition('right')
|
|
143
|
+
}
|
|
144
|
+
setDragPosition(null)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
document.addEventListener('mousemove', handleMouseMove)
|
|
148
|
+
document.addEventListener('mouseup', handleMouseUp)
|
|
149
|
+
|
|
150
|
+
return () => {
|
|
151
|
+
document.removeEventListener('mousemove', handleMouseMove)
|
|
152
|
+
document.removeEventListener('mouseup', handleMouseUp)
|
|
153
|
+
}
|
|
154
|
+
}, [isDragging])
|
|
155
|
+
|
|
156
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: need to scroll to the bottom when messages change
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (messagesEndRef.current) {
|
|
159
|
+
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' })
|
|
160
|
+
}
|
|
161
|
+
}, [messages, currentStatus])
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (open && textareaRef.current && !inputDisabled) {
|
|
165
|
+
setTimeout(() => textareaRef.current?.focus(), 50)
|
|
166
|
+
}
|
|
167
|
+
}, [open, inputDisabled])
|
|
168
|
+
|
|
169
|
+
// Highlight the selected context element on the page
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (!open || !contextElementId) return
|
|
172
|
+
|
|
173
|
+
const el = document.querySelector(`[${CSS.COMPONENT_ID_ATTRIBUTE}="${contextElementId}"]`)
|
|
174
|
+
if (!el) return
|
|
175
|
+
|
|
176
|
+
const htmlEl = el as HTMLElement
|
|
177
|
+
const prev = htmlEl.style.outline
|
|
178
|
+
const prevOffset = htmlEl.style.outlineOffset
|
|
179
|
+
htmlEl.style.outline = '2px solid rgba(99, 102, 241, 0.7)'
|
|
180
|
+
htmlEl.style.outlineOffset = '2px'
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
htmlEl.style.outline = prev
|
|
184
|
+
htmlEl.style.outlineOffset = prevOffset
|
|
185
|
+
}
|
|
186
|
+
}, [open, contextElementId])
|
|
187
|
+
|
|
188
|
+
const contextLabel = contextElementId ? getContextLabel(contextElementId) : null
|
|
189
|
+
|
|
190
|
+
const handleSubmit = (e: Event) => {
|
|
191
|
+
e.preventDefault()
|
|
192
|
+
if (message.trim() && !inputDisabled) {
|
|
193
|
+
callbacks.onSend(message.trim(), contextElementId || undefined)
|
|
194
|
+
setMessage('')
|
|
195
|
+
if (textareaRef.current) {
|
|
196
|
+
textareaRef.current.style.height = 'auto'
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const handleTextareaInput = (e: Event) => {
|
|
202
|
+
const target = e.target as HTMLTextAreaElement
|
|
203
|
+
setMessage(target.value)
|
|
204
|
+
target.style.height = 'auto'
|
|
205
|
+
target.style.height = `${Math.min(target.scrollHeight, 120)}px`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const handleTextareaKeyDown = (e: KeyboardEvent) => {
|
|
209
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
210
|
+
e.preventDefault()
|
|
211
|
+
handleSubmit(e)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const handleApply = (
|
|
216
|
+
messageId: string,
|
|
217
|
+
content: string,
|
|
218
|
+
elementId: string,
|
|
219
|
+
) => {
|
|
220
|
+
callbacks.onApplyToElement(content, elementId)
|
|
221
|
+
setAppliedMessages(new Set(appliedMessages).add(messageId))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!open) {
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const stopPropagation = (e: Event) => e.stopPropagation()
|
|
229
|
+
|
|
230
|
+
const containerStyle = dragPosition
|
|
231
|
+
? {
|
|
232
|
+
left: `${dragPosition.x}px`,
|
|
233
|
+
top: `${dragPosition.y}px`,
|
|
234
|
+
right: 'auto',
|
|
235
|
+
bottom: 'auto',
|
|
236
|
+
height: isMinimized ? 'auto' : 'calc(100vh - 40px)',
|
|
237
|
+
}
|
|
238
|
+
: undefined
|
|
239
|
+
|
|
240
|
+
const positionClass = position === 'left' ? 'left-5' : 'right-5'
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<div
|
|
244
|
+
ref={containerRef}
|
|
245
|
+
class={`fixed ${dragPosition ? '' : positionClass} top-5 ${
|
|
246
|
+
isMinimized ? '' : 'bottom-5'
|
|
247
|
+
} w-100 max-w-[calc(100vw-40px)] bg-cms-dark shadow-[0_8px_32px_rgba(0,0,0,0.4)] rounded-cms-xl border border-white/10 z-2147483645 flex flex-col font-sans overflow-hidden ${
|
|
248
|
+
isDragging ? '' : 'transition-all duration-300'
|
|
249
|
+
}`}
|
|
250
|
+
style={containerStyle}
|
|
251
|
+
data-cms-ui
|
|
252
|
+
onMouseDown={stopPropagation}
|
|
253
|
+
onClick={stopPropagation}
|
|
254
|
+
>
|
|
255
|
+
<div
|
|
256
|
+
class={`px-5 py-4 flex items-center justify-between border-b border-white/10 ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`}
|
|
257
|
+
onMouseDown={handleDragStart}
|
|
258
|
+
>
|
|
259
|
+
<div class="flex items-center gap-2.5">
|
|
260
|
+
<div class="flex items-center text-white">
|
|
261
|
+
<svg
|
|
262
|
+
width="20"
|
|
263
|
+
height="20"
|
|
264
|
+
viewBox="0 0 24 24"
|
|
265
|
+
fill="none"
|
|
266
|
+
stroke="currentColor"
|
|
267
|
+
stroke-width="2.5"
|
|
268
|
+
stroke-linecap="round"
|
|
269
|
+
stroke-linejoin="round"
|
|
270
|
+
>
|
|
271
|
+
<path d="M12 3l1.912 5.813a2 2 0 0 0 1.275 1.275L21 12l-5.813 1.912a2 2 0 0 0-1.275 1.275L12 21l-1.912-5.813a2 2 0 0 0-1.275-1.275L3 12l5.813-1.912a2 2 0 0 0 1.275-1.275L12 3z" />
|
|
272
|
+
</svg>
|
|
273
|
+
</div>
|
|
274
|
+
<h3 class="m-0 text-base font-semibold text-white">
|
|
275
|
+
AI Assistant
|
|
276
|
+
</h3>
|
|
277
|
+
</div>
|
|
278
|
+
<div class="flex items-center gap-1">
|
|
279
|
+
<button
|
|
280
|
+
onClick={(e) => {
|
|
281
|
+
e.stopPropagation()
|
|
282
|
+
setIsMinimized(!isMinimized)
|
|
283
|
+
}}
|
|
284
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
285
|
+
class="bg-white/10 border-none text-white/80 text-sm cursor-pointer p-1.5 leading-none transition-all w-8 h-8 flex items-center justify-center hover:bg-white/20 hover:text-white rounded-full"
|
|
286
|
+
title={isMinimized ? 'Expand' : 'Minimize'}
|
|
287
|
+
>
|
|
288
|
+
{isMinimized
|
|
289
|
+
? (
|
|
290
|
+
<svg
|
|
291
|
+
width="14"
|
|
292
|
+
height="14"
|
|
293
|
+
viewBox="0 0 24 24"
|
|
294
|
+
fill="none"
|
|
295
|
+
stroke="currentColor"
|
|
296
|
+
stroke-width="2.5"
|
|
297
|
+
stroke-linecap="round"
|
|
298
|
+
stroke-linejoin="round"
|
|
299
|
+
>
|
|
300
|
+
<polyline points="15 3 21 3 21 9" />
|
|
301
|
+
<polyline points="9 21 3 21 3 15" />
|
|
302
|
+
<line x1="21" y1="3" x2="14" y2="10" />
|
|
303
|
+
<line x1="3" y1="21" x2="10" y2="14" />
|
|
304
|
+
</svg>
|
|
305
|
+
)
|
|
306
|
+
: (
|
|
307
|
+
<svg
|
|
308
|
+
width="14"
|
|
309
|
+
height="14"
|
|
310
|
+
viewBox="0 0 24 24"
|
|
311
|
+
fill="none"
|
|
312
|
+
stroke="currentColor"
|
|
313
|
+
stroke-width="2.5"
|
|
314
|
+
stroke-linecap="round"
|
|
315
|
+
stroke-linejoin="round"
|
|
316
|
+
>
|
|
317
|
+
<polyline points="4 14 10 14 10 20" />
|
|
318
|
+
<polyline points="20 10 14 10 14 4" />
|
|
319
|
+
<line x1="14" y1="10" x2="21" y2="3" />
|
|
320
|
+
<line x1="3" y1="21" x2="10" y2="14" />
|
|
321
|
+
</svg>
|
|
322
|
+
)}
|
|
323
|
+
</button>
|
|
324
|
+
<button
|
|
325
|
+
onClick={(e) => {
|
|
326
|
+
e.stopPropagation()
|
|
327
|
+
callbacks.onClose()
|
|
328
|
+
}}
|
|
329
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
330
|
+
class="bg-white/10 border-none text-white/80 text-xl cursor-pointer p-1.5 leading-none transition-all w-8 h-8 flex items-center justify-center hover:bg-white/20 hover:text-white rounded-full"
|
|
331
|
+
>
|
|
332
|
+
×
|
|
333
|
+
</button>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
{!isMinimized && (
|
|
338
|
+
<>
|
|
339
|
+
<div class="flex-1 overflow-y-auto p-4 flex flex-col gap-4 bg-black/20">
|
|
340
|
+
{messages.length === 0
|
|
341
|
+
? (
|
|
342
|
+
<div class="flex flex-col items-center justify-center h-full text-white text-center p-10">
|
|
343
|
+
<svg
|
|
344
|
+
width="48"
|
|
345
|
+
height="48"
|
|
346
|
+
viewBox="0 0 24 24"
|
|
347
|
+
fill="none"
|
|
348
|
+
stroke="currentColor"
|
|
349
|
+
stroke-width="2"
|
|
350
|
+
stroke-linecap="round"
|
|
351
|
+
stroke-linejoin="round"
|
|
352
|
+
class="mb-4 text-white/30"
|
|
353
|
+
>
|
|
354
|
+
<path d="M12 3l1.912 5.813a2 2 0 0 0 1.275 1.275L21 12l-5.813 1.912a2 2 0 0 0-1.275 1.275L12 21l-1.912-5.813a2 2 0 0 0-1.275-1.275L3 12l5.813-1.912a2 2 0 0 0 1.275-1.275L12 3z" />
|
|
355
|
+
</svg>
|
|
356
|
+
<div class="text-sm font-semibold mb-2 text-white">
|
|
357
|
+
Start a conversation
|
|
358
|
+
</div>
|
|
359
|
+
<div class="text-xs text-white/50">
|
|
360
|
+
Ask AI to help you edit content
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
)
|
|
364
|
+
: (
|
|
365
|
+
messages.map((msg) => (
|
|
366
|
+
<div
|
|
367
|
+
key={msg.id}
|
|
368
|
+
class="flex flex-col gap-1.5 animate-[slideIn_0.2s_ease]"
|
|
369
|
+
>
|
|
370
|
+
{msg.role === 'assistant' && !msg.content.trim()
|
|
371
|
+
? (
|
|
372
|
+
<div class="px-4 py-3 text-[13px] leading-relaxed max-w-[85%] bg-white/10 text-white/50 self-start rounded-cms-lg rounded-bl-cms-sm border border-white/10 flex items-center gap-1.5">
|
|
373
|
+
<span class="inline-block w-1.5 h-1.5 bg-white/40 rounded-full animate-bounce [animation-delay:0ms]" />
|
|
374
|
+
<span class="inline-block w-1.5 h-1.5 bg-white/40 rounded-full animate-bounce [animation-delay:150ms]" />
|
|
375
|
+
<span class="inline-block w-1.5 h-1.5 bg-white/40 rounded-full animate-bounce [animation-delay:300ms]" />
|
|
376
|
+
</div>
|
|
377
|
+
)
|
|
378
|
+
: (
|
|
379
|
+
<div
|
|
380
|
+
class={`px-4 py-3 text-[13px] leading-relaxed wrap-break-word max-w-[85%] ${
|
|
381
|
+
msg.role === 'user'
|
|
382
|
+
? 'bg-cms-primary text-cms-primary-text self-end rounded-cms-lg rounded-br-cms-sm'
|
|
383
|
+
: 'bg-white/10 text-white self-start rounded-cms-lg rounded-bl-cms-sm cms-markdown border border-white/10'
|
|
384
|
+
}`}
|
|
385
|
+
|
|
386
|
+
dangerouslySetInnerHTML={msg.role === 'assistant'
|
|
387
|
+
? { __html: renderMarkdown(msg.content) }
|
|
388
|
+
: undefined}
|
|
389
|
+
>
|
|
390
|
+
{msg.role === 'user' ? msg.content : undefined}
|
|
391
|
+
</div>
|
|
392
|
+
)}
|
|
393
|
+
{msg.elementId && (
|
|
394
|
+
<div
|
|
395
|
+
class={`text-[10px] text-white/40 font-mono px-1 ${msg.role === 'user' ? 'self-end' : 'self-start'}`}
|
|
396
|
+
>
|
|
397
|
+
{msg.elementId}
|
|
398
|
+
</div>
|
|
399
|
+
)}
|
|
400
|
+
{
|
|
401
|
+
/* TODO: Re-enable when we can apply partial content instead of whole message
|
|
402
|
+
{msg.role === 'assistant' && msg.elementId && (
|
|
403
|
+
<button
|
|
404
|
+
onClick={() => handleApply(msg.id, msg.content, msg.elementId!)}
|
|
405
|
+
disabled={appliedMessages.has(msg.id)}
|
|
406
|
+
class={`px-3 py-1.5 text-[11px] font-medium cursor-pointer self-start transition-all mt-1 rounded-cms-pill ${
|
|
407
|
+
appliedMessages.has(msg.id)
|
|
408
|
+
? 'bg-white/10 text-white/50 cursor-not-allowed'
|
|
409
|
+
: 'bg-cms-primary text-cms-primary-text hover:bg-cms-primary-hover'
|
|
410
|
+
}`}
|
|
411
|
+
>
|
|
412
|
+
{appliedMessages.has(msg.id)
|
|
413
|
+
? '✓ Applied'
|
|
414
|
+
: 'Apply to element'}
|
|
415
|
+
</button>
|
|
416
|
+
)}
|
|
417
|
+
*/
|
|
418
|
+
}
|
|
419
|
+
</div>
|
|
420
|
+
))
|
|
421
|
+
)}
|
|
422
|
+
|
|
423
|
+
{/* Status indicator */}
|
|
424
|
+
{currentStatus && (
|
|
425
|
+
<div class="flex items-center gap-2 px-3 py-1.5 text-[11px] text-white/40 self-start animate-[slideIn_0.2s_ease]">
|
|
426
|
+
<StatusIcon status={currentStatus} />
|
|
427
|
+
<span>
|
|
428
|
+
{statusMessage || getDefaultStatusMessage(currentStatus)}
|
|
429
|
+
</span>
|
|
430
|
+
</div>
|
|
431
|
+
)}
|
|
432
|
+
|
|
433
|
+
<div ref={messagesEndRef} />
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<div class="p-4 border-t border-white/10 bg-cms-dark rounded-b-cms-xl">
|
|
437
|
+
{contextElementId && contextLabel
|
|
438
|
+
? (
|
|
439
|
+
<div class="px-3 py-2 bg-white/10 rounded-cms-md mb-3 text-[11px] text-white/60 relative">
|
|
440
|
+
<button
|
|
441
|
+
onClick={() => signals.setChatContextElement(null)}
|
|
442
|
+
class="absolute top-2 right-2 bg-none border-none text-white/50 cursor-pointer p-0 text-sm leading-none hover:text-white"
|
|
443
|
+
>
|
|
444
|
+
×
|
|
445
|
+
</button>
|
|
446
|
+
<div class="font-medium mb-0.5">Editing:</div>
|
|
447
|
+
<div class="text-white font-medium">{contextLabel}</div>
|
|
448
|
+
</div>
|
|
449
|
+
)
|
|
450
|
+
: (
|
|
451
|
+
<div class="px-3 py-2 rounded-cms-md mb-3 text-[11px] text-white/30">
|
|
452
|
+
Click on section on the page to focus the conversation
|
|
453
|
+
</div>
|
|
454
|
+
)}
|
|
455
|
+
<form onSubmit={handleSubmit} class="flex gap-2">
|
|
456
|
+
<textarea
|
|
457
|
+
ref={textareaRef}
|
|
458
|
+
placeholder="Ask AI anything..."
|
|
459
|
+
rows={1}
|
|
460
|
+
value={message}
|
|
461
|
+
onInput={handleTextareaInput}
|
|
462
|
+
onKeyDown={handleTextareaKeyDown}
|
|
463
|
+
disabled={inputDisabled}
|
|
464
|
+
class={`flex-1 px-4 py-3 border border-white/20 text-[13px] font-sans resize-none max-h-30 transition-all outline-none focus:border-white/40 focus:ring-1 focus:ring-white/10 rounded-cms-lg placeholder:text-white/40 ${
|
|
465
|
+
inputDisabled
|
|
466
|
+
? 'bg-white/5 text-white/50 opacity-60'
|
|
467
|
+
: 'bg-white/10 text-white'
|
|
468
|
+
}`}
|
|
469
|
+
/>
|
|
470
|
+
{inputDisabled
|
|
471
|
+
? (
|
|
472
|
+
<button
|
|
473
|
+
type="button"
|
|
474
|
+
onClick={() => callbacks.onCancel()}
|
|
475
|
+
class="px-4 cursor-pointer transition-all flex items-center justify-center rounded-cms-lg bg-red-500/20 text-red-300 hover:bg-red-500/30 hover:text-red-200 border border-red-500/30"
|
|
476
|
+
title="Cancel request"
|
|
477
|
+
>
|
|
478
|
+
<svg
|
|
479
|
+
width="16"
|
|
480
|
+
height="16"
|
|
481
|
+
viewBox="0 0 24 24"
|
|
482
|
+
fill="none"
|
|
483
|
+
stroke="currentColor"
|
|
484
|
+
stroke-width="2.5"
|
|
485
|
+
stroke-linecap="round"
|
|
486
|
+
stroke-linejoin="round"
|
|
487
|
+
>
|
|
488
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
|
489
|
+
</svg>
|
|
490
|
+
</button>
|
|
491
|
+
)
|
|
492
|
+
: (
|
|
493
|
+
<button
|
|
494
|
+
type="submit"
|
|
495
|
+
class="px-4 cursor-pointer transition-all flex items-center justify-center rounded-cms-lg bg-cms-primary text-cms-primary-text hover:bg-cms-primary-hover"
|
|
496
|
+
>
|
|
497
|
+
<svg
|
|
498
|
+
width="16"
|
|
499
|
+
height="16"
|
|
500
|
+
viewBox="0 0 24 24"
|
|
501
|
+
fill="none"
|
|
502
|
+
stroke="currentColor"
|
|
503
|
+
stroke-width="2.5"
|
|
504
|
+
stroke-linecap="round"
|
|
505
|
+
stroke-linejoin="round"
|
|
506
|
+
>
|
|
507
|
+
<line x1="22" y1="2" x2="11" y2="13"></line>
|
|
508
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
509
|
+
</svg>
|
|
510
|
+
</button>
|
|
511
|
+
)}
|
|
512
|
+
</form>
|
|
513
|
+
</div>
|
|
514
|
+
</>
|
|
515
|
+
)}
|
|
516
|
+
</div>
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Get default status message for a status type
|
|
522
|
+
*/
|
|
523
|
+
function getDefaultStatusMessage(status: string): string {
|
|
524
|
+
switch (status) {
|
|
525
|
+
case 'thinking':
|
|
526
|
+
return 'Thinking...'
|
|
527
|
+
case 'coding':
|
|
528
|
+
return 'Writing code...'
|
|
529
|
+
case 'building':
|
|
530
|
+
return 'Building preview...'
|
|
531
|
+
case 'deploying':
|
|
532
|
+
return 'Deploying...'
|
|
533
|
+
case 'complete':
|
|
534
|
+
return 'Done!'
|
|
535
|
+
default:
|
|
536
|
+
return 'Processing...'
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Status indicator icon component
|
|
542
|
+
*/
|
|
543
|
+
function StatusIcon({ status }: { status: string }) {
|
|
544
|
+
switch (status) {
|
|
545
|
+
case 'thinking':
|
|
546
|
+
return (
|
|
547
|
+
<svg
|
|
548
|
+
width="16"
|
|
549
|
+
height="16"
|
|
550
|
+
viewBox="0 0 24 24"
|
|
551
|
+
fill="none"
|
|
552
|
+
stroke="currentColor"
|
|
553
|
+
stroke-width="2"
|
|
554
|
+
class="animate-pulse text-purple-600"
|
|
555
|
+
>
|
|
556
|
+
<circle cx="12" cy="12" r="10" />
|
|
557
|
+
<path d="M12 16v-4m0-4h.01" />
|
|
558
|
+
</svg>
|
|
559
|
+
)
|
|
560
|
+
case 'coding':
|
|
561
|
+
return (
|
|
562
|
+
<svg
|
|
563
|
+
width="16"
|
|
564
|
+
height="16"
|
|
565
|
+
viewBox="0 0 24 24"
|
|
566
|
+
fill="none"
|
|
567
|
+
stroke="currentColor"
|
|
568
|
+
stroke-width="2"
|
|
569
|
+
class="text-blue-600"
|
|
570
|
+
>
|
|
571
|
+
<polyline points="16 18 22 12 16 6" />
|
|
572
|
+
<polyline points="8 6 2 12 8 18" />
|
|
573
|
+
</svg>
|
|
574
|
+
)
|
|
575
|
+
case 'building':
|
|
576
|
+
return (
|
|
577
|
+
<svg
|
|
578
|
+
width="16"
|
|
579
|
+
height="16"
|
|
580
|
+
viewBox="0 0 24 24"
|
|
581
|
+
fill="none"
|
|
582
|
+
stroke="currentColor"
|
|
583
|
+
stroke-width="2"
|
|
584
|
+
class="animate-spin text-orange-600"
|
|
585
|
+
>
|
|
586
|
+
<path d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" />
|
|
587
|
+
</svg>
|
|
588
|
+
)
|
|
589
|
+
case 'deploying':
|
|
590
|
+
return (
|
|
591
|
+
<svg
|
|
592
|
+
width="16"
|
|
593
|
+
height="16"
|
|
594
|
+
viewBox="0 0 24 24"
|
|
595
|
+
fill="none"
|
|
596
|
+
stroke="currentColor"
|
|
597
|
+
stroke-width="2"
|
|
598
|
+
class="animate-bounce text-green-600"
|
|
599
|
+
>
|
|
600
|
+
<path d="M12 19V5m-7 7l7-7 7 7" />
|
|
601
|
+
</svg>
|
|
602
|
+
)
|
|
603
|
+
case 'complete':
|
|
604
|
+
return (
|
|
605
|
+
<svg
|
|
606
|
+
width="16"
|
|
607
|
+
height="16"
|
|
608
|
+
viewBox="0 0 24 24"
|
|
609
|
+
fill="none"
|
|
610
|
+
stroke="currentColor"
|
|
611
|
+
stroke-width="2"
|
|
612
|
+
class="text-green-600"
|
|
613
|
+
>
|
|
614
|
+
<path d="M20 6L9 17l-5-5" />
|
|
615
|
+
</svg>
|
|
616
|
+
)
|
|
617
|
+
default:
|
|
618
|
+
return (
|
|
619
|
+
<svg
|
|
620
|
+
width="16"
|
|
621
|
+
height="16"
|
|
622
|
+
viewBox="0 0 24 24"
|
|
623
|
+
fill="none"
|
|
624
|
+
stroke="currentColor"
|
|
625
|
+
stroke-width="2"
|
|
626
|
+
class="animate-spin text-slate-600"
|
|
627
|
+
>
|
|
628
|
+
<path d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" />
|
|
629
|
+
</svg>
|
|
630
|
+
)
|
|
631
|
+
}
|
|
632
|
+
}
|