mirror of
https://github.com/vegu-ai/talemate.git
synced 2025-12-28 16:06:38 +01:00
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:
41
talemate_frontend/package-lock.json
generated
41
talemate_frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
224
talemate_frontend/src/components/VisualQueue.vue
Normal file
224
talemate_frontend/src/components/VisualQueue.vue
Normal 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>
|
||||
Reference in New Issue
Block a user