Prep 0.20.0 (#77)

* fix issue where recent save cover images would sometimes not load

* paraphrase prompt tweaks

* action_to_narration regenerate compatibility fixes

* sim suite add asnwer question instruction

* more sim suite tweaks

* refactor agent details display in agent bar

* visual agent progres (a1111 support)

* visual gen prompt tweaks

* openai compat client pass max_tokens

* world state sequential reinforcement max tokens tightened

* improve item names

* Improve item names

* attempt to remove "changed from.." notes when altering an existing character sheet

* prompt improvements for single character portraits

* visual agent progress

* fix issue where character.update wouldn't update long-term memory

* remove experimental flag for now

* add better instructions for updating existing character sheet

* background processing for agents, visual and tts

* fix selected voice not saving between restarts for elevenlabs

* lessen timeout

* clean up agent status logic

* conditional agent configs

* comfyui support

* visualization queue

* refactor visual styles, comfyui progress

* regen images
auto cover image assign
websocket handler plugin abstraction
agent websocket handler

* automatic1111 fixes
agent status and ready checks

* tweaks to character portrait prompt

* system prompt for visualize

* textgenwebui use temp smoothing on yi models

* comment out api key for now

* fixes issues with openai compat client for retaining api key and auto fixing urls

* update_reinforcment tweaks

* agent status emit from one place

* emit agent status as asyncio task

* remove debug output

* tts add openai support

* openai img gen support

* fix issue with confyui checkbox list not loading

* tts model selection for openai

* narrate_query include character sheet if character is referenced in query
improve visual character portrit generation prompt

* client implementation extra field support and runpod vllm client example

* relock

* fix issue where changing context length would cause next generation to error

* visual agent tweaks and auto gen character cover image in sim suite

* fix issue with readyness lock when there werent any clients defined

* load scene readiness fixes

* linting

* docs

* notes for the runpod vllm example
This commit is contained in:
veguAI
2024-02-16 13:57:45 +02:00
committed by GitHub
parent 9ae6fc822b
commit 2f07248211
73 changed files with 3843 additions and 1027 deletions

View File

@@ -10,6 +10,7 @@
"dependencies": {
"@mdi/font": "7.4.47",
"core-js": "^3.8.3",
"dot-prop": "^8.0.2",
"roboto-fontface": "*",
"vue": "^3.2.13",
"vuetify": "^3.5.0",
@@ -4914,6 +4915,31 @@
"tslib": "^2.0.3"
}
},
"node_modules/dot-prop": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz",
"integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==",
"dependencies": {
"type-fest": "^3.8.0"
},
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dot-prop/node_modules/type-fest": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-10.0.0.tgz",
@@ -14998,6 +15024,21 @@
"tslib": "^2.0.3"
}
},
"dot-prop": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz",
"integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==",
"requires": {
"type-fest": "^3.8.0"
},
"dependencies": {
"type-fest": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="
}
}
},
"dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-10.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "talemate_frontend",
"version": "0.19.0",
"version": "0.20.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -10,6 +10,7 @@
"dependencies": {
"@mdi/font": "7.4.47",
"core-js": "^3.8.3",
"dot-prop": "^8.0.2",
"roboto-fontface": "*",
"vue": "^3.2.13",
"vuetify": "^3.5.0",

View File

@@ -1,10 +1,12 @@
<template>
<div v-if="isConnected()">
<v-list v-for="(agent, index) in state.agents" :key="index">
<v-list v-for="(agent, index) in state.agents" :key="index" density="compact">
<v-list-item @click="editAgent(index)">
<v-list-item-title>
<v-progress-circular v-if="agent.status === 'busy'" indeterminate="disable-shrink" color="primary"
size="14"></v-progress-circular>
<v-progress-circular v-else-if="agent.status === 'busy_bg'" indeterminate="disable-shrink" color="secondary"
size="14"></v-progress-circular>
<v-icon v-else-if="agent.status === 'uninitialized'" color="orange" size="14">mdi-checkbox-blank-circle</v-icon>
<v-icon v-else-if="agent.status === 'disabled'" color="grey-darken-2" size="14">mdi-checkbox-blank-circle</v-icon>
<v-icon v-else-if="agent.status === 'error'" color="red-darken-1" size="14">mdi-checkbox-blank-circle</v-icon>
@@ -18,9 +20,42 @@
</template>
</v-tooltip>
</v-list-item-title>
<v-list-item-subtitle class="text-caption">
{{ agent.client }}
</v-list-item-subtitle>
<div v-if="typeof(agent.client) === 'string'">
<v-chip prepend-icon="mdi-network-outline" class="mr-1" size="x-small" color="grey" variant="tonal" label>{{ agent.client }}</v-chip>
<!--
<v-icon color="grey" size="x-small" v-bind="props">mdi-network-outline</v-icon>
<span class="ml-1 text-caption text-bold text-grey-lighten-1">{{ agent.client }}</span>
-->
</div>
<div v-else-if="typeof(agent.client) === 'object'">
<v-tooltip v-for="(detail, key) in agent.client" :key="key" :text="detail.description" >
<template v-slot:activator="{ props }">
<v-chip
class="mr-1"
size="x-small"
v-bind="props"
:prepend-icon="detail.icon"
label
:color="detail.color || 'grey'"
variant="tonal"
>
{{ detail.value }}
</v-chip>
</template>
</v-tooltip>
<!--
<div v-for="(detail, key) in agent.client" :key="key">
<v-tooltip :text="detail.description" v-if="detail.icon != null">
<template v-slot:activator="{ props }">
<v-icon color="grey" size="x-small" v-bind="props">{{ detail.icon }}</v-icon>
</template>
</v-tooltip>
<span class="ml-1 text-caption text-bold text-grey-lighten-1">{{ detail.value }}</span>
</div>
-->
</div>
<!--
<v-chip class="mr-1" v-if="agent.status === 'disabled'" size="x-small">Disabled</v-chip>
<v-chip v-if="agent.data.experimental" color="warning" size="x-small">experimental</v-chip>
@@ -74,7 +109,7 @@ export default {
for(let i = 0; i < this.state.agents.length; i++) {
let agent = this.state.agents[i];
if(!agent.data.requires_llm_client)
if(!agent.data.requires_llm_client || agent.meta.essential === false)
continue
if(agent.status === 'warning' || agent.status === 'error' || agent.status === 'uninitialized') {
@@ -133,21 +168,36 @@ export default {
// Find the client with the given name
const agent = this.state.agents.find(agent => agent.name === data.name);
if (agent) {
if(agent.name == 'tts') {
console.log("agents: agent_status TTS", data)
}
// Update the model name of the client
agent.client = data.client;
agent.data = data.data;
agent.status = data.status;
agent.label = data.message;
agent.meta = data.meta;
agent.actions = {}
for(let i in data.data.actions) {
agent.actions[i] = {enabled: data.data.actions[i].enabled, config: data.data.actions[i].config};
agent.actions[i] = {enabled: data.data.actions[i].enabled, config: data.data.actions[i].config, condition: data.data.actions[i].condition};
}
agent.enabled = data.data.enabled;
// sort agents by label
this.state.agents.sort((a, b) => {
if(a.label < b.label) { return -1; }
if(a.label > b.label) { return 1; }
return 0;
});
} else {
// Add the agent to the list of agents
let actions = {}
for(let i in data.data.actions) {
actions[i] = {enabled: data.data.actions[i].enabled, config: data.data.actions[i].config};
actions[i] = {enabled: data.data.actions[i].enabled, config: data.data.actions[i].config, condition: data.data.actions[i].condition};
}
this.state.agents.push({
name: data.name,
@@ -157,6 +207,7 @@ export default {
label: data.message,
actions: actions,
enabled: data.data.enabled,
meta: data.meta,
});
console.log("agents: added new agent", this.state.agents[this.state.agents.length - 1], data)
}

View File

@@ -18,7 +18,7 @@
</v-card-title>
<v-card-text class="scrollable-content">
<v-select v-if="agent.data.requires_llm_client" v-model="agent.client" :items="agent.data.client" label="Client" @update:modelValue="save(false)"></v-select>
<v-select v-if="agent.data.requires_llm_client" v-model="selectedClient" :items="agent.data.client" label="Client" @update:modelValue="save(false)"></v-select>
<v-alert type="warning" variant="tonal" density="compact" v-if="agent.data.experimental">
This agent is currently experimental and may significantly decrease performance and / or require
@@ -26,27 +26,29 @@
</v-alert>
<v-card v-for="(action, key) in agent.actions" :key="key" density="compact">
<v-card-subtitle>
<v-checkbox v-if="!actionAlwaysEnabled(key)" :label="agent.data.actions[key].label" hide-details density="compact" color="green" v-model="action.enabled" @update:modelValue="save(false)"></v-checkbox>
</v-card-subtitle>
<v-card-text>
<div v-if="!actionAlwaysEnabled(key)">
{{ agent.data.actions[key].description }}
</div>
<div v-for="(action_config, config_key) in agent.data.actions[key].config" :key="config_key">
<div v-if="action.enabled">
<!-- render config widgets based on action_config.type (int, str, bool, float) -->
<v-text-field v-if="action_config.type === 'text' && action_config.choices === null" v-model="action.config[config_key].value" :label="action_config.label" :hint="action_config.description" density="compact" @update:modelValue="save(true)"></v-text-field>
<v-autocomplete v-else-if="action_config.type === 'text' && action_config.choices !== null" v-model="action.config[config_key].value" :items="action_config.choices" :label="action_config.label" :hint="action_config.description" density="compact" item-title="label" item-value="value" @update:modelValue="save(false)"></v-autocomplete>
<v-slider v-if="action_config.type === 'number' && action_config.step !== null" v-model="action.config[config_key].value" :label="action_config.label" :hint="action_config.description" :min="action_config.min" :max="action_config.max" :step="action_config.step" density="compact" thumb-label @update:modelValue="save(true)"></v-slider>
<v-checkbox v-if="action_config.type === 'bool'" v-model="action.config[config_key].value" :label="action_config.label" :hint="action_config.description" density="compact" @update:modelValue="save(false)"></v-checkbox>
<v-alert v-if="action_config.note != null" variant="outlined" density="compact" color="grey-darken-1" icon="mdi-information">
{{ action_config.note }}
</v-alert>
<div v-if="testActionConditional(action)">
<v-card-subtitle>
<v-checkbox v-if="!actionAlwaysEnabled(key)" :label="agent.data.actions[key].label" hide-details density="compact" color="green" v-model="action.enabled" @update:modelValue="save(false)"></v-checkbox>
</v-card-subtitle>
<v-card-text>
<div v-if="!actionAlwaysEnabled(key)">
{{ agent.data.actions[key].description }}
</div>
</div>
</v-card-text>
<div v-for="(action_config, config_key) in agent.data.actions[key].config" :key="config_key">
<div v-if="action.enabled">
<!-- render config widgets based on action_config.type (int, str, bool, float) -->
<v-text-field v-if="action_config.type === 'text' && action_config.choices === null" v-model="action.config[config_key].value" :label="action_config.label" :hint="action_config.description" density="compact" @update:modelValue="save(true)"></v-text-field>
<v-autocomplete v-else-if="action_config.type === 'text' && action_config.choices !== null" v-model="action.config[config_key].value" :items="action_config.choices" :label="action_config.label" :hint="action_config.description" density="compact" item-title="label" item-value="value" @update:modelValue="save(false)"></v-autocomplete>
<v-slider v-if="action_config.type === 'number' && action_config.step !== null" v-model="action.config[config_key].value" :label="action_config.label" :hint="action_config.description" :min="action_config.min" :max="action_config.max" :step="action_config.step" density="compact" thumb-label @update:modelValue="save(true)"></v-slider>
<v-checkbox v-if="action_config.type === 'bool'" v-model="action.config[config_key].value" :label="action_config.label" :hint="action_config.description" density="compact" @update:modelValue="save(false)"></v-checkbox>
<v-alert v-if="action_config.note != null" variant="outlined" density="compact" color="grey-darken-1" icon="mdi-information">
{{ action_config.note }}
</v-alert>
</div>
</div>
</v-card-text>
</div>
</v-card>
</v-card-text>
@@ -55,6 +57,8 @@
</template>
<script>
import {getProperty} from 'dot-prop';
export default {
props: {
dialog: Boolean,
@@ -65,6 +69,7 @@ export default {
return {
saveTimeout: null,
localDialog: this.state.dialog,
selectedClient: null,
agent: { ...this.state.currentAgent }
};
},
@@ -73,6 +78,9 @@ export default {
immediate: true,
handler(newVal) {
this.localDialog = newVal;
if(newVal) {
this.selectedClient = typeof(this.agent.client) === 'object' && this.agent.client.client ? this.agent.client.client.value : this.agent.client;
}
}
},
'state.currentAgent': {
@@ -93,19 +101,40 @@ export default {
return 'Disabled';
}
},
actionAlwaysEnabled(action) {
if (action.charAt(0) === '_') {
actionAlwaysEnabled(actionName) {
if (actionName.charAt(0) === '_') {
return true;
} else {
return false;
}
},
testActionConditional(action) {
if(action.condition == null)
return true;
if(typeof(this.agent.client) !== 'object')
return true;
let value = getProperty(this.agent.actions, action.condition.attribute+".value");
return value == action.condition.value;
},
close() {
this.$emit('update:dialog', false);
},
save(delayed = false) {
console.log("save", delayed);
if(this.selectedClient != null) {
if(typeof(this.agent.client) === 'object') {
if(this.agent.client.client != null)
this.agent.client.client.value = this.selectedClient;
} else {
this.agent.client = this.selectedClient;
}
}
if(!delayed) {
this.$emit('save', this.agent);
return;

View File

@@ -34,7 +34,12 @@
<v-select v-model="client.model" v-if="clientMeta().manual_model && clientMeta().manual_model_choices" :items="clientMeta().manual_model_choices" label="Model"></v-select>
<v-text-field v-model="client.model_name" v-else-if="clientMeta().manual_model" label="Manually specify model name" hint="It looks like we're unable to retrieve the model name automatically. The model name is used to match the appropriate prompt template. This is likely only important if you're locally serving a model."></v-text-field>
</v-col>
</v-row>
</v-row>
<v-row v-for="field in clientMeta().extra_fields" :key="field.name">
<v-col cols="12">
<v-text-field v-model="client.data[field.name]" v-if="field.type==='text'" :label="field.label" :rules="[rules.required]" :hint="field.description"></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="4">
<v-text-field v-model="client.max_token_length" v-if="requiresAPIUrl(client)" type="number" label="Context Length" :rules="[rules.required]"></v-text-field>

View File

@@ -1,7 +1,7 @@
<template>
<div v-if="expanded">
<v-sheet v-if="expanded" elevation="10">
<v-img cover @click="toggle()" v-if="asset_id !== null" :src="'data:'+media_type+';base64, '+base64"></v-img>
</div>
</v-sheet>
<v-list-subheader v-else @click="toggle()"><v-icon>mdi-image-frame</v-icon> Cover image
<v-icon v-if="expanded" icon="mdi-chevron-down"></v-icon>
<v-icon v-else icon="mdi-chevron-up"></v-icon>
@@ -49,6 +49,11 @@ export default {
this.media_type = data.media_type;
}
}
if(data.type === "scene_asset_character_cover_image") {
this.asset_id = data.asset_id;
this.base64 = data.asset;
this.media_type = data.media_type;
}
},
},

View File

@@ -56,7 +56,7 @@ export default {
if(newVal != null) {
this.requestCoverImages();
}
}
},
},
methods: {
@@ -120,7 +120,6 @@ export default {
handleMessage(data) {
if(data.type === 'assets') {
console.log("ASSEsTS", data.assets)
for(let id in data.assets) {
let asset = data.assets[id];
this.coverImages[id] = {
@@ -128,10 +127,12 @@ export default {
mediaType: asset.mediaType,
};
}
console.log("assets", this.coverImages, data)
}
},
},
mounted() {
this.requestCoverImages();
},
created() {
this.registerMessageHandler(this.handleMessage);
},

View File

@@ -7,7 +7,7 @@
<v-list-subheader class="text-uppercase" v-else>
<v-progress-circular indeterminate="disable-shrink" color="primary" size="20"></v-progress-circular> Waiting for config...
</v-list-subheader>
<div v-if="!loading && isConnected() && expanded && !configurationRequired() && appConfig !== null">
<div v-if="!loading && isConnected() && expanded && sceneLoadingAvailable && appConfig !== null">
<v-list-item>
<div class="mb-3">
<!-- Toggle buttons for switching between file upload and path input -->
@@ -43,7 +43,7 @@
</div>
</v-list-item>
</div>
<div v-else-if="configurationRequired()">
<div v-else-if="!sceneLoadingAvailable">
<v-alert type="warning" variant="tonal">You need to configure a Talemate client before you can load scenes.</v-alert>
</div>
<DefaultCharacter ref="defaultCharacterModal" @save="loadScene" @cancel="loadCanceled"></DefaultCharacter>
@@ -58,6 +58,9 @@ export default {
components: {
DefaultCharacter,
},
props: {
sceneLoadingAvailable: Boolean
},
data() {
return {
loading: false,
@@ -75,7 +78,7 @@ export default {
emits: {
loading: null,
},
inject: ['getWebsocket', 'registerMessageHandler', 'isConnected', 'configurationRequired'],
inject: ['getWebsocket', 'registerMessageHandler', 'isConnected'],
methods: {
// Method to show the DefaultCharacter modal
showDefaultCharacterModal() {

View File

@@ -311,6 +311,30 @@
</template>
</v-tooltip>
<!-- visualizer actions -->
<v-menu>
<template v-slot:activator="{ props }">
<v-btn class="hotkey mx-3" v-bind="props" :disabled="isInputDisabled() || !visualAgentReady" color="primary" icon>
<v-icon>mdi-image-frame</v-icon>
</v-btn>
</template>
<v-list>
<v-list-subheader>Visualize</v-list-subheader>
<!-- environment -->
<v-list-item @click="sendHotButtonMessage('!vis_env')" prepend-icon="mdi-image-filter-hdr">
<v-list-item-title>Visualize Environment</v-list-item-title>
<v-list-item-subtitle>Generate a background image of the environment</v-list-item-subtitle>
</v-list-item>
<!-- npcs -->
<v-list-item v-for="npc_name in npc_characters" :key="npc_name"
@click="sendHotButtonMessage('!vis_char:' + npc_name)" prepend-icon="mdi-brush">
<v-list-item-title>Visualize {{ npc_name }}</v-list-item-title>
<v-list-item-subtitle>Generate a portrait of {{ npc_name }}</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-menu>
<!-- save menu -->
<v-menu>
@@ -371,6 +395,7 @@ export default {
sceneHelp: "",
sceneExperimental: false,
canAutoSave: false,
visualAgentReady: false,
npc_characters: [],
quickSettings: [
@@ -669,6 +694,8 @@ export default {
}
}
return;
} else if (data.type === 'agent_status' && data.name === 'visual') {
this.visualAgentReady = data.status == 'idle' || data.status == 'busy' || data.status == 'busy_bg';
} else if (data.type === "quick_settings" && data.action === 'set_done') {
return;
}

View File

@@ -11,7 +11,10 @@
Make sure the backend process is running.
</p>
</v-alert>
<LoadScene ref="loadScene" @loading="sceneStartedLoading" />
<LoadScene
ref="loadScene"
:scene-loading-available="ready && connected"
@loading="sceneStartedLoading" />
<v-divider></v-divider>
<div :style="(sceneActive && scene.environment === 'scene' ? 'display:block' : 'display:none')">
<!-- <GameOptions v-if="sceneActive" ref="gameOptions" /> -->
@@ -25,7 +28,7 @@
</v-navigation-drawer>
<!-- settings navigation drawer -->
<v-navigation-drawer v-model="drawer" app location="right">
<v-navigation-drawer v-model="drawer" app location="right" width="300">
<v-alert v-if="!connected" type="error" variant="tonal">
Not connected to Talemate backend
<p class="text-body-2" color="white">
@@ -49,7 +52,7 @@
</v-navigation-drawer>
<!-- debug tools navigation drawer -->
<v-navigation-drawer v-model="debugDrawer" app location="right">
<v-navigation-drawer v-model="debugDrawer" app location="right" width="400">
<v-list>
<v-list-subheader class="text-uppercase"><v-icon>mdi-bug</v-icon> Debug Tools</v-list-subheader>
<DebugTools ref="debugTools"></DebugTools>
@@ -74,7 +77,7 @@
<AudioQueue ref="audioQueue" />
<v-spacer></v-spacer>
<span v-if="version !== null">v{{ version }}</span>
<span v-if="configurationRequired()">
<span v-if="!ready">
<v-icon icon="mdi-application-cog"></v-icon>
<span class="ml-1">Configuration required</span>
</span>
@@ -104,9 +107,10 @@
Talemate
</v-toolbar-title>
<v-spacer></v-spacer>
<VisualQueue ref="visualQueue" />
<v-app-bar-nav-icon @click="toggleNavigation('debug')"><v-icon>mdi-bug</v-icon></v-app-bar-nav-icon>
<v-app-bar-nav-icon @click="openAppConfig()"><v-icon>mdi-cog</v-icon></v-app-bar-nav-icon>
<v-app-bar-nav-icon @click="toggleNavigation('settings')" v-if="configurationRequired()"
<v-app-bar-nav-icon @click="toggleNavigation('settings')" v-if="!ready"
color="red-darken-1"><v-icon>mdi-application-cog</v-icon></v-app-bar-nav-icon>
<v-app-bar-nav-icon @click="toggleNavigation('settings')"
v-else><v-icon>mdi-application-cog</v-icon></v-app-bar-nav-icon>
@@ -149,7 +153,7 @@
<IntroView v-else
@request-scene-load="(path) => { $refs.loadScene.loadJsonSceneFromPath(path); }"
:version="version"
:scene-loading-available="!configurationRequired() && connected"
:scene-loading-available="ready && connected"
:config="appConfig" />
</v-container>
@@ -179,6 +183,7 @@ import AppConfig from './AppConfig.vue';
import DebugTools from './DebugTools.vue';
import AudioQueue from './AudioQueue.vue';
import StatusNotification from './StatusNotification.vue';
import VisualQueue from './VisualQueue.vue';
import IntroView from './IntroView.vue';
@@ -200,6 +205,7 @@ export default {
AudioQueue,
StatusNotification,
IntroView,
VisualQueue,
},
name: 'TalemateApp',
data() {
@@ -220,6 +226,7 @@ export default {
errorMessage: null,
errorNotification: false,
notificatioonBusy: false,
ready: false,
inputHint: 'Enter your text...',
messageInput: '',
reconnectInterval: 3000,
@@ -352,7 +359,8 @@ export default {
}
if (data.type == "client_status" || data.type == "agent_status") {
if (this.configurationRequired()) {
this.ready = !this.configurationRequired();
if (!this.ready) {
this.setNavigation('settings');
}
return;
@@ -558,10 +566,6 @@ export default {
</script>
<style scoped>
.message.request_input {
}
.backdrop {
background-image: url('/src/assets/logo-13.1-backdrop.png');
background-repeat: no-repeat;

View File

@@ -0,0 +1,224 @@
<template>
<v-chip v-if="newImages" color="info" class="text-caption" label transition="scroll-x-reverse-transition">New Images</v-chip>
<v-app-bar-nav-icon v-if="images.length > 0" @click="open">
<v-icon>mdi-image-multiple-outline</v-icon>
<v-icon v-if="newImages" class="btn-notification" color="info">mdi-alert-circle</v-icon>
</v-app-bar-nav-icon>
<v-dialog v-model="dialog" max-width="920" height="920">
<v-card>
<v-card-title>
Visual queue
<span v-if="generating">
<v-progress-circular class="ml-1 mr-3" size="14" indeterminate="disable-shrink" color="primary">
</v-progress-circular>
<span class="text-caption text-primary">Generating...</span>
</span>
</v-card-title>
<v-toolbar density="compact" color="grey-darken-4">
<v-btn rounded="sm" @click="deleteAll()" prepend-icon="mdi-close-box-outline">Discard All</v-btn>
<v-spacer></v-spacer>
<span v-if="selectedImage != null">
<v-btn :disabled="generating" rounded="sm" @click="regenerateImage()" prepend-icon="mdi-refresh">Regenerate</v-btn>
<v-btn rounded="sm" @click="deleteImage()" prepend-icon="mdi-close-box-outline">Discard</v-btn>
</span>
</v-toolbar>
<v-divider></v-divider>
<v-card-text>
<v-row>
<v-col cols="2" class="overflow-content">
<v-img v-for="(image, idx) in images" elevation="7" :src="imageSource(image.base64)" :key="idx" @click.stop="selectImage(idx)" class="img-thumb"></v-img>
</v-col>
<v-col cols="10" class="overflow-content">
<v-row v-if="selectedImage != null">
<v-col :cols="selectedImage.context.format === 'portrait' ? 7 : 12">
<v-img max-height="800" :src="imageSource(selectedImage.base64)" :class="imagePreviewClass()"></v-img>
</v-col>
<v-col :cols="selectedImage.context.format === 'portrait' ? 5 : 12">
<v-card elevation="7" density="compact">
<v-card-text>
<v-alert density="compact" v-if="selectedImage.context.vis_type" icon="mdi-panorama-variant-outline" variant="text" color="grey">
{{ selectedImage.context.vis_type }}
</v-alert>
<v-alert density="compact" v-if="selectedImage.context.prepared_prompt" icon="mdi-script-text-outline" variant="text" color="grey">
<v-row>
<v-col :cols="selectedImage.context.format === 'portrait' ? 12 : 4">
<v-tooltip :text="selectedImage.context.prompt" class="pre-wrap" max-width="400">
<template v-slot:activator="{ props }">
<span class="text-underline text-info" v-bind="props">Initial prompt</span>
</template>
</v-tooltip>
</v-col>
<v-col :cols="selectedImage.context.format === 'portrait' ? 12 : 4">
<v-tooltip :text="selectedImage.context.prepared_prompt" class="pre-wrap" max-width="400">
<template v-slot:activator="{ props }">
<span class="text-underline text-info" v-bind="props">Prepared prompt</span>
</template>
</v-tooltip>
</v-col>
</v-row>
</v-alert>
<v-alert density="compact" v-if="selectedImage.context.character_name" icon="mdi-account" variant="text" color="grey">
{{ selectedImage.context.character_name }}
</v-alert>
<v-alert density="compact" v-if="selectedImage.context.instructions" icon="mdi-comment-text" variant="text" color="grey">
{{ selectedImage.context.instructions }}
</v-alert>
<div v-if="selectedImage.context.vis_type === 'CHARACTER'">
<!-- character actions -->
<v-btn color="primary" variant="text" prepend-icon="mdi-image-frame" @click.stop="setCharacterCoverImage()">
Set as cover image
</v-btn>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'VisualQueue',
inject: ['requestAssets', 'getWebsocket', 'registerMessageHandler'],
data() {
return {
selectedImage: null,
dialog: false,
images: [],
newImages: false,
selectOnGenerate: false,
generating: false,
}
},
emits: ["new-image"],
methods: {
deleteImage() {
let index = this.images.indexOf(this.selectedImage);
this.images.splice(index, 1);
if(this.images.length > 0) {
this.selectedImage = this.images[0];
} else {
this.selectedImage = null;
this.dialog = false;
}
},
deleteAll() {
this.images = [];
this.selectedImage = null;
this.dialog = false;
},
setCharacterCoverImage() {
this.getWebsocket().send(JSON.stringify({
"type": "visual",
"action": "cover_image",
"base64": "data:image/png;base64,"+this.selectedImage.base64,
"context": this.selectedImage.context,
}));
},
regenerateImage() {
this.getWebsocket().send(JSON.stringify({
"type": "visual",
"action": "regenerate",
"context": this.selectedImage.context,
}));
this.selectOnGenerate = true;
},
imagePreviewClass() {
return this.selectedImage.context.format === 'portrait' ? 'img-preview-portrait' : 'img-preview-wide';
},
selectImage(index) {
this.selectedImage = this.images[index];
},
imageSource(base64) {
return "data:image/png;base64,"+base64;
},
open() {
this.dialog = true;
this.newImages = false;
},
handleMessage(message) {
if(message.type == "image_generated") {
let image = {
"base64": message.data.base64,
"context": message.data.context,
}
this.images.unshift(image);
this.newImages = true;
this.$emit("new-image", image);
if(this.selectedImage == null || this.selectOnGenerate) {
this.selectedImage = image;
this.selectOnGenerate = false;
}
console.log("Received image", image);
} else if(message.type === "agent_status" && message.name === "visual") {
this.generating = message.status === "busy_bg" || message.status === "busy";
}
},
},
created() {
this.registerMessageHandler(this.handleMessage);
}
}
</script>
<style scoped>
.img-thumb {
cursor: pointer;
margin: 5px;
width: 100%;
height: auto;
}
.img-preview-portrait {
width: 100%;
height: auto;
margin: 5px;
}
.img-preview-wide {
width: 100%;
height: auto;
margin: 5px;
}
.overflow-content {
overflow-y: auto;
overflow-x: hidden;
min-height: 700px;
max-height: 850px;
}
.text-underline {
text-decoration: underline;
}
.pre-wrap {
white-space: pre-wrap;
}
.btn-notification {
position: absolute;
top: 0px;
right: 0px;
font-size: 15px;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
}
</style>