lollms-webui/web/src/views/PlayGroundView.vue

333 lines
13 KiB
Vue
Raw Normal View History

2023-08-19 01:28:40 +02:00
<template>
2023-08-19 19:26:15 +02:00
<div class="container bg-bg-light dark:bg-bg-dark shadow-lg overflow-y-auto scrollbar-thin scrollbar-track-bg-light-tone scrollbar-thumb-bg-light-tone-panel hover:scrollbar-thumb-primary dark:scrollbar-track-bg-dark-tone dark:scrollbar-thumb-bg-dark-tone-panel dark:hover:scrollbar-thumb-primary active:scrollbar-thumb-secondary">
2023-08-20 00:12:08 +02:00
<div class="container flex flex-row m-2">
2023-08-19 19:26:15 +02:00
<div class="flex-grow m-2">
2023-08-20 00:12:08 +02:00
<div class="flex-grow m-2 p-2 border border-blue-300 rounded-md border-2 border-blue-300 m-2 p-4">
2023-08-19 01:28:40 +02:00
<label class="mt-2">Presets</label>
2023-08-20 00:12:08 +02:00
<select v-model="selectedPreset" class="m-2 border-2 rounded-md shadow-sm w-full">
2023-08-19 01:28:40 +02:00
<option v-for="preset in Object.keys(presets)" :key="preset" :value="preset">
{{ preset }}
</option>
</select>
2023-08-19 03:25:36 +02:00
<button class="bg-green-500 hover:bg-green-600 active:bg-red-700 text-white font-bold py-2 px-4 rounded ml-2 " @click="setPreset" title="Use preset"><i data-feather="check"></i></button>
<button class="bg-green-500 hover:bg-green-600 active:bg-red-700 text-white font-bold py-2 px-4 rounded ml-2 " @click="addPreset" title="Add this text as a preset"><i data-feather="plus"></i></button>
<button class="bg-red-500 hover:bg-red-600 active:bg-red-700 text-white font-bold py-2 px-4 rounded ml-2" @click="removePreset" title="Remove preset"><i data-feather="x"></i></button>
<button class="bg-green-500 hover:bg-green-600 active:bg-red-700 text-white font-bold py-2 px-4 rounded ml-2" @click="savePreset" title="Save presets list"><i data-feather="save"></i></button>
<button class="bg-green-500 hover:bg-green-600 active:bg-red-700 text-white font-bold py-2 px-4 rounded ml-2" @click="reloadPresets" title="Reload presets list"><i data-feather="refresh-ccw"></i></button>
2023-08-19 01:28:40 +02:00
</div>
2023-08-19 19:26:15 +02:00
<div class="flex-grow m-2 p-2 border border-blue-300 rounded-md border-2 border-blue-300 m-2 p-4">
2023-08-20 00:12:08 +02:00
<textarea v-model="text" id="text_element" class="mt-4 h-64 overflow-y-scroll w-full dark:bg-bg-dark scrollbar-thin scrollbar-track-bg-light-tone scrollbar-thumb-bg-light-tone-panel hover:scrollbar-thumb-primary dark:scrollbar-track-bg-dark-tone dark:scrollbar-thumb-bg-dark-tone-panel dark:hover:scrollbar-thumb-primary active:scrollbar-thumb-secondary" type="text"></textarea>
<span>Cursor position {{ cursorPosition }}</span>
2023-08-19 01:28:40 +02:00
</div>
2023-08-19 03:25:36 +02:00
<div class="flex justify-between">
<div class="m-0">
<button v-show="!generating" id="generate-button" @click="generate" class="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white font-bold py-2 px-4 rounded ml-2">Generate Text</button>
<button v-show="generating" id="stop-button" @click="stopGeneration" class="bg-red-500 hover:bg-red-600 active:bg-red-700 text-white font-bold py-2 px-4 rounded ml-2 ">Stop Generation</button>
<button v-show="!generating" id="export-button" @click="exportText" class="bg-green-500 hover:bg-green-600 active:bg-green-700 text-white font-bold py-2 px-4 rounded ml-2"><i data-feather="upload"></i></button>
<button v-show="!generating" id="import-button" @click="importText" class="bg-green-500 hover:bg-green-600 active:bg-green-700 text-white font-bold py-2 px-4 rounded ml-2"><i data-feather="download"></i></button>
<input type="file" id="import-input" class="hidden">
2023-08-19 03:25:36 +02:00
</div>
</div>
2023-08-19 19:26:15 +02:00
<div class="flex-grow m-2 p-2 border border-blue-300 rounded-md border-2 border-blue-300 m-2 p-4">
<MarkdownRenderer ref="mdRender" :markdown-text="text" class="dark:bg-bg-dark">
</MarkdownRenderer>
</div>
2023-08-19 01:28:40 +02:00
</div>
2023-08-20 00:12:08 +02:00
<div id="settings" class="border border-blue-300 bg-blue-200 mt-4 w-25 mr-2 h-full mb-10 min-w-500" style="align-items: center; height: fit-content; margin: 10px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); border-radius: 4px;">
2023-08-19 19:26:15 +02:00
<div id="title" class="border border-blue-600 bg-blue-300 m-0 flex justify-center items-center box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) border-radius: 4px;">
2023-08-19 01:28:40 +02:00
<h3 class="text-gray-600 mb-4 text-center m-0">Settings</h3>
2023-08-19 19:26:15 +02:00
</div>
2023-08-19 01:28:40 +02:00
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Temperature</h3>
<input type="range" v-model="temperature" min="0" max="5" step="0.1" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ temperature }}</span>
</div>
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Top K</h3>
<input type="range" v-model="top_k" min="1" max="100" step="1" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ top_k }}</span>
</div>
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Top P</h3>
<input type="range" v-model="top_p" min="0" max="1" step="0.1" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ top_p }}</span>
</div>
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Repeat Penalty</h3>
<input type="range" v-model="repeat_penalty" min="0" max="5" step="0.1" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ repeat_penalty }}</span>
</div>
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Repeat Last N</h3>
<input type="range" v-model="repeat_last_n" min="0" max="100" step="1" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ repeat_last_n }}</span>
</div>
2023-08-19 14:06:24 +02:00
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Number of tokens to crop the text to</h3>
<input type="number" v-model="n_crop" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ n_crop }}</span>
</div>
2023-08-19 03:25:36 +02:00
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Number of tokens to generate</h3>
<input type="number" v-model="n_predicts" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ n_predicts }}</span>
</div>
2023-08-19 01:28:40 +02:00
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Seed</h3>
<input type="number" v-model="seed" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ seed }}</span>
</div>
2023-08-19 19:26:15 +02:00
</div>
2023-08-19 01:28:40 +02:00
</div>
</div>
<Toast ref="toast"/>
</template>
<script>
2023-08-19 03:25:36 +02:00
import feather from 'feather-icons'
2023-08-19 01:28:40 +02:00
import axios from "axios";
import socket from '@/services/websocket.js'
import Toast from '../components/Toast.vue'
2023-08-19 19:26:15 +02:00
import MarkdownRenderer from '../components/MarkdownRenderer.vue';
2023-08-19 01:28:40 +02:00
export default {
name: 'PlayGroundView',
data() {
return {
generating:false,
presets:{},
2023-08-20 00:12:08 +02:00
selectedPreset: '',
cursorPosition:0,
2023-08-19 01:28:40 +02:00
text:"",
2023-08-20 00:12:08 +02:00
pre_text:"",
post_text:"",
2023-08-19 01:28:40 +02:00
temperature: 0.1,
top_k: 50,
top_p: 0.9,
repeat_penalty: 1.3,
repeat_last_n: 50,
2023-08-19 14:06:24 +02:00
n_crop: -1,
2023-08-19 03:25:36 +02:00
n_predicts: 2000,
2023-08-19 01:28:40 +02:00
seed: -1,
};
},
components:{
Toast,
2023-08-19 19:26:15 +02:00
MarkdownRenderer
2023-08-19 01:28:40 +02:00
},
2023-08-19 03:25:36 +02:00
mounted() {
2023-08-20 00:12:08 +02:00
const text_element = document.getElementById('text_element');
2023-08-20 21:31:51 +02:00
text_element.addEventListener('input', () => {
2023-08-20 22:21:46 +02:00
this.cursorPosition = text_element.selectionStart+1;
2023-08-20 00:12:08 +02:00
});
text_element.addEventListener('click', () => {
this.cursorPosition = text_element.selectionStart;
});
axios.get('./presets.json').then(response => {
2023-08-19 01:28:40 +02:00
console.log(response.data)
this.presets=response.data
2023-08-19 03:25:36 +02:00
}).catch(ex=>{
this.$refs.toast.showToast(`Error: ${ex}`,4,false)
2023-08-19 01:28:40 +02:00
});
// Event handler for receiving generated text chunks
socket.on('text_chunk', data => {
this.appendToOutput(data.chunk);
2023-08-20 00:12:08 +02:00
const text_element = document.getElementById('text_element');
text_element.scrollTo(0, text_element.scrollHeight);
2023-08-19 01:28:40 +02:00
});
// Event handler for receiving generated text chunks
socket.on('text_generated', data => {
// Toggle button visibility
this.generating=false;
});
socket.on('generation_error', data => {
console.log('generation_error:', data);
this.$refs.toast.showToast(`Error: ${data}`,4,false)
// Toggle button visibility
this.generating=false;
});
// Event handler for successful connection
socket.on('connect', () => {
console.log('Connected to LoLLMs server');
this.$store.state.isConnected=true;
this.generating=false
});
// Event handler for error during text generation
socket.on('buzzy', error => {
console.error('Server is busy. Wait for your turn', error);
this.$refs.toast.showToast(`Error: ${error.message}`,4,false)
// Toggle button visibility
this.generating=false
});
// Event handler for error during text generation
socket.on('generation_canceled', error => {
// Toggle button visibility
this.generating=false
console.log("Generation canceled OK")
});
2023-08-20 00:12:08 +02:00
//console.log('chatbox mnt',this.$refs)
this.$nextTick(() => {
feather.replace();
});
},
created(){
2023-08-19 01:28:40 +02:00
},
methods:{
2023-08-20 00:12:08 +02:00
getCursorPosition() {
return this.cursorPosition;
},
2023-08-19 01:28:40 +02:00
appendToOutput(chunk){
2023-08-20 00:12:08 +02:00
this.pre_text += chunk
this.text = this.pre_text + this.post_text
2023-08-19 01:28:40 +02:00
},
generate(){
2023-08-20 00:12:08 +02:00
console.log("Finding cursor position")
this.pre_text = this.text.substring(0,this.getCursorPosition())
this.post_text = this.text.substring(this.getCursorPosition(), this.text.length)
var prompt = this.text.substring(0,this.getCursorPosition())
2023-08-19 01:28:40 +02:00
console.log(prompt)
// Trigger the 'generate_text' event with the prompt
2023-08-19 14:06:24 +02:00
socket.emit('generate_text', { prompt: prompt, personality: -1, n_predicts: this.n_predicts , n_crop: this.n_crop,
2023-08-19 01:28:40 +02:00
parameters: {
temperature: this.temperature,
top_k: this.top_k,
top_p: this.top_p,
repeat_penalty: this.repeat_penalty, // Update with desired repeat penalty value
repeat_last_n: this.repeat_last_n, // Update with desired repeat_last_n value
seed: parseInt(this.seed)
}});
// Toggle button visibility
this.generating=true
},
stopGeneration(){
// Trigger the 'cancel_generation' event
2023-08-19 14:06:24 +02:00
socket.emit('cancel_text_generation',{});
2023-08-19 01:28:40 +02:00
},
exportText(){
2023-08-19 03:25:36 +02:00
const textToExport = this.text;
const element = document.createElement('a');
const file = new Blob([textToExport], {type: 'text/plain'});
element.href = URL.createObjectURL(file);
element.download = 'exported_text.txt';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
2023-08-19 01:28:40 +02:00
},
importText() {
const inputFile = document.getElementById("import-input");
if (!inputFile) return; // If the element doesn't exist, do nothing
inputFile.addEventListener("change", event => {
if (event.target.files && event.target.files[0]) {
const reader = new FileReader();
reader.onload = () => {
this.text = reader.result;
};
reader.readAsText(event.target.files[0]);
} else {
alert("Please select a file.");
}
});
inputFile.click();
},
2023-08-19 01:28:40 +02:00
setPreset() {
this.text = this.presets[this.selectedPreset];
},
addPreset() {
let title = prompt('Enter the title of the preset:');
this.presets[title] = this.text
},
removePreset() {
if (this.selectedPreset) {
delete this.presets[this.selectedPreset];
}
},
savePreset() {
2023-08-19 03:25:36 +02:00
axios.post("/save_presets", this.presets).then((response) => {
2023-08-19 01:28:40 +02:00
console.log(response);
2023-08-19 03:25:36 +02:00
this.$refs.toast.showToast(`Presets saved`,4,true)
2023-08-19 01:28:40 +02:00
});
2023-08-19 03:25:36 +02:00
},
reloadPresets() {
axios.get('./presets.json').then(response => {
console.log(response.data)
this.presets=response.data
}).catch(ex=>{
this.$refs.toast.showToast(`Error: ${ex}`,4,false)
});
},
2023-08-19 01:28:40 +02:00
}
};
</script>
<style>
select {
width: 200px;
}
body {
background-color: #fafafa;
font-family: sans-serif;
}
.container {
margin: 4px auto;
width: 800px;
}
.settings {
position: fixed;
top: 0;
right: 0;
width: 250px;
background-color: #fff;
z-index: 1000;
display: none;
}
.settings-button {
cursor: pointer;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
color: #333;
font-size: 14px;
}
.settings-button:hover {
background-color: #eee;
}
.settings-button:active {
background-color: #ddd;
}
.slider-container {
margin-top: 20px;
}
.slider-value {
display: inline-block;
margin-left: 10px;
color: #6b7280;
font-size: 14px;
}
.small-button {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
</style>