Skip to content

Commit 36160ef

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 673cd1f + 194b7ac commit 36160ef

File tree

6 files changed

+97
-45
lines changed

6 files changed

+97
-45
lines changed

AiServer/wwwroot/css/app.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,10 @@ select{
975975
z-index: 40;
976976
}
977977

978+
.z-30 {
979+
z-index: 30;
980+
}
981+
978982
.col-span-12 {
979983
grid-column: span 12 / span 12;
980984
}
@@ -1217,6 +1221,18 @@ select{
12171221
margin-top: auto;
12181222
}
12191223

1224+
.me-5 {
1225+
margin-inline-end: 1.25rem;
1226+
}
1227+
1228+
.me-4 {
1229+
margin-inline-end: 1rem;
1230+
}
1231+
1232+
.me-2 {
1233+
margin-inline-end: 0.5rem;
1234+
}
1235+
12201236
.block {
12211237
display: block;
12221238
}

AiServer/wwwroot/lib/mjs/servicestack-vue.min.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

AiServer/wwwroot/lib/mjs/servicestack-vue.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4362,7 +4362,7 @@ const ti = { key: 0 }, si = { class: "md:p-4" }, lo = /* @__PURE__ */ de({
43624362
}, u0),
43634363
n.value ? (o(), i("ul", {
43644364
key: 0,
4365-
class: "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-black py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm",
4365+
class: "absolute z-20 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-black py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm",
43664366
onKeydown: L,
43674367
id: `${T.id}-options`,
43684368
role: "listbox"
Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
1-
import { ref, computed, onMounted } from "vue"
1+
import { ref, computed, watch, onMounted } from "vue"
22
import { useClient } from "@servicestack/vue"
33
import { createErrorStatus } from "@servicestack/client"
44
import { ActiveAiModels, QueryPrompts, OpenAiChatCompletion } from "dtos"
55

66
export default {
77
template:`
88
<div v-if="system">
9-
<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">
10-
<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>
9+
<button @click="toggleShow()" 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">
10+
<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>
1111
<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>
1212
AI Prompt Generator
1313
</button>
14-
<div v-if="show">
14+
<div v-if="prefs.show">
1515
<form class="grid grid-cols-6 gap-4" @submit.prevent="send()" :disabled="!validPrompt">
1616
<div class="col-span-6 sm:col-span-2">
17-
<TextInput id="subject" v-model="subject" label="subject" placeholder="Use AI to generate image prompts for..." />
17+
<TextInput id="subject" v-model="prefs.subject" label="subject" placeholder="Use AI to generate image prompts for..." />
1818
</div>
1919
<div class="col-span-6 sm:col-span-2">
20-
<Autocomplete id="model" :options="models" v-model="model" label="model"
20+
<Autocomplete id="model" :options="models" v-model="prefs.model" label="model"
2121
:match="(x, value) => x.toLowerCase().includes(value.toLowerCase())"
22+
class="z-20"
2223
placeholder="Select Model...">
2324
<template #item="name">
2425
<div class="flex items-center">
@@ -29,53 +30,57 @@ export default {
2930
</Autocomplete>
3031
</div>
3132
<div class="col-span-6 sm:col-span-1">
32-
<TextInput type="number" id="count" v-model="count" label="count" min="1" />
33+
<TextInput type="number" id="count" v-model="prefs.count" label="count" min="1" />
3334
</div>
3435
<div class="col-span-6 sm:col-span-1 align-bottom">
3536
<div>&nbsp;</div>
3637
<PrimaryButton :disabled="!validPrompt">Generate</PrimaryButton>
3738
</div>
3839
</form>
39-
<Loading v-if="client.loading.value">Asking {{model}}...</Loading>
40+
<Loading v-if="client.loading.value">Asking {{prefs.model}}...</Loading>
4041
<ErrorSummary v-else-if="error" :status="error" />
41-
<div v-else-if="results.length" class="mt-4">
42-
<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">
42+
<div v-else-if="prefs.results.length" class="mt-4">
43+
<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">
4344
{{result}}
4445
</div>
4546
</div>
4647
</div>
4748
</div>
4849
`,
49-
emits:['selected'],
50+
emits:['save','selected'],
5051
props: {
52+
thread: Object,
5153
promptId: String,
5254
systemPrompt: String,
5355
},
54-
setup(props) {
56+
setup(props, { emit }) {
5557
const client = useClient()
5658
const request = ref(new OpenAiChatCompletion({ }))
5759
const system = ref(props.systemPrompt)
58-
const subject = ref('')
5960
const defaults = {
6061
show: false,
61-
model: 'gemini-flash',
62+
subject: '',
63+
model: '',
6264
count: 3,
65+
results: [],
6366
}
64-
const prefsKey = 'img2txt.gen.prefs'
65-
const prefs = JSON.parse(localStorage.getItem(prefsKey) ?? JSON.stringify(defaults))
66-
const show = ref(prefs.show)
67-
const count = ref(prefs.count)
68-
const model = ref(prefs.model)
67+
const prefs = ref(Object.assign({}, defaults, props.thread?.generator))
6968
const error = ref()
7069
const models = ref([])
71-
const results = ref([])
72-
const validPrompt = computed(() => subject.value && model.value && count.value)
70+
const validPrompt = computed(() => prefs.value.subject && prefs.value.model && prefs.value.count)
71+
72+
watch(() => props.thread, () => {
73+
Object.assign(prefs.value, defaults, props.thread?.generator)
74+
})
75+
76+
function toggleShow() {
77+
prefs.value.show = !prefs.value.show
78+
savePrefs()
79+
}
7380

7481
function savePrefs() {
75-
prefs.show = show.value
76-
prefs.model = model.value
77-
prefs.count = count.value
78-
localStorage.setItem(prefsKey, JSON.stringify(prefs))
82+
if (props.thread) props.thread.generator = prefs
83+
emit('save', prefs)
7984
}
8085

8186
if (!system.value && props.promptId) {
@@ -95,7 +100,7 @@ export default {
95100
if (!validPrompt.value) return
96101
savePrefs()
97102

98-
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"]`
103+
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"]`
99104

100105
const msgs = [
101106
{ role:'system', content:system.value },
@@ -104,7 +109,7 @@ export default {
104109

105110
const request = new OpenAiChatCompletion({
106111
tag: "admin",
107-
model: model.value,
112+
model: prefs.value.model,
108113
messages: msgs,
109114
temperature: 0.7,
110115
maxTokens: 2048,
@@ -116,7 +121,7 @@ export default {
116121
let json = api.response?.choices[0]?.message?.content?.trim() ?? ''
117122
console.debug(api.response)
118123
if (json) {
119-
results.value = []
124+
prefs.value.results = []
120125
const docPrefix = '```json'
121126
if (json.startsWith(docPrefix)) {
122127
json = json.substring(docPrefix.length, json.length - 3)
@@ -125,18 +130,20 @@ export default {
125130
console.log('json', json)
126131
const obj = JSON.parse(json)
127132
if (Array.isArray(obj)) {
128-
results.value = obj
133+
prefs.value.results = obj
129134
}
130135
} catch(e) {
131136
console.warn('could not parse json', e, json)
132137
}
133138
}
134-
if (!results.value.length) {
139+
if (!prefs.value.results.length) {
135140
error.value = createErrorStatus('Could not parse prompts')
141+
} else {
142+
savePrefs()
136143
}
137144
}
138145
}
139146

140-
return { client, system, request, show, subject, count, models, model, error, results, validPrompt, send }
147+
return { client, system, request, prefs, models, error, validPrompt, toggleShow, send }
141148
}
142149
}

AiServer/wwwroot/mjs/components/TextToImage.mjs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ export default {
5555
</div>
5656
5757
<div class="mt-4 flex w-full flex-col gap-1.5 rounded-lg p-1.5 transition-colors bg-[#f4f4f4]">
58-
<div class="flex items-end gap-1.5 md:gap-2">
58+
<div class="flex items-center gap-1.5 md:gap-2">
5959
<div class="pl-4 flex min-w-0 flex-1 flex-col">
60-
<textarea ref="refMessage" id="txtMessage" v-model="request.positivePrompt" :disabled="waitingOnResponse" rows="1"
60+
<textarea ref="refMessage" id="txtMessage" v-model="request.positivePrompt" :disabled="waitingOnResponse" rows="2"
6161
placeholder="Generate Image Prompt..." @keydown.enter.prevent="send"
6262
: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']"
63-
style="height: 40px; overflow-y: hidden;"></textarea>
63+
style="height:60px; overflow-y: hidden;"></textarea>
6464
</div>
6565
<button :disabled="!validPrompt || waitingOnResponse" title="Send (Enter)"
66-
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">
66+
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">
6767
<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>
6868
<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>
6969
</button>
@@ -74,7 +74,9 @@ export default {
7474
</div>
7575
</form>
7676
</div>
77-
<PromptGenerator promptId="midjourney-prompt-generator" @selected="selectPrompt($event)" />
77+
78+
<PromptGenerator :thread="thread" promptId="midjourney-prompt-generator"
79+
@save="saveThread()" @selected="selectPrompt($event)" />
7880
7981
<div class="pb-20">
8082
@@ -131,10 +133,13 @@ export default {
131133
const waitingOnResponse = ref(false)
132134
const storage = new ThreadStorage(`img2txt`, {
133135
model: 'jib-mix-realistic',
136+
positivePrompt: "",
134137
negativePrompt: '(nsfw),(explicit),(gore),(violence),(blood)',
135138
width: 1024,
136139
height: 1024,
137140
batchSize: 1,
141+
seed: '',
142+
tag: '',
138143
})
139144
const error = ref()
140145

@@ -162,6 +167,12 @@ export default {
162167
function saveHistory() {
163168
storage.saveHistory(history.value)
164169
}
170+
function saveThread() {
171+
console.log('saveThread', thread.value)
172+
if (thread.value) {
173+
storage.saveThread(thread.value)
174+
}
175+
}
165176

166177
async function send() {
167178
savePrefs()
@@ -189,7 +200,7 @@ export default {
189200
response: r
190201
}
191202
thread.value.results.push(result)
192-
storage.saveThread(thread.value)
203+
saveThread()
193204

194205
const id = parseInt(routes.id) || storage.createId()
195206
if (!history.value.find(x => x.id === id)) {
@@ -230,7 +241,6 @@ export default {
230241

231242
function getThreadResults() {
232243
const ret = Array.from(thread.value?.results ?? [])
233-
console.log('getThreadResults',ret,thread.value)
234244
ret.reverse()
235245
return ret
236246
}
@@ -256,7 +266,7 @@ export default {
256266

257267
function discardResult(result) {
258268
thread.value.results = thread.value.results.filter(x => x.id != result.id)
259-
storage.saveThread(thread.value)
269+
saveThread()
260270
}
261271

262272
function toggleIcon(item) {
@@ -273,20 +283,24 @@ export default {
273283
const id = parseInt(routes.id)
274284
thread.value = storage.getThread(storage.getThreadId(id))
275285
threadRef.value = history.value.find(x => x.id === parseInt(routes.id))
276-
// console.log('thread', id, thread.value)
277-
// console.log('threadRef', threadRef.value)
286+
Object.keys(storage.defaults).forEach(field =>
287+
request.value[field] = thread.value[field] ?? storage.defaults[field])
278288
} else {
279289
thread.value = null
280290
}
281291
}
282292

283293
function updated() {
284-
// console.debug('updated', routes.admin, routes.id)
285294
onRouteChange()
286295
}
287296

288297
function saveHistoryItem(item) {
289298
storage.saveHistory(history.value)
299+
console.log('saveHistoryItem',item)
300+
if (thread.value && item.title) {
301+
thread.value.title = item.title
302+
saveThread()
303+
}
290304
}
291305

292306
function removeHistoryItem(item) {
@@ -302,6 +316,20 @@ export default {
302316
}
303317

304318
watch(() => routes.id, updated)
319+
watch(() => [
320+
request.value.model,
321+
request.value.positivePrompt,
322+
request.value.negativePrompt,
323+
request.value.width,
324+
request.value.height,
325+
request.value.batchSize,
326+
request.value.seed,
327+
request.value.tag,
328+
], () => {
329+
Object.keys(storage.defaults).forEach(field =>
330+
thread.value[field] = request.value[field] ?? storage.defaults[field])
331+
saveThread()
332+
})
305333

306334
onMounted(async () => {
307335
const api = await client.api(new ActiveMediaModels())
@@ -330,6 +358,7 @@ export default {
330358
defaultIcon,
331359
send,
332360
saveHistory,
361+
saveThread,
333362
toArtifacts,
334363
toggleIcon,
335364
selectRequest,

AiServer/wwwroot/mjs/components/utils.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const HistoryGroups = {
5959
<div class="w-64 overflow-hidden whitespace-nowrap text-ellipsis"
6060
@contextmenu.prevent.stop="showThreadMenu=showThreadMenu==item.id ? null : item.id">
6161
<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"
62-
@keypress.enter="renameItem" @keydown.esc="renameThreadId=null">
62+
@keypress.enter="renameItem(item)" @keydown.esc="renameThreadId=null">
6363
<div v-else class="flex items-center">
6464
<slot :item="item"></slot>
6565
</div>

0 commit comments

Comments
 (0)