|
<script lang="ts"> |
|
import type { GradioClient, CaptionParams, CaptionResult, CaptionType, CaptionLength } from '$lib/types'; |
|
|
|
interface Props { |
|
client: GradioClient | null; |
|
currentImage?: Blob | null; |
|
} |
|
|
|
let { client = null, currentImage = null }: Props = $props(); |
|
|
|
let imageInput: HTMLInputElement; |
|
let uploadedImage: File | null = $state(null); |
|
let captionType: CaptionType = $state("Descriptive"); |
|
let captionLength: CaptionLength = $state("long"); |
|
let isGenerating = $state(false); |
|
let result: CaptionResult | null = $state(null); |
|
let error: string | null = $state(null); |
|
|
|
const captionTypes: CaptionType[] = [ |
|
"Descriptive", |
|
"Descriptive (Informal)", |
|
"Training Prompt", |
|
"MidJourney", |
|
"Booru tag list", |
|
"Booru-like tag list", |
|
"Art Critic", |
|
"Product Listing", |
|
"Social Media Post" |
|
]; |
|
|
|
const captionLengths: CaptionLength[] = [ |
|
"any", |
|
"very short", |
|
"short", |
|
"medium-length", |
|
"long", |
|
"very long" |
|
]; |
|
|
|
function handleFileChange(e: Event) { |
|
const target = e.target as HTMLInputElement; |
|
if (target.files && target.files[0]) { |
|
uploadedImage = target.files[0]; |
|
} |
|
} |
|
|
|
async function handleSubmit(e: Event) { |
|
e.preventDefault(); |
|
|
|
const imageToCaption = uploadedImage || currentImage; |
|
|
|
if (!client || !imageToCaption) { |
|
error = "Please upload an image or generate one above."; |
|
return; |
|
} |
|
|
|
isGenerating = true; |
|
error = null; |
|
result = null; |
|
|
|
try { |
|
const output = await client.predict("/stream_chat", [ |
|
imageToCaption, |
|
captionType, |
|
captionLength, |
|
[], |
|
"", |
|
"" |
|
]); |
|
|
|
const [prompt, caption] = output.data; |
|
|
|
result = { |
|
caption, |
|
type: captionType, |
|
length: captionLength |
|
}; |
|
} catch (err) { |
|
console.error(err); |
|
error = `Caption generation failed: ${err}`; |
|
} finally { |
|
isGenerating = false; |
|
} |
|
} |
|
</script> |
|
|
|
<form class="caption-form" onsubmit={handleSubmit}> |
|
<h3>Caption Image</h3> |
|
|
|
<label for="imageInput"> |
|
Upload Image {currentImage ? 'or use generated image' : ''} |
|
</label> |
|
<input |
|
type="file" |
|
id="imageInput" |
|
accept="image/*" |
|
onchange={handleFileChange} |
|
bind:this={imageInput} |
|
disabled={isGenerating} |
|
/> |
|
|
|
<label for="captionType">Caption Type</label> |
|
<select |
|
id="captionType" |
|
bind:value={captionType} |
|
disabled={isGenerating} |
|
> |
|
{#each captionTypes as type} |
|
<option value={type}>{type}</option> |
|
{/each} |
|
</select> |
|
|
|
<label for="captionLength">Caption Length</label> |
|
<select |
|
id="captionLength" |
|
bind:value={captionLength} |
|
disabled={isGenerating} |
|
> |
|
{#each captionLengths as length} |
|
<option value={length}> |
|
{length.charAt(0).toUpperCase() + length.slice(1).replace('-', ' ')} |
|
</option> |
|
{/each} |
|
</select> |
|
|
|
<button |
|
type="submit" |
|
class="generate-button" |
|
disabled={isGenerating || !client} |
|
> |
|
{isGenerating ? 'Generating Caption…' : 'Generate Caption'} |
|
</button> |
|
</form> |
|
|
|
{#if error} |
|
<div class="error-message">{error}</div> |
|
{/if} |
|
|
|
{#if result} |
|
<div class="caption-result"> |
|
<h4>Generated Caption</h4> |
|
<p><strong>Type:</strong> {result.type}</p> |
|
<p><strong>Length:</strong> {result.length}</p> |
|
<p><strong>Caption:</strong></p> |
|
<p class="caption-text">{result.caption}</p> |
|
</div> |
|
{/if} |
|
|
|
<style> |
|
.caption-form { |
|
margin-top: 2rem; |
|
padding-top: 2rem; |
|
border-top: 1px solid #eee; |
|
} |
|
|
|
h3 { |
|
margin-top: 0; |
|
margin-bottom: 1.5rem; |
|
} |
|
|
|
label { |
|
font-weight: 600; |
|
margin-bottom: 0.25rem; |
|
display: block; |
|
} |
|
|
|
input[type="file"], |
|
select { |
|
width: 100%; |
|
padding: 0.5rem 0.75rem; |
|
border: 1px solid #ccc; |
|
border-radius: 4px; |
|
box-sizing: border-box; |
|
margin-bottom: 1rem; |
|
} |
|
|
|
.generate-button { |
|
background: #007bff; |
|
color: #fff; |
|
border: none; |
|
padding: 0.6rem 1.4rem; |
|
border-radius: 6px; |
|
cursor: pointer; |
|
font-size: 1rem; |
|
transition: background-color 0.2s; |
|
} |
|
|
|
.generate-button:hover:not(:disabled) { |
|
background: #0056b3; |
|
} |
|
|
|
.generate-button:disabled { |
|
background: #9ac7ff; |
|
cursor: not-allowed; |
|
} |
|
|
|
.caption-result { |
|
background: #f8f9fa; |
|
padding: 1rem; |
|
border-radius: 6px; |
|
margin-top: 1rem; |
|
} |
|
|
|
.caption-result h4 { |
|
margin-top: 0; |
|
} |
|
|
|
.caption-text { |
|
font-style: italic; |
|
} |
|
|
|
.error-message { |
|
color: #dc3545; |
|
margin-top: 1rem; |
|
padding: 0.5rem; |
|
background: #f8d7da; |
|
border-radius: 4px; |
|
} |
|
</style> |