File size: 5,116 Bytes
abcf568 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | import { StudioComposerAttachmentList } from '../StudioComposerAttachmentList'
import { StudioCommandAutocomplete } from '../../commands/ui/autocomplete/StudioCommandAutocomplete'
import { StudioImageInputCommandUI } from '../../commands/ui/image-input/StudioImageInputCommandUI'
import type { useStudioCommandComposerController } from './use-studio-command-composer-controller'
interface StudioCommandComposerProps {
variant: 'default' | 't-layout-bottom' | 'pure-minimal-bottom'
isFrameless: boolean
isTLayout: boolean
isMinimal: boolean
isBusy: boolean
disabled: boolean
effectivePlaceholder: string
enterToSendLabel: string
onEscapePress?: () => void
composer: ReturnType<typeof useStudioCommandComposerController>
}
export function StudioCommandComposer({
variant,
isFrameless,
isTLayout,
isMinimal,
isBusy,
disabled,
effectivePlaceholder,
enterToSendLabel,
onEscapePress,
composer,
}: StudioCommandComposerProps) {
return (
<>
<footer className={`shrink-0 ${isTLayout ? 'border-t border-[#f2f2f2] px-5 py-4' : isMinimal ? 'mt-auto pl-4 pr-3 pt-4' : 'px-8 py-6'}`}>
<StudioComposerAttachmentList
attachments={composer.attachments}
disabled={isBusy}
onRemove={composer.handleRemoveAttachment}
variant={isMinimal ? 'minimal' : 'default'}
/>
{composer.attachmentError ? (
<p className="mb-3 mt-3 text-xs text-rose-500/80">{composer.attachmentError}</p>
) : null}
<div className={`${isMinimal ? 'flex items-baseline gap-4' : 'group flex items-center gap-3'}`}>
<span
className={`${isTLayout ? 'font-mono text-sm text-[#999]' : isMinimal ? 'block w-4 shrink-0 text-center text-[11px] font-semibold leading-loose text-accent' : 'font-mono text-sm text-text-secondary/40'} tracking-widest`}
>
{'>'}
</span>
<div className={`${isMinimal ? 'relative flex-1' : 'flex-1'}`}>
<StudioCommandAutocomplete
suggestions={composer.commandAutocomplete.suggestions}
activeIndex={Math.min(composer.commandAutocomplete.activeSuggestionIndex, Math.max(composer.commandAutocomplete.suggestions.length - 1, 0))}
onSelect={(suggestion) => composer.commandAutocomplete.applySuggestion(suggestion, composer.effectiveApplySuggestion)}
/>
<input
ref={composer.inputRef}
type="text"
value={composer.input}
onChange={(e) => composer.handleInputChange(e.target.value)}
onPaste={(e) => { void composer.handlePaste(e) }}
onKeyDown={(e) => {
const autocompleteResult = composer.commandAutocomplete.handleKeyDown(e, composer.effectiveApplySuggestion)
if (autocompleteResult.handled) {
return
}
if (e.key === 'Escape' && onEscapePress) {
e.preventDefault()
e.stopPropagation()
onEscapePress()
return
}
if (e.key === 'Enter') {
e.preventDefault()
void composer.handleSubmit()
}
}}
placeholder={isMinimal ? '' : effectivePlaceholder}
disabled={false}
aria-disabled={disabled}
className={`w-full bg-transparent outline-none ${isTLayout ? 'text-[14px] text-[#333] placeholder:text-[#ccc]' : isMinimal ? 'text-[13px] leading-loose text-accent' : 'text-[14px] font-medium leading-relaxed text-text-primary placeholder:text-text-secondary/25'}`}
/>
{isMinimal && (
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center">
{composer.input.length === 0 && (
<span className="text-[13px] leading-loose text-accent/20">
{effectivePlaceholder}({enterToSendLabel})
</span>
)}
</div>
)}
</div>
{!isFrameless && (
<div className="flex items-center gap-2 opacity-30">
<div className="font-mono text-[9px] uppercase tracking-widest text-text-secondary">{enterToSendLabel}</div>
</div>
)}
{isTLayout && (
<span className="text-[11px] text-[#ccc] shrink-0">{enterToSendLabel}</span>
)}
</div>
</footer>
<StudioImageInputCommandUI
isImageModeOpen={composer.imageInputCommand.isImageModeOpen}
isCanvasOpen={composer.imageInputCommand.isCanvasOpen}
onCloseImageMode={composer.imageInputCommand.closeImageInputMode}
onCloseCanvas={composer.imageInputCommand.closeCanvas}
onImportFiles={composer.imageInputCommand.handleImportFiles}
onStartImport={composer.imageInputCommand.startImport}
onStartDraw={composer.imageInputCommand.startDraw}
onCanvasComplete={composer.imageInputCommand.handleCanvasComplete}
/>
</>
)
}
|