Skip to content

Commit

Permalink
Keep state of AI generator per thread
Browse files Browse the repository at this point in the history
  • Loading branch information
mythz committed Oct 9, 2024
1 parent 7895b09 commit 1c449f1
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 40 deletions.
16 changes: 16 additions & 0 deletions AiServer/wwwroot/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,10 @@ select{
z-index: 40;
}

.z-30 {
z-index: 30;
}

.col-span-12 {
grid-column: span 12 / span 12;
}
Expand Down Expand Up @@ -1217,6 +1221,18 @@ select{
margin-top: auto;
}

.me-5 {
margin-inline-end: 1.25rem;
}

.me-4 {
margin-inline-end: 1rem;
}

.me-2 {
margin-inline-end: 0.5rem;
}

.block {
display: block;
}
Expand Down
65 changes: 34 additions & 31 deletions AiServer/wwwroot/mjs/components/PromptGenerator.mjs
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import { ref, computed, onMounted } from "vue"
import { ref, computed, watch, onMounted } from "vue"
import { useClient } from "@servicestack/vue"
import { createErrorStatus } from "@servicestack/client"
import { ActiveAiModels, QueryPrompts, OpenAiChatCompletion } from "dtos"

export default {
template:`
<div v-if="system">
<button @click="show=!show" type="button" class="-ml-3 bg-white text-gray-600 hover:text-gray-900 group w-full flex items-center pr-2 py-2 text-left text-sm font-medium">
<svg v-if="show" class="text-gray-400 rotate-90 mr-0.5 flex-shrink-0 h-5 w-5 transform group-hover:text-gray-400 transition-colors ease-in-out duration-150" viewBox="0 0 20 20" aria-hidden="true"><path d="M6 6L14 10L6 14V6Z" fill="currentColor"></path></svg>
<button @click="prefs.show=!prefs.show" type="button" class="-ml-3 bg-white text-gray-600 hover:text-gray-900 group w-full flex items-center pr-2 py-2 text-left text-sm font-medium">
<svg v-if="prefs.show" class="text-gray-400 rotate-90 mr-0.5 flex-shrink-0 h-5 w-5 transform group-hover:text-gray-400 transition-colors ease-in-out duration-150" viewBox="0 0 20 20" aria-hidden="true"><path d="M6 6L14 10L6 14V6Z" fill="currentColor"></path></svg>
<svg v-else class="text-gray-300 mr-0.5 flex-shrink-0 h-5 w-5 transform group-hover:text-gray-400 transition-colors ease-in-out duration-150" viewBox="0 0 20 20" aria-hidden="true"><path d="M6 6L14 10L6 14V6Z" fill="currentColor"></path></svg>
AI Prompt Generator
</button>
<div v-if="show">
<div v-if="prefs.show">
<form class="grid grid-cols-6 gap-4" @submit.prevent="send()" :disabled="!validPrompt">
<div class="col-span-6 sm:col-span-2">
<TextInput id="subject" v-model="subject" label="subject" placeholder="Use AI to generate image prompts for..." />
<TextInput id="subject" v-model="prefs.subject" label="subject" placeholder="Use AI to generate image prompts for..." />
</div>
<div class="col-span-6 sm:col-span-2">
<Autocomplete id="model" :options="models" v-model="model" label="model"
<Autocomplete id="model" :options="models" v-model="prefs.model" label="model"
:match="(x, value) => x.toLowerCase().includes(value.toLowerCase())"
class="z-20"
placeholder="Select Model...">
<template #item="name">
<div class="flex items-center">
Expand All @@ -29,53 +30,53 @@ export default {
</Autocomplete>
</div>
<div class="col-span-6 sm:col-span-1">
<TextInput type="number" id="count" v-model="count" label="count" min="1" />
<TextInput type="number" id="count" v-model="prefs.count" label="count" min="1" />
</div>
<div class="col-span-6 sm:col-span-1 align-bottom">
<div>&nbsp;</div>
<PrimaryButton :disabled="!validPrompt">Generate</PrimaryButton>
</div>
</form>
<Loading v-if="client.loading.value">Asking {{model}}...</Loading>
<Loading v-if="client.loading.value">Asking {{prefs.model}}...</Loading>
<ErrorSummary v-else-if="error" :status="error" />
<div v-else-if="results.length" class="mt-4">
<div v-for="result in results" @click="$emit('selected',result)" class="message mb-2 cursor-pointer rounded-lg inline-flex justify-center rounded-lg text-sm py-3 px-4 bg-gray-50 text-slate-900 ring-1 ring-slate-900/10 hover:bg-white/25 hover:ring-slate-900/15">
<div v-else-if="prefs.results.length" class="mt-4">
<div v-for="result in prefs.results" @click="$emit('selected',result)" class="message mb-2 cursor-pointer rounded-lg inline-flex justify-center rounded-lg text-sm py-3 px-4 bg-gray-50 text-slate-900 ring-1 ring-slate-900/10 hover:bg-white/25 hover:ring-slate-900/15">
{{result}}
</div>
</div>
</div>
</div>
`,
emits:['selected'],
emits:['save','selected'],
props: {
thread: Object,
promptId: String,
systemPrompt: String,
},
setup(props) {
setup(props, { emit }) {
const client = useClient()
const request = ref(new OpenAiChatCompletion({ }))
const system = ref(props.systemPrompt)
const subject = ref('')
const defaults = {
show: false,
model: 'gemini-flash',
subject: '',
model: '',
count: 3,
results: [],
}
const prefsKey = 'img2txt.gen.prefs'
const prefs = JSON.parse(localStorage.getItem(prefsKey) ?? JSON.stringify(defaults))
const show = ref(prefs.show)
const count = ref(prefs.count)
const model = ref(prefs.model)
const prefs = ref(Object.assign({}, defaults, props.thread?.generator))
const error = ref()
const models = ref([])
const results = ref([])
const validPrompt = computed(() => subject.value && model.value && count.value)
const validPrompt = computed(() => prefs.value.subject && prefs.value.model && prefs.value.count)

watch(() => props.thread, () => {
Object.assign(prefs.value, defaults, props.thread?.generator)
console.log('watch', prefs.value)
})

function savePrefs() {
prefs.show = show.value
prefs.model = model.value
prefs.count = count.value
localStorage.setItem(prefsKey, JSON.stringify(prefs))
if (props.thread) props.thread.generator = prefs
emit('save', prefs)
}

if (!system.value && props.promptId) {
Expand All @@ -95,7 +96,7 @@ export default {
if (!validPrompt.value) return
savePrefs()

const content = `Provide ${count.value} great descriptive prompts to generate images of ${subject.value} in Stable Diffusion SDXL and Mid Journey. Respond with only the prompts in a JSON array. Example ["prompt1","prompt2"]`
const content = `Provide ${prefs.value.count} great descriptive prompts to generate images of ${prefs.value.subject} in Stable Diffusion SDXL and Mid Journey. Respond with only the prompts in a JSON array. Example ["prompt1","prompt2"]`

const msgs = [
{ role:'system', content:system.value },
Expand All @@ -104,7 +105,7 @@ export default {

const request = new OpenAiChatCompletion({
tag: "admin",
model: model.value,
model: prefs.value.model,
messages: msgs,
temperature: 0.7,
maxTokens: 2048,
Expand All @@ -116,7 +117,7 @@ export default {
let json = api.response?.choices[0]?.message?.content?.trim() ?? ''
console.debug(api.response)
if (json) {
results.value = []
prefs.value.results = []
const docPrefix = '```json'
if (json.startsWith(docPrefix)) {
json = json.substring(docPrefix.length, json.length - 3)
Expand All @@ -125,18 +126,20 @@ export default {
console.log('json', json)
const obj = JSON.parse(json)
if (Array.isArray(obj)) {
results.value = obj
prefs.value.results = obj
}
} catch(e) {
console.warn('could not parse json', e, json)
}
}
if (!results.value.length) {
if (!prefs.value.results.length) {
error.value = createErrorStatus('Could not parse prompts')
} else {
savePrefs()
}
}
}

return { client, system, request, show, subject, count, models, model, error, results, validPrompt, send }
return { client, system, request, prefs, models, error, validPrompt, send }
}
}
28 changes: 20 additions & 8 deletions AiServer/wwwroot/mjs/components/TextToImage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ export default {
</div>
<div class="mt-4 flex w-full flex-col gap-1.5 rounded-lg p-1.5 transition-colors bg-[#f4f4f4]">
<div class="flex items-end gap-1.5 md:gap-2">
<div class="flex items-center gap-1.5 md:gap-2">
<div class="pl-4 flex min-w-0 flex-1 flex-col">
<textarea ref="refMessage" id="txtMessage" v-model="request.positivePrompt" :disabled="waitingOnResponse" rows="1"
<textarea ref="refMessage" id="txtMessage" v-model="request.positivePrompt" :disabled="waitingOnResponse" rows="2"
placeholder="Generate Image Prompt..." @keydown.enter.prevent="send"
:class="[{'opacity-50' : waitingOnResponse},'m-0 resize-none border-0 bg-transparent px-0 text-token-text-primary focus:ring-0 focus-visible:ring-0 max-h-[25dvh] max-h-52']"
style="height: 40px; overflow-y: hidden;"></textarea>
style="height:60px; overflow-y: hidden;"></textarea>
</div>
<button :disabled="!validPrompt || waitingOnResponse" title="Send (Enter)"
class="mb-1 me-1 flex h-8 w-8 items-center justify-center rounded-full bg-black text-white transition-colors hover:opacity-70 focus-visible:outline-none focus-visible:outline-black disabled:bg-[#D7D7D7] disabled:text-[#f4f4f4] disabled:hover:opacity-100 dark:bg-white dark:text-black dark:focus-visible:outline-white disabled:dark:bg-token-text-quaternary dark:disabled:text-token-main-surface-secondary">
class="mb-1 me-2 flex h-8 w-8 items-center justify-center rounded-full bg-black text-white transition-colors hover:opacity-70 focus-visible:outline-none focus-visible:outline-black disabled:bg-[#D7D7D7] disabled:text-[#f4f4f4] disabled:hover:opacity-100 dark:bg-white dark:text-black dark:focus-visible:outline-white disabled:dark:bg-token-text-quaternary dark:disabled:text-token-main-surface-secondary">
<svg v-if="!waitingOnResponse" class="ml-1 w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M3 3.732a1.5 1.5 0 0 1 2.305-1.265l6.706 4.267a1.5 1.5 0 0 1 0 2.531l-6.706 4.268A1.5 1.5 0 0 1 3 12.267z"/></svg>
<svg v-else class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M8 16h8V8H8zm4 6q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>
</button>
Expand All @@ -74,7 +74,9 @@ export default {
</div>
</form>
</div>
<PromptGenerator promptId="midjourney-prompt-generator" @selected="selectPrompt($event)" />
<PromptGenerator :thread="thread" promptId="midjourney-prompt-generator"
@save="saveThread()" @selected="selectPrompt($event)" />
<div class="pb-20">
Expand Down Expand Up @@ -162,6 +164,11 @@ export default {
function saveHistory() {
storage.saveHistory(history.value)
}
function saveThread() {
if (thread.value) {
storage.saveThread(thread.value)
}
}

async function send() {
savePrefs()
Expand Down Expand Up @@ -189,7 +196,7 @@ export default {
response: r
}
thread.value.results.push(result)
storage.saveThread(thread.value)
saveThread()

const id = parseInt(routes.id) || storage.createId()
if (!history.value.find(x => x.id === id)) {
Expand Down Expand Up @@ -230,7 +237,6 @@ export default {

function getThreadResults() {
const ret = Array.from(thread.value?.results ?? [])
console.log('getThreadResults',ret,thread.value)
ret.reverse()
return ret
}
Expand All @@ -256,7 +262,7 @@ export default {

function discardResult(result) {
thread.value.results = thread.value.results.filter(x => x.id != result.id)
storage.saveThread(thread.value)
saveThread()
}

function toggleIcon(item) {
Expand Down Expand Up @@ -287,6 +293,11 @@ export default {

function saveHistoryItem(item) {
storage.saveHistory(history.value)
console.log('saveHistoryItem',item)
if (thread.value && item.title) {
thread.value.title = item.title
saveThread()
}
}

function removeHistoryItem(item) {
Expand Down Expand Up @@ -330,6 +341,7 @@ export default {
defaultIcon,
send,
saveHistory,
saveThread,
toArtifacts,
toggleIcon,
selectRequest,
Expand Down
2 changes: 1 addition & 1 deletion AiServer/wwwroot/mjs/components/utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const HistoryGroups = {
<div class="w-64 overflow-hidden whitespace-nowrap text-ellipsis"
@contextmenu.prevent.stop="showThreadMenu=showThreadMenu==item.id ? null : item.id">
<input v-if="renameThreadId === item.id" id="txtItemTitle" type="text" v-model="item.title" class="text-sm py-1 px-2 font-normal text-gray-700"
@keypress.enter="renameItem" @keydown.esc="renameThreadId=null">
@keypress.enter="renameItem(item)" @keydown.esc="renameThreadId=null">
<div v-else class="flex items-center">
<slot :item="item"></slot>
</div>
Expand Down

0 comments on commit 1c449f1

Please sign in to comment.