talemate/talemate_frontend/src/components/SceneMessages.vue
veguAI 83027b3a0f
0.23.0 (#91)
* dockerfiles and docker-compose

* containerization fixes

* docker instructions

* readme

* readme

* dont mount src by default, readme

* hf template determine fixes

* auto determine prompt template

* script to start talemate listening only to 127.0.0.1

* prompt tweaks

* auto narrate round every 3 rounds

* tweaks

* Add return to startscreen button

* Only show return to start screen button if scene is active

* improvements to character creation

* dedicated property for scene title separate fromn the save directory name

* filter out negations into negative keywords

* increase auto narrate delay

* add character portrait keyword

* summarization should ignore most recent message, as it is often regenerated.

* cohere client

* specify python3

* improve viable runpod text gen detection

* fix formatting in template preview

* cohere command-r plus template that i am not sure if correct or not

* mistral client set to decensor

* fix issue with parsing json responses

* command-r prompts updated

* use official mistralai python client

* send max_tokens

* new input autocomplete functionality

* prompt tweeaks

* llama 3 templates

* add <|eot_id|> to stopping strings

* prompt tweak

* tooltip

* llama-3 identifier

* command-r and command-r plus prompt identifiers

* text-gen-webui client tweaks to make llama3 eos tokens work correctly

* better llama-3 detection

* better llama-3 finalizing of parameters

* streamline client prompt finalizers
reduce YY model smoothing factor from 0.3 to 0.1 for text-generation-webui client

* relock

* linting

* set 0.23.0

* add new gpt-4 models

* set 0.23.0

* add note about conecting to text-gen-webui from docker

* fix openai image generation no longer working

* default to concept_art
2024-04-20 01:01:06 +03:00

310 lines
No EOL
11 KiB
Vue

<template>
<div class="message-container" ref="messageContainer" style="flex-grow: 1; overflow-y: auto;">
<div v-for="(message, index) in messages" :key="index">
<div v-if="message.type === 'character' || message.type === 'processing_input'"
:class="`message ${message.type}`" :id="`message-${message.id}`" :style="{ borderColor: message.color }">
<div class="character-message">
<CharacterMessage :character="message.character" :text="message.text" :color="message.color" :message_id="message.id" />
</div>
</div>
<div v-else-if="message.type === 'request_input' && message.choices">
<v-alert variant="tonal" type="info" class="system-message mb-3">
{{ message.text }}
</v-alert>
<div>
<v-radio-group inline class="radio-group" v-if="!message.multiSelect" v-model="message.selectedChoices" :disabled="message.sent">
<div v-for="(choice, index) in message.choices" :key="index">
<v-radio :key="index" :label="choice" :value="choice"></v-radio>
</div>
</v-radio-group>
<div v-else class="choice-buttons">
<div v-for="(choice, index) in message.choices" :key="index">
<v-checkbox :label="choice" v-model="message.selectedChoices" :value="choice" :disabled="message.sent"></v-checkbox>
</div>
</div>
<div class="mb-3">
<v-btn v-if="!message.sent" @click="sendAllChoices(message)" color="secondary" :disabled="message.sent">Continue</v-btn>
</div>
</div>
</div>
<v-alert v-else-if="message.type === 'system'" variant="text" closable :type="message.status || 'info'" class="system-message mb-3 text-caption"
:text="message.text">
</v-alert>
<div v-else-if="message.type === 'status'" :class="`message ${message.type}`">
<div class="narrator-message">
<StatusMessage :text="message.text" :status="message.status" />
</div>
</div>
<div v-else-if="message.type === 'narrator'" :class="`message ${message.type}`">
<div class="narrator-message" :id="`message-${message.id}`">
<NarratorMessage :text="message.text" :message_id="message.id" />
</div>
</div>
<div v-else-if="message.type === 'director'" :class="`message ${message.type}`">
<div class="director-message" :id="`message-${message.id}`">
<DirectorMessage :text="message.text" :message_id="message.id" :character="message.character" :direction_mode="message.direction_mode" :action="message.action"/>
</div>
</div>
<div v-else-if="message.type === 'time'" :class="`message ${message.type}`">
<div class="time-message" :id="`message-${message.id}`">
<TimePassageMessage :text="message.text" :message_id="message.id" :ts="message.ts" />
</div>
</div>
<div v-else :class="`message ${message.type}`">
{{ message.text }}
</div>
</div>
</div>
</template>
<script>
import CharacterMessage from './CharacterMessage.vue';
import NarratorMessage from './NarratorMessage.vue';
import DirectorMessage from './DirectorMessage.vue';
import TimePassageMessage from './TimePassageMessage.vue';
import StatusMessage from './StatusMessage.vue';
export default {
name: 'SceneMessages',
components: {
CharacterMessage,
NarratorMessage,
DirectorMessage,
TimePassageMessage,
StatusMessage,
},
data() {
return {
messages: [],
}
},
inject: ['getWebsocket', 'registerMessageHandler', 'setWaitingForInput'],
provide() {
return {
requestDeleteMessage: this.requestDeleteMessage,
createPin: this.createPin,
}
},
methods: {
createPin(message_id){
this.getWebsocket().send(JSON.stringify({ type: 'interact', text:'!ws_sap:'+message_id}));
},
requestDeleteMessage(message_id) {
this.getWebsocket().send(JSON.stringify({ type: 'delete_message', id: message_id }));
},
handleChoiceInput(data) {
// Create a new message with buttons for the choices
const message = {
id: data.id,
type: data.type,
text: data.message,
choices: data.data.choices,
selectedChoices: data.data.default || (data.data.multi_select ? [] : null),
multiSelect: data.data.multi_select,
color: data.color,
sent: false,
ts: data.ts,
};
this.messages.push(message);
},
sendChoice(message, choice) {
const index = message.selectedChoices.indexOf(choice);
if (index === -1) {
// If the checkbox is checked, add the choice to the selectedChoices array
message.selectedChoices.push(choice);
} else {
// If the checkbox is unchecked, remove the choice from the selectedChoices array
message.selectedChoices.splice(index, 1);
}
},
sendAllChoices(message) {
let text;
if(message.multiSelect) {
text = message.selectedChoices.join(', ');
} else {
text = message.selectedChoices;
}
// Send all selected choices to the server
this.getWebsocket().send(JSON.stringify({ type: 'interact', text: text }));
// Clear the selectedChoices array
message.sent = true;
this.setWaitingForInput(false);
},
messageTypeIsSceneMessage(type) {
return ![
'request_input',
'client_status',
'agent_status',
'status',
'autocomplete_suggestion'
].includes(type);
},
handleMessage(data) {
var i;
if (data.type == "clear_screen") {
this.messages = [];
}
if (data.type == "remove_message") {
// find message where type == "character" and id == data.id
// remove that message from the array
let newMessages = [];
for (i = 0; i < this.messages.length; i++) {
if (this.messages[i].id != data.id) {
newMessages.push(this.messages[i]);
}
}
this.messages = newMessages;
return
}
if (data.type == "message_edited") {
// find the message by id and update the text#
for (i = 0; i < this.messages.length; i++) {
if (this.messages[i].id == data.id) {
console.log("message_edited!", i , data.id, data.message, this.messages[i].type, data)
if (this.messages[i].type == "character") {
this.messages[i].text = data.message.split(':')[1].trim();
} else {
this.messages[i].text = data.message;
}
break;
}
}
return
}
if (data.message) {
if (data.type === 'character') {
const parts = data.message.split(':');
const character = parts.shift();
const text = parts.join(':');
this.messages.push({ id: data.id, type: data.type, character: character.trim(), text: text.trim(), color: data.color }); // Add color property to the message
} else if (data.type === 'director') {
this.messages.push(
{
id: data.id,
type: data.type,
character: data.character,
text: data.message, direction_mode: data.direction_mode,
action: data.action
}
);
} else if (this.messageTypeIsSceneMessage(data.type)) {
this.messages.push({ id: data.id, type: data.type, text: data.message, color: data.color, character: data.character, status:data.status, ts:data.ts }); // Add color property to the message
} else if (data.type === 'status' && data.data && data.data.as_scene_message === true) {
// status message can only exist once, remove the most recent one (if within the last 100 messages)
// by walking the array backwards then removing the first one found
// then add the new status message
let max = 100;
let iter = 0;
for (i = this.messages.length - 1; i >= 0; i--) {
if (this.messages[i].type == 'status') {
this.messages.splice(i, 1);
break;
}
iter++;
if(iter > max) {
break;
}
}
this.messages.push({
id: data.id,
type: data.type,
text: data.message,
status: data.status,
ts: data.ts
});
}
}
}
},
created() {
this.registerMessageHandler(this.handleMessage);
},
}
</script>
<style scoped>
.message-container {
overflow-y: auto;
}
.message {
padding: 10px;
white-space: pre-wrap;
margin-bottom: 10px;
}
.message.system {
color: #FFA726;
}
.message.narrator {
color: #26A69A;
}
.message.character {
color: #E0E0E0;
}
.character-message {
display: flex;
flex-direction: row;
}
.character-name {
font-weight: bold;
margin-right: 10px;
}
.character-avatar {
height: 50px;
margin-top: 10px;
}
.hotbuttons-section {
display: flex;
justify-content: flex-start;
margin-bottom: 10px;
}
.hotbuttons-section-1,
.hotbuttons-section-2,
.hotbuttons-section-3 {
display: flex;
align-items: center;
margin-right: 20px;
}
.choice-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.message.request_input {}
</style>