| <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> |