File size: 3,843 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 | import { useImperativeHandle, useRef, useState } from 'react'
import type { ClipboardEvent as ReactClipboardEvent, Ref } from 'react'
import { useStudioCommandAutocomplete } from '../../commands/ui/autocomplete/use-studio-command-autocomplete'
import { debugStudioMessages } from '../../agent-response/debug'
import type { StudioSession } from '../../protocol/studio-agent-types'
import type { StudioCommandPanelHandle } from '../StudioCommandPanel'
import { extractImageFilesFromDataTransfer } from './image-transfer'
import { submitStudioCommandComposer } from './submit-studio-command-composer'
import { useStudioCommandComposerAttachmentsController } from './use-studio-command-composer-attachments-controller'
interface UseStudioCommandComposerControllerInput {
session: StudioSession | null
disabled: boolean
onRun: (inputText: string) => Promise<void> | void
composerRef: Ref<StudioCommandPanelHandle>
}
export function useStudioCommandComposerController({
session,
disabled,
onRun,
composerRef,
}: UseStudioCommandComposerControllerInput) {
const [input, setInput] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const commandAutocomplete = useStudioCommandAutocomplete(input, session)
const focusInput = () => {
if (disabled) {
return
}
inputRef.current?.focus()
}
const composerAttachments = useStudioCommandComposerAttachmentsController({
disabled,
focusInput,
setInput,
})
const applySuggestion = (nextInput: string) => {
setInput(nextInput)
inputRef.current?.focus()
}
const handlePaste = async (event: ReactClipboardEvent<HTMLInputElement>) => {
const imageFiles = extractImageFilesFromDataTransfer(event.clipboardData)
debugStudioMessages('composer-paste-detected', {
imageCount: imageFiles.length,
target: 'input',
})
if (imageFiles.length === 0) {
return
}
event.preventDefault()
await addImageFilesToComposer(imageFiles)
}
const handleDocumentPaste = async (event: ClipboardEvent) => {
const imageFiles = extractImageFilesFromDataTransfer(event.clipboardData)
debugStudioMessages('composer-paste-detected', {
imageCount: imageFiles.length,
target: 'document',
})
if (imageFiles.length === 0) {
return
}
await addImageFilesToComposer(imageFiles)
}
useImperativeHandle(composerRef, () => ({
ingestImageFiles: async (files) => {
await composerAttachments.addImageFilesToComposer(files)
},
appendPreviewAttachment: composerAttachments.appendPreviewAttachment,
focusComposer: focusInput,
}), [composerAttachments, disabled])
const addImageFilesToComposer = composerAttachments.addImageFilesToComposer
const handleSubmit = async () => {
await submitStudioCommandComposer({
input,
disabled,
session,
attachments: composerAttachments.attachmentsState.attachments,
onRun,
clearInput: () => setInput(''),
restoreInput: setInput,
clearAttachments: composerAttachments.attachmentsState.clearAttachments,
retainAttachments: composerAttachments.attachmentsState.retainAttachments,
focusInput: () => inputRef.current?.focus(),
openImageInputMode: composerAttachments.imageInputCommand.openImageInputMode,
})
}
return {
input,
inputRef,
attachments: composerAttachments.attachmentsState.attachments,
attachmentError: composerAttachments.attachmentsState.attachmentError,
commandAutocomplete,
imageInputCommand: composerAttachments.imageInputCommand,
effectiveApplySuggestion: applySuggestion,
focusInput,
handlePaste,
handleDocumentPaste,
handleInputChange: composerAttachments.handleInputChange,
handleRemoveAttachment: composerAttachments.handleRemoveAttachment,
handleSubmit,
}
}
|