initial commit
This commit is contained in:
397
frontend/public/pages/chats.html
Normal file
397
frontend/public/pages/chats.html
Normal file
@@ -0,0 +1,397 @@
|
||||
<div x-data="chatManager()">
|
||||
<!-- Chat Content -->
|
||||
<div
|
||||
class="h-dvh pt-16 flex flex-col-reverse pb-36 overflow-scroll mx-auto px-4 md:px-6 max-w-6xl"
|
||||
>
|
||||
<template x-for="message in currentChatMessages" :key="message.content">
|
||||
<div
|
||||
:class="['mb-4', message.role === 'user' ? 'text-right' : 'text-left']"
|
||||
>
|
||||
<div
|
||||
:class="['inline-block px-4 py-3 text-left rounded-lg max-w-[95%] md:max-w-[85%]',
|
||||
message.role === 'user'
|
||||
? 'bg-primary-100 text-primary-900 rounded-br-none'
|
||||
: 'bg-primary-200 text-primary-900 rounded-bl-none'
|
||||
]"
|
||||
>
|
||||
<!-- Thinking Section -->
|
||||
<div
|
||||
x-show="message.thinking"
|
||||
x-data="{ expanded: false }"
|
||||
@click="expanded = !expanded"
|
||||
>
|
||||
<div
|
||||
class="cursor-pointer rounded-lg overflow-hidden bg-primary-100 hover:bg-primary-50"
|
||||
>
|
||||
<div
|
||||
class="flex justify-center w-full px-3 py-2 text-xs text-primary-700 flex items-center gap-2 transition-colors"
|
||||
>
|
||||
<span x-text="expanded ? '▼' : '◀'"></span>
|
||||
<span class="font-medium">Reasoning</span>
|
||||
<span x-text="expanded ? '▼' : '▶'"></span>
|
||||
</div>
|
||||
<div
|
||||
x-show="expanded"
|
||||
class="prose p-4 max-w-none text-sm prose-p:my-1 prose-headings:my-2 prose-ul:my-1 prose-ol:my-1 prose-pre:p-0"
|
||||
x-html="renderMarkdown(message.thinking)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr x-show="message.thinking" class="my-2 border-primary-400/50" />
|
||||
|
||||
<!-- Main Content -->
|
||||
<div
|
||||
class="prose max-w-none text-sm prose-p:my-1 prose-headings:my-2 prose-ul:my-1 prose-ol:my-1 prose-pre:p-0"
|
||||
x-html="renderMarkdown(message.content)"
|
||||
></div>
|
||||
|
||||
<!-- Timestamp -->
|
||||
<div class="flex items-center justify-between gap-2 mt-2">
|
||||
<div
|
||||
class="text-[10px] opacity-60"
|
||||
x-text="new Date(message.created_at).toLocaleTimeString()"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Badges (Assistant) -->
|
||||
<div
|
||||
x-show="message.role === 'assistant' && message.stats"
|
||||
class="flex items-center gap-1 py-2 flex-wrap justify-start text-primary-700"
|
||||
>
|
||||
<!-- Cumulative Tokens with Hover Breakdown -->
|
||||
<div
|
||||
x-show="message.stats?.prompt_tokens || message.stats?.generated_tokens"
|
||||
class="group relative px-2 py-0.5 text-[10px] bg-primary-300/50 rounded-full cursor-help"
|
||||
>
|
||||
<span
|
||||
x-text="`${(message.stats?.prompt_tokens || 0) + (message.stats?.generated_tokens || 0)} tokens`"
|
||||
></span>
|
||||
|
||||
<!-- Tokens -->
|
||||
<div
|
||||
class="invisible group-hover:visible absolute bottom-full left-1/2 -translate-x-1/2 m-2 px-2 py-1 bg-gray-900 text-white text-[10px] rounded whitespace-nowrap pointer-events-none grid grid-cols-[auto_1fr] gap-x-2"
|
||||
>
|
||||
<div
|
||||
x-show="message.stats?.prompt_tokens"
|
||||
x-text="message.stats?.prompt_tokens"
|
||||
></div>
|
||||
<div x-show="message.stats?.prompt_tokens">prompt tokens</div>
|
||||
|
||||
<div
|
||||
x-show="message.stats?.generated_tokens"
|
||||
x-text="message.stats?.generated_tokens"
|
||||
></div>
|
||||
<div x-show="message.stats?.generated_tokens">
|
||||
generated tokens
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span
|
||||
x-show="message.stats?.prompt_per_second"
|
||||
class="px-2 py-0.5 text-[10px] bg-primary-300/50 rounded-full"
|
||||
x-text="`${message.stats?.prompt_per_second.toFixed(1)} ppt/s`"
|
||||
></span>
|
||||
<span
|
||||
x-show="message.stats?.generated_per_second"
|
||||
class="px-2 py-0.5 text-[10px] bg-primary-300/50 rounded-full"
|
||||
x-text="`${message.stats?.generated_per_second.toFixed(1)} tgt/s`"
|
||||
></span>
|
||||
<span
|
||||
x-show="message.stats?.time_to_first_token"
|
||||
class="px-2 py-0.5 text-[10px] bg-primary-300/50 rounded-full"
|
||||
x-text="`${(message.stats?.time_to_first_token / 1000).toFixed(2)}s TTFT`"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Floating Input and Model Selection -->
|
||||
<div class="fixed bottom-4 w-full flex justify-center px-4 md:px-6">
|
||||
<div class="w-full sm:w-[calc(100%-2rem)] max-w-3xl z-10">
|
||||
<div
|
||||
class="flex flex-col gap-3 p-3 bg-primary-50/95 backdrop-blur-sm rounded-2xl shadow-2xl border border-primary-200"
|
||||
>
|
||||
<!-- Model Select -->
|
||||
<div class="relative">
|
||||
<select
|
||||
x-model="selectedModel"
|
||||
class="w-full appearance-none px-9 py-3 bg-gradient-to-r from-primary-50 to-primary-300 border border-primary-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-400 text-primary-900 text-sm font-medium cursor-pointer transition-shadow hover:shadow-md"
|
||||
>
|
||||
<option value="">Select Model</option>
|
||||
<template x-for="model in models" :key="model.id">
|
||||
<option
|
||||
:value="model.id"
|
||||
x-text="model.name || model.id"
|
||||
></option>
|
||||
</template>
|
||||
</select>
|
||||
|
||||
<!-- Computer Icon -->
|
||||
<svg
|
||||
class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-primary-400 pointer-events-none"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Chevron Icon -->
|
||||
<svg
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-primary-400 pointer-events-none transition-colors hover:text-primary-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Message Form -->
|
||||
<form @submit.prevent="sendMessage" class="flex gap-2 items-end">
|
||||
<textarea
|
||||
x-model="inputMessage"
|
||||
placeholder="Type your message..."
|
||||
rows="1"
|
||||
class="scrollbar-hide flex-1 p-3 bg-primary-50 border border-primary-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-400 text-primary-900 text-sm transition-shadow hover:bg-primary-100 resize-none overflow-y-auto max-h-60"
|
||||
@keydown.enter="if (!$event.shiftKey) { $event.preventDefault(); sendMessage(); }"
|
||||
@input="$el.style.height = 'auto'; $el.style.height = $el.scrollHeight + 'px'"
|
||||
></textarea>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="!inputMessage.trim() || loading"
|
||||
:class="(!inputMessage.trim() || loading) ? 'opacity-50 cursor-not-allowed' : 'hover:shadow-md hover:scale-105'"
|
||||
class="self-stretch w-[44px] bg-gradient-to-r from-primary-600 to-primary-500 text-white rounded-xl transition-all flex items-center justify-center flex-shrink-0"
|
||||
>
|
||||
<template x-if="loading">
|
||||
<div
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent"
|
||||
></div>
|
||||
</template>
|
||||
<template x-if="!loading">
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div
|
||||
x-show="error"
|
||||
class="bg-tertiary-50 border border-tertiary-200 px-4 py-2"
|
||||
>
|
||||
<p class="text-sm text-tertiary-700" x-text="error"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating Conversation List Toggle -->
|
||||
<button
|
||||
@click="chatListOpen = !chatListOpen"
|
||||
:aria-expanded="chatListOpen ? 'true' : 'false'"
|
||||
aria-label="Toggle left navigation"
|
||||
class="isolate cursor-pointer fixed z-50 flex justify-between top-4 left-4 md:left-6 p-2 rounded-md text-primary-700 hover:bg-primary-300 transition-colors"
|
||||
>
|
||||
<svg
|
||||
x-show="!chatListOpen"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
x-show="chatListOpen"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Floating Conversation List -->
|
||||
<div
|
||||
x-show="chatListOpen"
|
||||
x-transition:enter="transform transition-all duration-300 ease-out"
|
||||
x-transition:enter-start="-translate-x-full opacity-0"
|
||||
x-transition:enter-end="translate-x-0 opacity-100"
|
||||
x-transition:leave="transform transition-all duration-300 ease-in"
|
||||
x-transition:leave-start="translate-x-0 opacity-100"
|
||||
x-transition:leave-end="-translate-x-full opacity-0"
|
||||
class="fixed top-16 left-0 right-0 mx-auto md:left-6 md:right-auto md:mx-0 bottom-4 w-86 bg-primary-100 rounded-xl shadow-lg z-20 overflow-hidden flex flex-col"
|
||||
>
|
||||
<div class="px-4 py-3 border-b border-primary-200 flex justify-center">
|
||||
<h4 class="font-semibold text-primary-900">
|
||||
<span>Conversations</span>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<!-- Conversation List-->
|
||||
<div id="left-nav-desktop" class="flex-1 overflow-y-auto p-4">
|
||||
<div
|
||||
x-show="chats.length === 0"
|
||||
class="h-full flex flex-col justify-center text-center py-8 text-primary-600"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-10 w-10 mx-auto mb-2 text-primary-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-sm">No chats yet</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<template x-for="chat in chats" :key="chat.id">
|
||||
<div
|
||||
@click="selectChat(chat.id); chatListOpen = false;"
|
||||
:class="[
|
||||
'p-3 rounded-lg cursor-pointer transition-all border-l-3',
|
||||
selectedChatID === chat.id
|
||||
? 'bg-primary-200 border-l-primary-600'
|
||||
: 'hover:bg-primary-200 border-l-transparent'
|
||||
]"
|
||||
:title="chat.title"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<!-- Icon -->
|
||||
<div class="mt-0.5 shrink-0">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:class="[
|
||||
'h-4 w-4',
|
||||
selectedChatID === chat.id ? 'text-primary-600' : 'text-primary-400'
|
||||
]"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-medium text-sm text-primary-900 truncate">
|
||||
<span x-text="chat.title || 'New Conversation'"></span>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-2 mt-1.5 text-xs text-primary-600"
|
||||
>
|
||||
<span
|
||||
x-show="chat.message_count > 0"
|
||||
class="shrink-0 bg-primary-300 text-primary-700 px-1.5 py-0.5 rounded text-[10px] font-medium"
|
||||
x-text="chat.message_count"
|
||||
></span>
|
||||
<span class="truncate" x-text="chat.initial_message"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click.stop="deleteChat($event, chat.id)"
|
||||
class="cursor-pointer shrink-0 p-1 text-primary-400 hover:text-tertiary-600 hover:bg-tertiary-100 rounded transition-colors"
|
||||
title="Delete Chat"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Left Nav Footer -->
|
||||
<div
|
||||
x-show="$store.navigation.activeTab === 'chats'"
|
||||
class="p-4 border-t border-primary-200 shrink-0"
|
||||
>
|
||||
<button
|
||||
@click="selectChat(null)"
|
||||
class="w-full px-4 py-2.5 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors text-sm font-medium flex cursor-pointer items-center justify-center gap-2"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
New Conversation
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
244
frontend/public/pages/images.html
Normal file
244
frontend/public/pages/images.html
Normal file
@@ -0,0 +1,244 @@
|
||||
<div
|
||||
class="flex flex-col gap-4 pt-16 mx-auto px-4 md:px-6 max-w-6xl"
|
||||
x-data="imageGenerator()"
|
||||
>
|
||||
<div>
|
||||
<form @submit.prevent="generateImage" class="flex flex-col gap-4 w-full">
|
||||
<!-- Prompt -->
|
||||
<div class="flex-1">
|
||||
<label for="prompt" class="text-sm font-medium text-primary-700"
|
||||
>Prompt</label
|
||||
>
|
||||
<textarea
|
||||
id="prompt"
|
||||
name="prompt"
|
||||
class="mt-1 p-2 w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm min-h-[100px] overflow-y-auto text-primary-900 resize-none"
|
||||
required
|
||||
x-model="prompt"
|
||||
placeholder="Enter your image generation prompt here..."
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Parameters -->
|
||||
<div class="flex flex-col gap-3">
|
||||
<div>
|
||||
<label
|
||||
for="nav-model"
|
||||
class="block text-sm font-medium text-primary-700"
|
||||
>Model</label
|
||||
>
|
||||
<select
|
||||
id="nav-model"
|
||||
name="model"
|
||||
x-model="selectedModel"
|
||||
class="mt-1 p-2 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||
required
|
||||
>
|
||||
<option value="">Select Model</option>
|
||||
<template x-for="model in models" :key="model.id">
|
||||
<option :value="model.id" x-text="model.name"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="size" class="text-sm font-medium text-primary-700"
|
||||
>Size</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="size"
|
||||
name="size"
|
||||
x-model="size"
|
||||
class="mt-1 p-2 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-1">
|
||||
<label
|
||||
for="nav-n"
|
||||
class="block text-sm font-medium text-primary-700"
|
||||
>Count</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
id="nav-n"
|
||||
name="n"
|
||||
min="1"
|
||||
max="10"
|
||||
x-model="n"
|
||||
class="mt-1 p-2 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||
value="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<label
|
||||
for="nav-seed"
|
||||
class="block text-sm font-medium text-primary-700"
|
||||
>Seed</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
id="nav-seed"
|
||||
name="seed"
|
||||
x-model="seed"
|
||||
class="mt-1 p-2 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||
value="-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="image-upload" class="text-sm font-medium text-primary-700"
|
||||
>Upload Image to Edit</label
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
id="image-upload"
|
||||
accept="image/*"
|
||||
@change="startEdit"
|
||||
class="mt-1 p-2 block w-full rounded-md border-primary-400 shadow text-primary-900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Edit Panel -->
|
||||
<div
|
||||
id="edit-panel"
|
||||
x-show="editMode"
|
||||
class="mt-2 bg-primary-50 p-4 rounded shadow"
|
||||
>
|
||||
<div
|
||||
:class="['flex gap-4', isLandscape ? 'flex-col' : 'flex-col lg:flex-row']"
|
||||
>
|
||||
<!-- Image Preview -->
|
||||
<div class="flex justify-center relative">
|
||||
<img
|
||||
id="editing-image"
|
||||
:src="editingImage?.url"
|
||||
alt="Original image for editing"
|
||||
class="max-h-[75vh] rounded-lg shadow-md"
|
||||
/>
|
||||
<canvas
|
||||
id="mask"
|
||||
class="absolute top-0 left-0 w-full h-full rounded-lg cursor-crosshair"
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Mask Options -->
|
||||
<div class="flex-1 flex flex-col gap-2 mt-auto justify-end">
|
||||
<div class="mt-2">
|
||||
<label
|
||||
for="lineWidthSlider"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Line Width: <span x-text="lineWidth"></span>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
id="lineWidthSlider"
|
||||
x-model="lineWidth"
|
||||
min="1"
|
||||
max="100"
|
||||
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
@click="clearMask"
|
||||
class="mt-2 px-3 py-1 cursor-pointer bg-primary-200 text-primary-700 rounded hover:bg-primary-600 hover:text-white text-center transition-colors"
|
||||
>
|
||||
Clear Mask
|
||||
</span>
|
||||
<span
|
||||
@click="cancelEdit"
|
||||
class="mt-2 px-3 py-1 cursor-pointer bg-primary-200 text-primary-700 rounded hover:bg-primary-600 hover:text-white text-center transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
x-bind:disabled="loading || !selectedModel"
|
||||
:class="loading || !selectedModel ? 'cursor-not-allowed' : 'cursor-pointer'"
|
||||
class="inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-primary-50 bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 gap-2 transition-colors"
|
||||
>
|
||||
<span
|
||||
x-text="loading ? '' : editMode ? 'Edit Image' : 'Generate Image'"
|
||||
></span>
|
||||
<div
|
||||
x-show="loading"
|
||||
class="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent"
|
||||
></div>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-medium text-primary-900 mb-2">Generated Images</h3>
|
||||
|
||||
<div
|
||||
x-show="error"
|
||||
class="bg-tertiary-50 border border-tertiary-200 rounded-md p-4 mb-4"
|
||||
>
|
||||
<p class="text-tertiary-700" x-text="error"></p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="!generatedImages.length"
|
||||
class="text-center py-8 text-primary-500"
|
||||
>
|
||||
No Images Found
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="generatedImages.length"
|
||||
class="columns-2 md:columns-3 lg:columns-4 gap-2"
|
||||
>
|
||||
<template x-for="(image, index) in generatedImages" :key="index">
|
||||
<div
|
||||
class="flex flex-col gap-2 break-inside-avoid border border-primary-200 rounded-lg p-2 mb-2 h-full bg-primary-100 hover:border-primary-300 transition-colors shadow"
|
||||
>
|
||||
<button
|
||||
@click="deleteImage(image.name)"
|
||||
class="text-white hover:text-white text-sm justify-center cursor-pointer p-1 rounded bg-red-600 hover:bg-red-700 flex items-center h-full transition-colors"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<img
|
||||
:src="image.path"
|
||||
:alt="image.prompt"
|
||||
@click="openLightbox(image.path)"
|
||||
class="rounded-lg shadow-sm max-w-full h-auto cursor-pointer hover:opacity-90 transition-opacity"
|
||||
/>
|
||||
<span
|
||||
class="text-xs text-primary-500 bg-primary-200 px-2 py-1 rounded flex justify-center"
|
||||
x-text="image.date"
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lightbox -->
|
||||
<div
|
||||
x-show="lightbox.open"
|
||||
x-cloak
|
||||
@click="closeLightbox"
|
||||
@keydown.escape.window="closeLightbox"
|
||||
@keydown.arrow-left.window="prevImage"
|
||||
@keydown.arrow-right.window="nextImage"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchend="handleTouchEnd"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/75 p-4 backdrop-blur-sm"
|
||||
>
|
||||
<img
|
||||
:src="lightbox.imageSrc"
|
||||
@click.stop
|
||||
class="max-w-[90vw] max-h-[90vh] object-contain rounded-lg shadow-2xl"
|
||||
alt="Full size preview"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
108
frontend/public/pages/settings.html
Normal file
108
frontend/public/pages/settings.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<form
|
||||
x-data="settingsManager()"
|
||||
@submit.prevent="saveSettings"
|
||||
class="p-0.5 w-full flex flex-col gap-4 pt-16 mx-auto px-4 md:px-6 max-w-6xl"
|
||||
>
|
||||
<div>
|
||||
<label
|
||||
for="apiEndpoint"
|
||||
class="block text-sm font-semibold text-primary-700"
|
||||
>API Endpoint URL</label
|
||||
>
|
||||
<div class="ml-1">
|
||||
<input
|
||||
type="url"
|
||||
id="apiEndpoint"
|
||||
name="apiEndpoint"
|
||||
x-model="settings.api_endpoint"
|
||||
class="mt-1 p-1 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||
placeholder="https://api.example.com/v1"
|
||||
required
|
||||
/>
|
||||
<p class="mt-2 text-xs text-primary-500">URL of your API endpoint</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-medium font-semibold text-primary-700"
|
||||
>Selectors</span
|
||||
>
|
||||
<div class="flex flex-col md:flex-row pl-1 gap-4 justify-between">
|
||||
<div class="w-full">
|
||||
<label
|
||||
for="generateModelSelector"
|
||||
class="text-sm font-medium text-primary-700"
|
||||
>Image</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="generateModelSelector"
|
||||
name="generateModelSelector"
|
||||
x-model="settings.image_generation_selector"
|
||||
class="mt-1 p-1 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||
placeholder=".meta.type: image-generate"
|
||||
/>
|
||||
<p class="mt-2 text-xs text-primary-500">Image generation selector</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<label
|
||||
for="editModelSelector"
|
||||
class="text-sm font-medium text-primary-700"
|
||||
>Image Edit</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="editModelSelector"
|
||||
name="editModelSelector"
|
||||
x-model="settings.image_edit_selector"
|
||||
class="mt-1 p-1 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||
placeholder=".meta.type: image-edit"
|
||||
/>
|
||||
<p class="mt-2 text-xs text-primary-500">
|
||||
Image edit generation selector
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<label
|
||||
for="textModelSelector"
|
||||
class="text-sm font-medium text-primary-700"
|
||||
>Chat</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="textGenerationSelector"
|
||||
name="textGenerationSelector"
|
||||
x-model="settings.text_generation_selector"
|
||||
class="mt-1 p-1 block w-full rounded-md border-primary-400 shadow focus:border-secondary-500 focus:ring-secondary-500 sm:text-sm text-primary-900"
|
||||
placeholder=".meta.type: text-generate"
|
||||
/>
|
||||
<p class="mt-2 text-xs text-primary-500">Text generation selector</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="error"
|
||||
class="bg-tertiary-50 border border-tertiary-200 rounded-md p-4"
|
||||
>
|
||||
<p class="text-tertiary-700" x-text="error"></p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="saved"
|
||||
class="bg-secondary-50 border border-secondary-200 rounded-md p-4"
|
||||
>
|
||||
<p class="text-secondary-700">Settings saved successfully!</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
x-bind:disabled="loading"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-primary-50 bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 cursor-pointer transition-colors"
|
||||
>
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
Reference in New Issue
Block a user