Add image attachment functionality to App component
This commit is contained in:
78
src/App.jsx
78
src/App.jsx
@@ -362,6 +362,14 @@ export default function App() {
|
|||||||
throw new Error(detail || `HTTP ${response.status}`)
|
throw new Error(detail || `HTTP ${response.status}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchModelCapabilities(modelName, signal) {
|
||||||
|
const response = await fetch(
|
||||||
|
`${backendApiUrl}/models/capabilities?name=${encodeURIComponent(modelName)}`,
|
||||||
|
{ signal }
|
||||||
|
)
|
||||||
|
return expectBackendJson(response)
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchStartupOllamaStatus() {
|
async function fetchStartupOllamaStatus() {
|
||||||
const response = await fetch(`${backendApiUrl}/ollama/startup-status`)
|
const response = await fetch(`${backendApiUrl}/ollama/startup-status`)
|
||||||
return expectBackendJson(response)
|
return expectBackendJson(response)
|
||||||
@@ -391,6 +399,76 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function appendComposerImageFiles(fileList) {
|
||||||
|
const incoming = Array.from(fileList || []).filter(isImageFile)
|
||||||
|
if (!incoming.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!selectedModelSupportsVision) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingSlots = Math.max(0, MAX_IMAGE_ATTACHMENTS - composerAttachments.length)
|
||||||
|
if (remainingSlots <= 0) {
|
||||||
|
window.alert(`You can attach up to ${MAX_IMAGE_ATTACHMENTS} images per message.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = incoming.slice(0, remainingSlots)
|
||||||
|
const oversized = candidates.filter(file => Number(file.size) > MAX_IMAGE_ATTACHMENT_BYTES)
|
||||||
|
const acceptedFiles = candidates.filter(file => Number(file.size) <= MAX_IMAGE_ATTACHMENT_BYTES)
|
||||||
|
|
||||||
|
if (oversized.length > 0) {
|
||||||
|
window.alert(`Images must be ${Math.round(MAX_IMAGE_ATTACHMENT_BYTES / (1024 * 1024))} MB or smaller.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acceptedFiles.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nextAttachments = await Promise.all(
|
||||||
|
acceptedFiles.map(async (file, index) => ({
|
||||||
|
id: `attachment-${Date.now()}-${index}-${Math.random().toString(36).slice(2)}`,
|
||||||
|
name: file.name || 'image',
|
||||||
|
mime_type: file.type || 'image/*',
|
||||||
|
data_url: await readFileAsDataUrl(file),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
setComposerAttachments(prev => [...prev, ...nextAttachments])
|
||||||
|
|
||||||
|
if (incoming.length > remainingSlots) {
|
||||||
|
window.alert(`Only the first ${MAX_IMAGE_ATTACHMENTS} images can be attached.`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load image attachments', error)
|
||||||
|
window.alert(`Image import failed: ${getErrorText(error)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeComposerAttachment(attachmentId) {
|
||||||
|
setComposerAttachments(prev => prev.filter(attachment => attachment.id !== attachmentId))
|
||||||
|
}
|
||||||
|
|
||||||
|
function openImagePicker() {
|
||||||
|
if (!selectedModelSupportsVision) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imageInputRef.current?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleComposerImageSelection(event) {
|
||||||
|
const files = event.target?.files
|
||||||
|
try {
|
||||||
|
await appendComposerImageFiles(files)
|
||||||
|
} finally {
|
||||||
|
if (event.target) {
|
||||||
|
event.target.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function startEditMessage(index, content) {
|
function startEditMessage(index, content) {
|
||||||
setEditingMessageIndex(index);
|
setEditingMessageIndex(index);
|
||||||
setEditText(content || '');
|
setEditText(content || '');
|
||||||
|
|||||||
Reference in New Issue
Block a user