Develop scenario/scenario executions chat

This commit is contained in:
2025-03-18 17:11:13 +01:00
parent 38b21032e7
commit 49b0863a60
6 changed files with 737 additions and 745 deletions

View File

@@ -1,107 +1,59 @@
<template>
<div class="chat-wrapper p-p-3">
<div class="p-d-flex p-flex-column" style="height: 100%;">
<div class="chat-messages" ref="messagesContainer">
<div v-for="(msg, index) in messages" :key="index" :class="['chat-message', msg.sender]">
<div class="message-bubble">
<div v-if="msg.sender === 'bot'">
<MdPreview class="editor" theme="light" previewTheme="github" v-model="msg.text" language="en-US" :key="index" />
</div>
<p v-else>{{ msg.text }}</p>
<div class="chat-wrapper p-p-3">
<div class="p-d-flex p-flex-column" style="height: 100%">
<div class="chat-messages" ref="messagesContainer">
<div v-for="(msg, index) in messages" :key="index" :class="['chat-message', msg.sender]">
<div class="message-bubble">
<div v-if="msg.sender === 'bot'">
<MdPreview class="editor" theme="light" previewTheme="github" v-model="msg.text" language="en-US" :key="index" />
</div>
<p v-else>{{ msg.text }}</p>
</div>
</div>
</div>
</div>
</div>
<p-inputGroup class="p-mt-2" style="width: 100%;">
<p-inputText
v-model="message"
placeholder="Ask anything..."
@keyup.enter="sendMessage"
/>
<p-button
label="Send"
icon="pi pi-send"
class="p-button-primary"
@click="sendMessage"
/>
<p-button
label="Reset"
icon="pi pi-trash"
@click="clearHistory"
class=" p-button-danger"
/>
<p-button
icon="pi pi-cog"
@click="showSettings = !showSettings"
class=" p-button-normal"
/>
</p-inputGroup>
</div>
<p-inputGroup class="p-mt-2" style="width: 100%">
<p-inputText v-model="message" placeholder="Ask anything..." @keyup.enter="sendMessage" />
<!-- CARD DELLE IMPOSTAZIONI -->
<p-card v-if="showSettings" class="chat-settings-card p-p-2 p-mt-3">
<template #title>
<div class="p-d-flex p-ai-center">
<i class="pi pi-cog p-mr-2"></i>
<span>Chat Settings</span>
<p-button label="Ask" icon="pi pi-send" class="p-button-primary" @click="sendMessage" />
<p-button label="Clear" icon="pi pi-trash" @click="clearHistory" class="p-button-danger" />
<!-- <p-button icon="pi pi-cog" @click="showSettings = !showSettings" class="p-button-normal" /> -->
</p-inputGroup>
</div>
</template>
<!-- Checkboxes e campi di testo -->
<template #content>
<p-inputGroup class="p-mt-2" style="width: 100%;">
<div class="p-field-checkbox p-mr-3">
<p-checkbox v-model="useDocumentation" inputId="documentation" binary="true" />
<label for="documentation">Documentation</label>
</div>
<div class="p-field-checkbox p-mr-3">
<p-checkbox v-model="useSource" inputId="source" binary="true" />
<label for="source">Source</label>
</div>
<p-inputText
v-model="scenarioExecutionId"
placeholder="Enter executionId..."
class="p-mr-2"
/>
<p-button
label="Load Context"
icon="pi pi-upload"
@click="loadContext"
class="p-button-outlined p-button-sm"
/>
<p-inputText disabled
v-model="conversationId"
placeholder="Enter conversation ID..."
class="p-mr-2"
/>
<p-inputText disabled
v-model="project"
placeholder="Project"
class="p-mr-2"
/>
<p-inputText disabled
v-model="application"
placeholder="Application"
class="p-mr-2"
/>
</p-inputGroup>
<!-- CARD DELLE IMPOSTAZIONI -->
<!-- <p-card v-if="showSettings" class="chat-settings-card p-p-2 p-mt-3">
<template #title>
<div class="p-d-flex p-ai-center">
<i class="pi pi-cog p-mr-2"></i>
<span>Chat Settings</span>
</div>
</template>
</template>
</p-card>
</div>
Checkboxes e campi di testo
<template #content>
<p-inputGroup class="p-mt-2" style="width: 100%">
<div class="p-field-checkbox p-mr-3">
<p-checkbox v-model="useDocumentation" inputId="documentation" binary="true" />
<label for="documentation">Documentation</label>
</div>
<div class="p-field-checkbox p-mr-3">
<p-checkbox v-model="useSource" inputId="source" binary="true" />
<label for="source">Source</label>
</div>
<p-inputText v-model="scenarioExecutionId" placeholder="Enter executionId..." class="p-mr-2" />
<p-button label="Load Context" icon="pi pi-upload" @click="loadContext" class="p-button-outlined p-button-sm" />
<p-inputText disabled v-model="conversationId" placeholder="Enter conversation ID..." class="p-mr-2" />
<p-inputText disabled v-model="project" placeholder="Project" class="p-mr-2" />
<p-inputText disabled v-model="application" placeholder="Application" class="p-mr-2" />
</p-inputGroup>
</template>
</p-card> -->
</div>
</template>
<script>
import { marked } from "marked";
import { marked } from 'marked';
import { MdPreview } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import Button from 'primevue/button';
@@ -115,277 +67,281 @@ import ScrollPanel from 'primevue/scrollpanel';
import { UserPrefStore } from '../stores/UserPrefStore.js';
const userPrefStore = UserPrefStore();
export default {
name: "ChatGPTInterface",
export default {
name: 'ChatGPTInterface',
components: {
"p-scrollPanel": ScrollPanel,
"p-inputText": InputText,
"p-button": Button,
"p-checkbox": Checkbox,
"p-card": Card,
"p-inputGroup": InputGroup,
"p-inputGroupAddon": InputGroupAddon,
MdPreview
'p-scrollPanel': ScrollPanel,
'p-inputText': InputText,
'p-button': Button,
'p-checkbox': Checkbox,
'p-card': Card,
'p-inputGroup': InputGroup,
'p-inputGroupAddon': InputGroupAddon,
MdPreview
},
props: {
scenarioExecutionId: {
type: String,
default: ''
}
},
data() {
return {
conversationId: userPrefStore.user.id, // Default conversation ID
message: "",
messages: [],
useDocumentation: true,
useSource: true,
project: userPrefStore.user.selectedProject.internal_name,
application: userPrefStore.getSelApp ? userPrefStore.getSelApp.internal_name : "",
scenarioExecutionId:"",
showSettings: false,
authorization:"Bearer " + this.$auth.token(),
waitingData: false
};
return {
conversationId: `${userPrefStore.user.id}-${userPrefStore.user.selectedProject.internal_name}`,
message: '',
messages: [],
useDocumentation: true,
useSource: true,
project: userPrefStore.user.selectedProject.internal_name,
application: userPrefStore.getSelApp ? userPrefStore.getSelApp.internal_name : '',
scenarioExecutionId: this.scenarioExecutionId,
showSettings: false,
authorization: 'Bearer ' + this.$auth.token(),
waitingData: false,
previousMessagesLength: 0
};
},
//
mounted() {
console.log("mounted");
console.log("userPrefStore", userPrefStore);
this.fetchChatHistory();
console.log('userPrefStore', userPrefStore);
this.updateConversationId();
},
methods: {
async fetchChatHistory() {
if (!this.conversationId.trim()) {
console.warn("No conversation ID set.");
return;
}
try {
const response = await fetch(`http://olympus-api-gateway-aks.olympusai.live/chatservice/get-history?conversationId=${this.conversationId}&lastN=100`, {
method: "GET",
headers: { "Content-Type": "application/json",
"authorization": "Bearer " + this.$auth.token()}
},
);
if (!response.ok) throw new Error("Failed to fetch chat history");
const history = await response.json();
// Convert API format to frontend format
this.messages = [];
history.forEach(msg => {
console.log("msg", msg);
if (msg.messageType != "SYSTEM") {
this.messages.push ({
sender : msg.messageType === "USER" ? "user" : 'bot',
text : msg.text
})
async updateConversationId() {
this.conversationId = this.scenarioExecutionId
? `${userPrefStore.user.id}-${this.scenarioExecutionId}`
: `${userPrefStore.user.id}-${userPrefStore.user.selectedProject.internal_name}`;
await this.fetchChatHistory();
if (this.scenarioExecutionId && this.messages.length === 0) {
this.loadContext();
}
});
console.log("messages", this.messages);
},
this.scrollToBottom();
} catch (error) {
console.error("Error loading chat history:", error);
}
},
async fetchChatHistory() {
if (!this.conversationId.trim()) {
console.warn('No conversation ID set.');
return;
}
try {
const response = await fetch(`http://olympus-api-gateway-aks.olympusai.live/chatservice/get-history?conversationId=${this.conversationId}&lastN=100`, {
method: 'GET',
headers: { 'Content-Type': 'application/json', authorization: 'Bearer ' + this.$auth.token() }
});
if (!response.ok) throw new Error('Failed to fetch chat history');
async sendMessage() {
if (this.message.trim() === "" || !this.conversationId.trim()) return;
const history = await response.json();
this.messages.push({ sender: "user", text: this.message });
const botMessage = { sender: "bot", text: "" };
this.messages.push(botMessage);
this.scrollToBottom();
const payload = {
message: this.message,
conversationId: this.conversationId,
useDocumentation: this.useDocumentation,
useSource: this.useSource,
project: this.project,
application: this.application
};
// Convert API format to frontend format
this.messages = [];
this.message = "";
history.forEach((msg) => {
if (msg.messageType != 'SYSTEM') {
this.messages.push({
sender: msg.messageType === 'USER' ? 'user' : 'bot',
text: msg.text
});
}
});
this.scrollToBottom();
} catch (error) {
console.error('Error loading chat history:', error);
}
},
try {
this.waitingData = true;
const response = await fetch("http://olympus-api-gateway-aks.olympusai.live/chatservice/chat", {
method: "POST",
headers: { "Content-Type": "application/json",
"authorization": this.authorization},
body: JSON.stringify(payload),
});
this.waitingData = false;
if (!response.body) throw new Error("Streaming not supported");
async sendMessage() {
if (this.message.trim() === '' || !this.conversationId.trim()) return;
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
this.messages.push({ sender: 'user', text: this.message });
const botMessage = { sender: 'bot', text: '' };
this.messages.push(botMessage);
this.scrollToBottom();
const payload = {
message: this.message,
conversationId: this.conversationId,
useDocumentation: this.useDocumentation,
useSource: this.useSource,
project: this.project,
application: this.application
};
const processStream = async ({ done, value }) => {
if (done) return;
buffer += decoder.decode(value, { stream: true });
this.message = '';
const parts = buffer.split("\n");
buffer = parts.pop();
try {
this.waitingData = true;
const response = await fetch('http://olympus-api-gateway-aks.olympusai.live/chatservice/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json', authorization: this.authorization },
body: JSON.stringify(payload)
});
this.waitingData = false;
if (!response.body) throw new Error('Streaming not supported');
parts.forEach((line) => {
if (line.startsWith("data:")) {
try {
const jsonData = JSON.parse(line.slice(5).trim());
if (jsonData.result?.output?.text) {
botMessage.text += jsonData.result.output.text;
}
this.$forceUpdate();
this.scrollToBottom();
} catch (error) {
console.error("Error parsing JSON:", error);
}
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
const processStream = async ({ done, value }) => {
if (done) return;
buffer += decoder.decode(value, { stream: true });
const parts = buffer.split('\n');
buffer = parts.pop();
parts.forEach((line) => {
if (line.startsWith('data:')) {
try {
const jsonData = JSON.parse(line.slice(5).trim());
if (jsonData.result?.output?.text) {
botMessage.text += jsonData.result.output.text;
}
this.$forceUpdate();
this.scrollToBottom();
} catch (error) {
console.error('Error parsing JSON:', error);
}
}
});
return reader.read().then(processStream);
};
await reader.read().then(processStream);
} catch (error) {
console.error('Error fetching response:', error);
botMessage.text += '\n[Error fetching response]';
}
},
formatMessage(text) {
return marked.parse(text); // Converts Markdown to HTML
},
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.messagesContainer;
container.scrollTop = container.scrollHeight;
});
},
return reader.read().then(processStream);
};
resetChat() {
this.messages = []; // Clear chat messages
this.fetchChatHistory(); // Reload history for new conversation ID
},
await reader.read().then(processStream);
} catch (error) {
console.error("Error fetching response:", error);
botMessage.text += "\n[Error fetching response]";
async clearHistory() {
try {
const response = await fetch(`http://olympus-api-gateway-aks.olympusai.live/chatservice/delete-history?conversationId=${this.conversationId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json', authorization: this.authorization }
});
if (!response.ok) {
throw new Error('Failed to clear chat history');
}
this.messages = [];
console.log('Chat history deleted successfully!');
} catch (error) {
console.error('Error clearing chat history:', error);
}
},
async loadContext() {
try {
const response = await fetch(`http://olympus-api-gateway-aks.olympusai.live/chatservice/load-context-to-conversation?conversationId=${this.conversationId}&scenarioExecutionId=${this.scenarioExecutionId}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json', authorization: this.authorization }
});
if (!response.ok) {
throw new Error('Failed to clear chat history');
}
this.messages = [];
this.resetChat();
console.log('Chat history deleted successfully!');
} catch (error) {
console.error('Error clearing chat history:', error);
}
}
},
formatMessage(text) {
return marked.parse(text); // Converts Markdown to HTML
},
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.messagesContainer;
container.scrollTop = container.scrollHeight;
});
},
resetChat() {
this.messages = []; // Clear chat messages
this.fetchChatHistory(); // Reload history for new conversation ID
},
async clearHistory() {
try {
const response = await fetch(`http://olympus-api-gateway-aks.olympusai.live/chatservice/delete-history?conversationId=${this.conversationId}`, {
method: 'DELETE',
headers: { "Content-Type": "application/json",
"authorization": this.authorization}
});
if (!response.ok) {
throw new Error('Failed to clear chat history');
}
this.messages = [];
console.log('Chat history deleted successfully!');
} catch (error) {
console.error('Error clearing chat history:', error);
}
},
async loadContext() {
try {
const response = await fetch(`http://olympus-api-gateway-aks.olympusai.live/chatservice/load-context-to-conversation?conversationId=${this.conversationId}&scenarioExecutionId=${this.scenarioExecutionId}`, {
method: 'GET',
headers: { "Content-Type": "application/json",
"authorization": this.authorization}
});
if (!response.ok) {
throw new Error('Failed to clear chat history');
}
this.messages = [];
this.resetChat();
console.log('Chat history deleted successfully!');
} catch (error) {
console.error('Error clearing chat history:', error);
}
},
}
};
</script>
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap');
@import 'md-editor-v3/lib/style.css';
};
</script>
.md-editor {
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap');
@import 'md-editor-v3/lib/style.css';
.md-editor {
background-color: inherit;
}
.chat-wrapper {
width: 100%;
box-sizing: border-box;
}
.chat-card {
width: 100%;
margin: 0 auto;
height: 82vh;
.chat-wrapper {
width: 100%;
box-sizing: border-box;
}
.chat-card {
width: 100%;
margin: 0 auto;
height: 82vh;
}
/* Card delle impostazioni */
.chat-settings-card {
width: 100%;
margin: 0 auto;
width: 100%;
margin: 0 auto;
}
/* Area messaggi */
.chat-messages {
background: #f4f4f4;
border-radius: 4px;
padding: 1rem;
overflow-y: auto;
max-height: 70vh;
min-height: 70vh;
background: #f4f4f4;
border-radius: 4px;
padding: 1rem;
overflow-y: auto;
max-height: 70vh;
min-height: 70vh;
}
/* Singolo messaggio */
.chat-message {
margin-bottom: 1rem;
display: flex;
align-items: flex-start;
margin-bottom: 1rem;
display: flex;
align-items: flex-start;
}
/* Messaggi dell'utente a destra */
.chat-message.user {
justify-content: flex-end;
justify-content: flex-end;
}
/* Bolla del messaggio */
.message-bubble {
max-width: 70%;
padding: 0.75rem;
border-radius: 15px;
font-size: 1.1rem;
line-height: 1.4;
max-width: 70%;
padding: 0.75rem;
border-radius: 15px;
font-size: 1.1rem;
line-height: 1.4;
}
/* Stile messaggi del bot */
.chat-message.bot .message-bubble {
background: #e1e1e1;
color: #000;
background: #e1e1e1;
color: #000;
}
/* Stile messaggi dell'utente */
.chat-message.user .message-bubble {
background: #6f3ff5; /* Sostituisci con il colore desiderato */
color: #fff;
background: #6f3ff5; /* Sostituisci con il colore desiderato */
color: #fff;
}
/* Esempio di pulsanti "outlined" personalizzati */
.p-button-outlined {
border: 1px solid #6f3ff5; /* Adatta al tuo tema */
color: #6f3ff5;
border: 1px solid #6f3ff5; /* Adatta al tuo tema */
color: #6f3ff5;
}
.p-button-outlined.p-button-danger {
border: 1px solid #f44336; /* Rosso */
color: #f44336;
border: 1px solid #f44336; /* Rosso */
color: #f44336;
}
</style>
</style>

View File

@@ -25,18 +25,22 @@ const model = ref([
{
label: 'Canvas',
items: [{ label: 'New Canvas', icon: 'pi pi-fw pi-pencil', to: '/mdcanvas' }]
}
},
{
label: 'Chat',
items: [{ label: 'Chat', icon: 'pi pi-fw pi-comments', to: '/chat' }]
}
]);
onMounted(() => {
if(userPrefStore.user.role == 'ADMIN'){
model.value.push({
label: 'Chat',
items: [{ label: 'Chat', icon: 'pi pi-fw pi-comments', to: '/chat' }]
});
}
});
// onMounted(() => {
// if(userPrefStore.user.role == 'ADMIN'){
// model.value.push({
// label: 'Chat',
// items: [{ label: 'Chat', icon: 'pi pi-fw pi-comments', to: '/chat' }]
// });
// }
// });
// Funzione per aggiornare la sezione "Your Applications" in base a selectedApp
function updateApplicationsMenu() {

View File

@@ -108,6 +108,10 @@ watch(() => userPrefStore.getSelApp, appUpdated, { immediate: true });
</div>
<div class="layout-topbar-theme">
<span class="flex items-center mt-2">Project:</span>
</div>
<div class="topbar-project">
<button v-if="userPrefStore.user.selectedProject" @click="redirectProject()" class="p-button p-button-outlined" v-tooltip="'Click to change the project'">{{ userPrefStore.user.selectedProject.fe_name }}</button>
<button v-else @click="redirectProject()" class="p-button p-button-outlined" v-tooltip="'Click to change the project'">Project</button>
@@ -115,7 +119,11 @@ watch(() => userPrefStore.getSelApp, appUpdated, { immediate: true });
<!-- <span v-if="userPrefStore.user.selectedProject">
<small>PROJECT:</small> {{ userPrefStore.user.selectedProject.fe_name }}
</span> -->
</div>
</div>
<div class="layout-topbar-theme">
<span class="flex items-center mt-2">Application:</span>
</div>
<Dropdown
v-model="selectedApp"
:options="userPrefStore.availableApp"
@@ -155,7 +163,7 @@ watch(() => userPrefStore.getSelApp, appUpdated, { immediate: true });
display: flex;
align-items: center;
justify-content: left;
margin-left: 1rem;
margin-right: 1rem;
font-size: 1.2rem;
min-width: 100px;

View File

@@ -1,167 +1,147 @@
<template>
<div v-if="loading" class="flex justify-center">
<ProgressSpinner style="width: 50px; height: 50px; margin-top: 50px" strokeWidth="3" fill="transparent"/>
</div>
<div v-else>
<div class="flex items-center justify-between p-2">
<!-- <Button
<div v-if="loading" class="flex justify-center">
<ProgressSpinner style="width: 50px; height: 50px; margin-top: 50px" strokeWidth="3" fill="transparent" />
</div>
<div v-else>
<div class="flex items-center justify-between p-2">
<!-- <Button
@click="back()"
label="Load"
class="flex items-center text-sm">
<ChevronLeftIcon name="chevron-left" class="w-4 h-5 text-white"/>
<span>Back to Scenarios</span>
</Button> -->
</div>
</div>
</div>
<div v-if="loading_data" class="flex justify-center">
<ProgressSpinner style="width: 30px; height: 30px; margin: 30px" strokeWidth="6" fill="transparent"/>
</div>
<div v-if="data_loaded">
<!-- <div class="box p-4 border rounded-md shadow-sm" style="background-color: #f3f3f3;">
<div v-for="(input, index) in inputs" :key="index" class="input-container">
<div class="input-wrapper">
<span class="font-bold">{{ index.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase()) }}:</span>
<span>{{ input }}</span>
</div>
<div v-if="loading_data" class="flex justify-center">
<ProgressSpinner style="width: 30px; height: 30px; margin: 30px" strokeWidth="6" fill="transparent" />
</div>
<div v-if="data_loaded">
<Panel class="mt-6">
<template #header>
<div class="flex items-center gap-2">
<span class="font-bold">Execution Input for ID {{ execution_id }}</span>
</div>
</template>
<template #icons>
<div v-if="updateLoading" class="flex justify-end">
<Rating :modelValue="rating" :stars="5" @change="updateRating($event)" />
</div>
<div v-else class="flex justify-end">
<Rating :modelValue="rating" :stars="5" :readonly="true" @change="updateRating($event)" />
</div>
</template>
<div class="box p-4 border rounded-md shadow-sm" style="background-color: white">
<table class="table-auto w-full border-collapse border border-gray-300">
<tbody>
<tr v-for="(input, index) in inputs" :key="index">
<th v-if="index === 'MultiFileUpload'" class="border border-gray-300 px-4 py-2">Files Uploaded</th>
<th v-else-if="index === 'SingleFileUpload'" class="border border-gray-300 px-4 py-2 bg-gray-500 text-white">Parameter</th>
<th v-else class="border border-gray-300 px-4 py-2">
{{ index.replace(/_/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()) }}
</th>
<td class="border border-gray-300 px-4 py-2">
<div v-if="index === 'MultiFileUpload'">
<div v-if="fileNames.length">
<ul>
<li v-for="(file, idx) in fileNames" :key="idx" class="file-item">
{{ file.substring(file.lastIndexOf('/') + 1) }}
<Button icon="pi pi-download" class="p-button-text p-button-sm" label="Download" @click="downloadFileInput(file)" />
</li>
</ul>
</div>
<div v-else>
<p>No files found in the zip.</p>
</div>
</div>
<div v-else-if="index !== 'SingleFileUpload'">{{ input }}</div>
</td>
</tr>
</tbody>
</table>
<div v-if="data_loaded && scenario.chatEnabled" class="flex justify-center">
<div v-if="!chat_enabled" class="flex gap-4 mt-4">
<Button label="Open Chat" @click="chatEnabled" size="large" iconPos="right" icon="pi pi-comments"></Button>
</div>
</div> -->
<div v-else class="flex gap-4 mt-4">
<Button label="Return to scenario" @click="chatDisabled" size="large" iconPos="right" icon="pi pi-backward"></Button>
</div>
</div>
</div>
</Panel>
<div v-if="!chat_enabled" class="mt-4">
<Panel class="mt-6">
<template #header>
<div class="flex items-center gap-2">
<span class="font-bold">Execution Input for ID {{execution_id}}</span>
</div>
</template>
<template #icons>
<div v-if="updateLoading" class="flex justify-end">
<Rating
:modelValue="rating"
:stars="5"
@change="updateRating($event)"
/>
<div class="flex items-center gap-2">
<span class="font-bold">Workflow response</span>
</div>
<div v-else class="flex justify-end">
<Rating
:modelValue="rating"
:stars="5"
:readonly="true"
@change="updateRating($event)"
/>
</div>
</template>
<div class="box p-4 border rounded-md shadow-sm" style="background-color: white;">
<table class="table-auto w-full border-collapse border border-gray-300">
<tbody>
<tr v-for="(input, index) in inputs" :key="index">
<th v-if="index === 'MultiFileUpload'" class="border border-gray-300 px-4 py-2">
Files Uploaded
</th>
<th v-else-if="index === 'SingleFileUpload'" class="border border-gray-300 px-4 py-2 bg-gray-500 text-white">
Parameter
</th>
<th v-else class="border border-gray-300 px-4 py-2">
{{ index.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase()) }}
</th>
<td class="border border-gray-300 px-4 py-2">
<div v-if="index === 'MultiFileUpload'">
<div v-if="fileNames.length">
<ul>
<li v-for="(file, idx) in fileNames" :key="idx" class="file-item">
{{ file.substring(file.lastIndexOf('/') + 1) }}
<Button
icon="pi pi-download"
class="p-button-text p-button-sm"
label="Download"
@click="downloadFileInput(file)"
/>
</li>
</ul>
</div>
<div v-else>
<p>No files found in the zip.</p>
</div>
</div>
<div v-else-if="index !== 'SingleFileUpload'">{{ input }}</div>
</td>
</tr>
</tbody>
</table>
</div>
</Panel>
<Panel class="mt-6">
<template #header>
<div class="flex items-center gap-2">
<span class="font-bold">Workflow response</span>
</div>
</template>
<template #icons>
<div class="flex justify-end">
<Button severity="secondary" rounded @click="openDebug" v-tooltip.left="'View code'">
<i class="pi pi-code"></i>
</Button>
<Button severity="secondary" rounded @click="openDebug" v-tooltip.left="'View code'">
<i class="pi pi-code"></i>
</Button>
</div>
</template>
<div class="card flex flex-col gap-4 w-full">
<div v-if="scenario.outputType == 'ciaOutput'">
<ChangeImpactOutputViewer :scenario_output="scenario_output" />
<ChangeImpactOutputViewer :scenario_output="scenario_output" />
</div>
<div v-else-if="loadingStore.exectuion_loading && loadingStore.getExecIdLoading===execution_id">
<div class="flex justify-center mt-4">
<jellyfish-loader :loading="loadingStore.exectuion_loading" scale="1" color="#A100FF" />
</div>
<div v-else-if="loadingStore.exectuion_loading && loadingStore.getExecIdLoading === execution_id">
<div class="flex justify-center mt-4">
<jellyfish-loader :loading="loadingStore.exectuion_loading" scale="1" color="#A100FF" />
</div>
</div>
<div v-else>
<div v-if="fileType == 'FILE' && exec_scenario.execSharedMap.status!=null && exec_scenario.execSharedMap.status==='DONE'">
<ul class="file-list">
<li class="file-item">
sf_document-{{execution_id}}
<Button
icon="pi pi-download"
class="p-button-text p-button-sm"
label="Download"
@click="downloadFile(scenario_output)"
/>
</li>
</ul>
</div>
<div v-else-if="fileType == 'MARKDOWN'">
<div v-html="fileContent" class="markdown-content"></div>
</div>
<div v-else-if="fileType == 'JSON'">
<pre>{{ fileContent }}</pre>
</div>
<div v-else>
<MdPreview class="editor" v-model="scenario_output" language="en-US" />
</div>
<div v-if="fileType == 'FILE' && exec_scenario.execSharedMap.status != null && exec_scenario.execSharedMap.status === 'DONE'">
<ul class="file-list">
<li class="file-item">
sf_document-{{ execution_id }}
<Button icon="pi pi-download" class="p-button-text p-button-sm" label="Download" @click="downloadFile(scenario_output)" />
</li>
</ul>
</div>
<div v-else-if="fileType == 'MARKDOWN'">
<div v-html="fileContent" class="markdown-content"></div>
</div>
<div v-else-if="fileType == 'JSON'">
<pre>{{ fileContent }}</pre>
</div>
<div v-else>
<MdPreview class="editor" v-model="scenario_output" language="en-US" />
</div>
</div>
</div>
</Panel>
<Dialog v-model:visible="debug_modal" maximizable modal :header="scenario.name" :style="{ width: '75%' }" :breakpoints="{ '1199px': '75vw', '575px': '90vw' }">
<div class="flex">
<div class="card flex flex-col gap-4 w-full">
<JsonEditorVue
v-model="exec_scenario"
/>
</div>
</div>
<div class="card flex flex-col gap-4 w-full">
<JsonEditorVue v-model="exec_scenario" />
</div>
</div>
</Dialog>
</div>
<div v-else="chat_enabled" class="mt-4">
<Panel class="mt-6">
<template #header>
<div class="flex items-center gap-2 mt-2">
<span class="font-bold">Chat with WizardAI</span>
</div>
</template>
<div class="card flex flex-col gap-4 w-full">
<ChatClient :scenarioExecutionId="execution_id" />
</div>
</Panel>
</div>
</div>
</template>
<script setup>
import ChangeImpactOutputViewer from '@/components/ChangeImpactOutputViewer.vue';
import { LoadingStore } from '@/stores/LoadingStore.js';
import axios from 'axios';
@@ -174,10 +154,11 @@ import ProgressSpinner from 'primevue/progressspinner';
import { useToast } from 'primevue/usetoast';
import { onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { JellyfishLoader } from "vue3-spinner";
import { JellyfishLoader } from 'vue3-spinner';
import { ScenarioService } from '../../service/ScenarioService.js';
import { ScenarioExecutionStore } from '../../stores/ScenarioExecutionStore.js';
import { UserPrefStore } from '../../stores/UserPrefStore.js';
import ChatClient from '@/components/ChatClient.vue';
const loadingStore = LoadingStore();
const router = useRouter();
@@ -195,111 +176,100 @@ const debug_modal = ref(false);
const rating = ref(null);
const scenario_execution_store = ScenarioExecutionStore();
const execution = scenario_execution_store.getSelectedExecScenario;
const execution_id = ref(null)
const execution_id = ref(null);
const inputs = ref(null);
const steps = ref(null);
const toast = useToast();
const toast = useToast();
const fileNames = ref([]); // Memorizza i nomi dei file nello zip
const fileNamesOutput = ref([]); // Memorizza i nomi dei file nello zip
const zipInput = ref(null); // Contenitore per il file zip
const zipOutput = ref(null); // Contenitore per il file zip
const userPrefStore = UserPrefStore();
const updateLoading = ref(false);
const fileContent = ref('');
const fileContent = ref('');
const fileType = ref('');
const chat_enabled = ref(false);
onMounted(() => {
if (execution){
console.log("scenario id: ", execution);
execution_id.value = execution.id
console.log("execution_id: ",execution_id)
}else{
if (execution) {
execution_id.value = execution.id;
} else {
const url = window.location.href;
execution_id.value = (new URL(url)).searchParams.get('id')
console.log(execution_id.value)
execution_id.value = new URL(url).searchParams.get('id');
}
retrieveScenarioExec(execution_id.value)
retrieveScenarioExec(execution_id.value);
});
const retrieveScenarioExec = (id) => {
//const id = execution_id.value.id;
loading.value = true
loading.value = true;
axios.get('/execution?id=' + id )
.then(response => {
loading.value = false
scenario.value = response.data.scenario
exec_scenario.value = response.data
data_loaded.value = true;
rating.value = response.data.rating
scenario_output.value = response.data.execSharedMap.scenario_output;
exec_id.value = response.data.scenarioExecution_id
inputs.value = response.data.scenarioExecutionInput.inputs
steps.value = response.data.scenario.steps
if(inputs.value['MultiFileUpload']){
extractFiles(inputs.value['MultiFileUpload'], 'input', zipInput)
if(steps.value[0].attributes['codegenie_output_type']){
if(steps.value[0].attributes['codegenie_output_type'] == 'FILE'){
//console.log('base64 ', scenario_output.value)
//extractFiles(scenario_output.value, 'output', zipOutput)
fileType.value = 'FILE'
}
else if(steps.value[0].attributes['codegenie_output_type'] == 'MARKDOWN'){
fileType.value = 'MARKDOWN'
showFileContent(scenario_output.value, 'MARKDOWN')
}
else if(steps.value[0].attributes['codegenie_output_type'] == 'JSON'){
fileType.value = 'JSON'
showFileContent(scenario_output.value, 'JSON')
}
axios.get('/execution?id=' + id).then((response) => {
loading.value = false;
scenario.value = response.data.scenario;
exec_scenario.value = response.data;
data_loaded.value = true;
rating.value = response.data.rating;
scenario_output.value = response.data.execSharedMap.scenario_output;
exec_id.value = response.data.scenarioExecution_id;
inputs.value = response.data.scenarioExecutionInput.inputs;
steps.value = response.data.scenario.steps;
if (inputs.value['MultiFileUpload']) {
extractFiles(inputs.value['MultiFileUpload'], 'input', zipInput);
if (steps.value[0].attributes['codegenie_output_type']) {
if (steps.value[0].attributes['codegenie_output_type'] == 'FILE') {
fileType.value = 'FILE';
} else if (steps.value[0].attributes['codegenie_output_type'] == 'MARKDOWN') {
fileType.value = 'MARKDOWN';
showFileContent(scenario_output.value, 'MARKDOWN');
} else if (steps.value[0].attributes['codegenie_output_type'] == 'JSON') {
fileType.value = 'JSON';
showFileContent(scenario_output.value, 'JSON');
}
}
if(exec_scenario.value.executedByUsername===userPrefStore.getUser.username){
updateLoading.value = true
}
});
}
if (exec_scenario.value.executedByUsername === userPrefStore.getUser.username) {
updateLoading.value = true;
}
});
};
const extractFiles = async (base64String, type, zip) => {
try {
// Decodifica la base64 in un array di byte
const byteCharacters = atob(base64String);
const byteNumbers = Array.from(byteCharacters, char => char.charCodeAt(0));
const byteArray = new Uint8Array(byteNumbers);
try {
// Decodifica la base64 in un array di byte
const byteCharacters = atob(base64String);
const byteNumbers = Array.from(byteCharacters, (char) => char.charCodeAt(0));
const byteArray = new Uint8Array(byteNumbers);
// Carica il file zip con JSZip
const zipData = await JSZip.loadAsync(byteArray);
zip.value = zipData;
// Carica il file zip con JSZip
const zipData = await JSZip.loadAsync(byteArray);
zip.value = zipData;
// Ottieni tutti i file (compresi quelli nelle sottocartelle)
if(type == 'input'){
fileNames.value = getFileNamesInput(zipData);
}else{
fileNamesOutput.value = getFileNames(zipData);
// Ottieni tutti i file (compresi quelli nelle sottocartelle)
if (type == 'input') {
fileNames.value = getFileNamesInput(zipData);
} else {
fileNamesOutput.value = getFileNames(zipData);
}
} catch (error) {
console.error('Error extracting zip:', error);
if (type == 'input') {
fileNames.value = [];
} else {
fileNamesOutput.value = [];
}
}
} catch (error) {
console.error('Error extracting zip:', error);
if(type == 'input'){
fileNames.value = [];
}else{
fileNamesOutput.value = [];
}
}
};
const showFileContent = (base64String, type) => {
try {
try {
// Decodifica la stringa Base64
const binaryString = atob(base64String);
const binaryLength = binaryString.length;
const bytes = new Uint8Array(binaryLength);
for (let i = 0; i < binaryLength; i++) {
bytes[i] = binaryString.charCodeAt(i);
bytes[i] = binaryString.charCodeAt(i);
}
// Converti i byte in una stringa leggibile
@@ -307,257 +277,257 @@ const showFileContent = (base64String, type) => {
// Gestione del tipo di file
if (type === 'MARKDOWN') {
//fileType.value = 'markdown';
fileContent.value = marked(textContent); // Converte Markdown in HTML
//fileType.value = 'markdown';
fileContent.value = marked(textContent); // Converte Markdown in HTML
} else if (type === 'JSON') {
//fileType.value = 'json';
const jsonObject = JSON.parse(textContent); // Parse JSON
fileContent.value = JSON.stringify(jsonObject, null, 2); // Formatta JSON
//fileType.value = 'json';
const jsonObject = JSON.parse(textContent); // Parse JSON
fileContent.value = JSON.stringify(jsonObject, null, 2); // Formatta JSON
} else {
fileContent.value = 'Tipo di file non supportato.';
fileContent.value = 'Tipo di file non supportato.';
}
} catch (error) {
} catch (error) {
fileContent.value = 'Errore durante la decodifica o il parsing del file.';
console.error(error);
}
};
}
};
// Funzione ricorsiva per ottenere tutti i file (anche quelli dentro le cartelle)
const getFileNames = (zipData) => {
const files = [];
// Esplora tutti i file nel file zip, considerando anche le sottocartelle
zipData.forEach((relativePath, file) => {
if (!file.dir) { // Escludiamo le cartelle
const fileName = relativePath.split('/').pop(); // Estrai solo il nome del file
files.push(fileName); // Aggiungiamo solo il nome del file
}
});
// Esplora tutti i file nel file zip, considerando anche le sottocartelle
zipData.forEach((relativePath, file) => {
if (!file.dir) {
// Escludiamo le cartelle
const fileName = relativePath.split('/').pop(); // Estrai solo il nome del file
files.push(fileName); // Aggiungiamo solo il nome del file
}
});
return files;
return files;
};
const getFileNamesInput = (zipData) => {
const files = [];
const getFileNamesInput = (zipData) => {
const files = [];
// Esplora tutti i file nel file zip, considerando anche le sottocartelle
zipData.forEach((relativePath, file) => {
if (!file.dir) { // Escludiamo le cartelle
files.push(relativePath); // Aggiungiamo il percorso relativo del file
}
});
// Esplora tutti i file nel file zip, considerando anche le sottocartelle
zipData.forEach((relativePath, file) => {
if (!file.dir) {
// Escludiamo le cartelle
files.push(relativePath); // Aggiungiamo il percorso relativo del file
}
});
return files;
};
return files;
};
// Funzione per scaricare il file
const downloadFileInput = async (fileName) => {
if (!zipInput.value) return;
if (!zipInput.value) return;
try {
// Estrai il file dallo zip
const fileContent = await zipInput.value.file(fileName).async('blob');
const url = URL.createObjectURL(fileContent);
try {
// Estrai il file dallo zip
const fileContent = await zipInput.value.file(fileName).async('blob');
const url = URL.createObjectURL(fileContent);
// Crea un link per scaricare il file
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Crea un link per scaricare il file
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (error) {
console.error(`Error downloading file "${fileName}":`, error);
}
URL.revokeObjectURL(url);
} catch (error) {
console.error(`Error downloading file "${fileName}":`, error);
}
};
const downloadFileOutput = async (fileName) => {
if (!zipOutput.value) return;
if (!zipOutput.value) return;
try {
// Estrai il file dallo zip
const fileContent = await zipOutput.value.file(fileName).async('blob');
const url = URL.createObjectURL(fileContent);
try {
// Estrai il file dallo zip
const fileContent = await zipOutput.value.file(fileName).async('blob');
const url = URL.createObjectURL(fileContent);
// Crea un link per scaricare il file
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Crea un link per scaricare il file
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (error) {
console.error(`Error downloading file "${fileName}":`, error);
}
URL.revokeObjectURL(url);
} catch (error) {
console.error(`Error downloading file "${fileName}":`, error);
}
};
const downloadFile = (base64String) => {
// Decodifica la stringa Base64
const binaryString = atob(base64String);
const binaryLength = binaryString.length;
const bytes = new Uint8Array(binaryLength);
// Decodifica la stringa Base64
const binaryString = atob(base64String);
const binaryLength = binaryString.length;
const bytes = new Uint8Array(binaryLength);
for (let i = 0; i < binaryLength; i++) {
for (let i = 0; i < binaryLength; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// Creazione di un Blob dal file binario
const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
// Creazione di un URL per il Blob
const url = URL.createObjectURL(blob);
// Creazione di un elemento anchor per il download
const link = document.createElement('a');
link.href = url;
link.download = 'sf_document-'+execution_id.value+'.docx';
// Simulazione di un click per scaricare il file
document.body.appendChild(link);
link.click();
// Pulizia del DOM
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
const downloadFolderFromBase64 = async (base64String) => {
try {
// Decodifica la stringa base64 in un array di byte
const binaryString = atob(base64String); // Decodifica base64 in una stringa binaria
const byteArray = new Uint8Array(binaryString.length); // Crea un array di byte
for (let i = 0; i < binaryString.length; i++) {
byteArray[i] = binaryString.charCodeAt(i); // Popola l'array di byte
}
// Crea un oggetto JSZip per lavorare con il contenuto ZIP
const zip = await JSZip.loadAsync(byteArray); // Carica direttamente l'array di byte
// Creazione di un Blob dal file binario
const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
// Estrai il primo nome della cartella presente nello ZIP
let folderName = Object.keys(zip.files).find(file => zip.files[file].dir); // Trova il primo file che è una cartella
// Se non è stata trovata alcuna cartella, creiamo una cartella chiamata "folderToDownload"
if (!folderName) {
folderName = 'docOutput-' + execution_id.value;
console.log(`Non è stata trovata alcuna cartella nello ZIP. Verrà creata una cartella chiamata "${folderName}".`);
}
// Creazione di un URL per il Blob
const url = URL.createObjectURL(blob);
// Crea un nuovo archivio ZIP in cui mettere i file della cartella
const newZip = new JSZip();
// Creazione di un elemento anchor per il download
const link = document.createElement('a');
link.href = url;
link.download = 'sf_document-' + execution_id.value + '.docx';
// Se una cartella esiste nello ZIP, estrai i file da essa, altrimenti crea una nuova cartella
const folder = zip.folder(folderName);
if (folder) {
// Aggiungi ogni file della cartella al nuovo archivio ZIP
const files = folder.files;
for (const fileName in files) {
const file = files[fileName];
// Controlla se il file è valido (non nullo)
if (file && file.async) {
try {
const fileContent = await file.async('blob'); // Estrai il contenuto del file
newZip.file(fileName, fileContent);
} catch (fileError) {
console.error(`Errore durante l'estrazione del file "${fileName}":`, fileError);
}
} else {
console.warn(`Il file "${fileName}" non è valido o non può essere elaborato.`);
}
}
} else {
// Se la cartella non esiste, crea una cartella vuota con nome "folderToDownload"
newZip.folder(folderName);
}
// Crea il nuovo file ZIP da scaricare
const newZipContent = await newZip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(newZipContent);
// Crea un link per scaricare il file ZIP
const a = document.createElement('a');
a.href = url;
a.download = `${folderName}.zip`; // Nome del file ZIP che contiene la cartella
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Simulazione di un click per scaricare il file
document.body.appendChild(link);
link.click();
// Pulizia del DOM
document.body.removeChild(link);
URL.revokeObjectURL(url);
} catch (error) {
console.error('Errore durante il download della cartella:', error);
}
};
const downloadFolderFromBase64 = async (base64String) => {
try {
// Decodifica la stringa base64 in un array di byte
const binaryString = atob(base64String); // Decodifica base64 in una stringa binaria
const byteArray = new Uint8Array(binaryString.length); // Crea un array di byte
for (let i = 0; i < binaryString.length; i++) {
byteArray[i] = binaryString.charCodeAt(i); // Popola l'array di byte
}
// Crea un oggetto JSZip per lavorare con il contenuto ZIP
const zip = await JSZip.loadAsync(byteArray); // Carica direttamente l'array di byte
// Estrai il primo nome della cartella presente nello ZIP
let folderName = Object.keys(zip.files).find((file) => zip.files[file].dir); // Trova il primo file che è una cartella
// Se non è stata trovata alcuna cartella, creiamo una cartella chiamata "folderToDownload"
if (!folderName) {
folderName = 'docOutput-' + execution_id.value;
console.log(`No folders founded in the ZIP. Creating a new folder: "${folderName}".`);
}
// Crea un nuovo archivio ZIP in cui mettere i file della cartella
const newZip = new JSZip();
// Se una cartella esiste nello ZIP, estrai i file da essa, altrimenti crea una nuova cartella
const folder = zip.folder(folderName);
if (folder) {
// Aggiungi ogni file della cartella al nuovo archivio ZIP
const files = folder.files;
for (const fileName in files) {
const file = files[fileName];
// Controlla se il file è valido (non nullo)
if (file && file.async) {
try {
const fileContent = await file.async('blob'); // Estrai il contenuto del file
newZip.file(fileName, fileContent);
} catch (fileError) {
console.error(`Error while extracting "${fileName}":`, fileError);
}
} else {
console.warn(`"${fileName}" is invalid or cannot be elaborated`);
}
}
} else {
// Se la cartella non esiste, crea una cartella vuota con nome "folderToDownload"
newZip.folder(folderName);
}
// Crea il nuovo file ZIP da scaricare
const newZipContent = await newZip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(newZipContent);
// Crea un link per scaricare il file ZIP
const a = document.createElement('a');
a.href = url;
a.download = `${folderName}.zip`; // Nome del file ZIP che contiene la cartella
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (error) {
console.error('Error while downloading folder:', error);
}
};
async function updateRating(newRating) {
loading_data.value = true;
ScenarioService.updateScenarioExecRating(execution_id.value,newRating.value).then((response) => {
console.log('response:', response);
if (response.data === "OK") {
rating.value = newRating.value;
console.log('Rating aggiornato con successo:', response.data);
toast.add({
severity: 'success', // Tipo di notifica (successo)
summary: 'Success', // Titolo della notifica
detail: 'Rating updated with success.', // Messaggio dettagliato
life: 3000 // Durata della notifica in millisecondi
});
} else {
console.error('Errore nell\'aggiornamento del rating', response.data);
toast.add({
severity: 'error', // Tipo di notifica (errore)
summary: 'Error', // Titolo della notifica
detail: 'Error updating rating. Try later.', // Messaggio dettagliato
life: 3000 // Durata della notifica in millisecondi
});
}
}).catch((error) => {
console.error('Errore durante la chiamata al backend:', error);
}).finally(() => {
loading_data.value = false;
});
ScenarioService.updateScenarioExecRating(execution_id.value, newRating.value)
.then((response) => {
console.log('response:', response);
if (response.data === 'OK') {
rating.value = newRating.value;
console.log('Rating successfully updated:', response.data);
toast.add({
severity: 'success', // Tipo di notifica (successo)
summary: 'Success', // Titolo della notifica
detail: 'Rating updated with success.', // Messaggio dettagliato
life: 3000 // Durata della notifica in millisecondi
});
} else {
console.error("Error while updating rating", response.data);
toast.add({
severity: 'error', // Tipo di notifica (errore)
summary: 'Error', // Titolo della notifica
detail: 'Error updating rating. Try later.', // Messaggio dettagliato
life: 3000 // Durata della notifica in millisecondi
});
}
})
.catch((error) => {
console.error('Error while calling backend:', error);
})
.finally(() => {
loading_data.value = false;
});
}
const back = () => {
router.push({ name: 'scenario-list'});
}
router.push({ name: 'scenario-list' });
};
const openDebug = () => {
debug_modal.value = true
}
debug_modal.value = true;
};
const chatEnabled = () => {
chat_enabled.value = true;
};
const chatDisabled = () => {
chat_enabled.value = false;
};
</script>
<style scoped>
.input-container {
margin-bottom: 1em;
}
.input-container {
margin-bottom: 1em;
}
.input-wrapper {
display: flex;
align-items: center;
gap: 0.5em;
margin-top: 10px;
}
.full-width-input {
width: 100%;
}
.input-wrapper {
display: flex;
align-items: center;
gap: 0.5em;
margin-top: 10px;
}
.full-width-input {
width: 100%;
}
.editor ol {
list-style-type: decimal !important;
@@ -568,31 +538,29 @@ const openDebug = () => {
}
pre {
white-space: pre-wrap; /* Fa andare a capo il contenuto automaticamente */
word-wrap: break-word; /* Interrompe le parole troppo lunghe */
overflow-wrap: break-word; /* Per compatibilità con più browser */
max-width: 100%; /* Imposta una larghezza massima pari al contenitore genitore */
overflow-x: auto; /* Aggiunge uno scorrimento orizzontale solo se necessario */
background-color: #f5f5f5; /* Colore di sfondo opzionale per migliorare leggibilità */
padding: 10px; /* Spaziatura interna */
border-radius: 5px; /* Bordo arrotondato opzionale */
font-family: monospace; /* Font specifico per codice */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Ombra per migliorare estetica */
white-space: pre-wrap; /* Fa andare a capo il contenuto automaticamente */
word-wrap: break-word; /* Interrompe le parole troppo lunghe */
overflow-wrap: break-word; /* Per compatibilità con più browser */
max-width: 100%; /* Imposta una larghezza massima pari al contenitore genitore */
overflow-x: auto; /* Aggiunge uno scorrimento orizzontale solo se necessario */
background-color: #f5f5f5; /* Colore di sfondo opzionale per migliorare leggibilità */
padding: 10px; /* Spaziatura interna */
border-radius: 5px; /* Bordo arrotondato opzionale */
font-family: monospace; /* Font specifico per codice */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Ombra per migliorare estetica */
}
.markdown-content {
white-space: pre-wrap; /* Gestisce correttamente gli spazi e i ritorni a capo */
word-wrap: break-word; /* Spezza le parole lunghe */
overflow-wrap: break-word; /* Per compatibilità con più browser */
max-width: 100%; /* Adatta il contenuto alla larghezza del contenitore */
overflow-x: auto; /* Aggiunge scorrimento orizzontale solo se necessario */
background-color: #f5f5f5; /* Sfondo per distinguere il contenuto */
padding: 10px; /* Margini interni */
border-radius: 5px; /* Bordo arrotondato */
font-family: Arial, sans-serif; /* Puoi scegliere un font leggibile */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Effetto estetico di ombra */
line-height: 1.5; /* Aumenta la leggibilità */
white-space: pre-wrap; /* Gestisce correttamente gli spazi e i ritorni a capo */
word-wrap: break-word; /* Spezza le parole lunghe */
overflow-wrap: break-word; /* Per compatibilità con più browser */
max-width: 100%; /* Adatta il contenuto alla larghezza del contenitore */
overflow-x: auto; /* Aggiunge scorrimento orizzontale solo se necessario */
background-color: #f5f5f5; /* Sfondo per distinguere il contenuto */
padding: 10px; /* Margini interni */
border-radius: 5px; /* Bordo arrotondato */
font-family: Arial, sans-serif; /* Puoi scegliere un font leggibile */
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Effetto estetico di ombra */
line-height: 1.5; /* Aumenta la leggibilità */
}
</style>
</style>

View File

@@ -9,7 +9,12 @@
{{ scenario.description }}
</h2>
</div>
<div class="flex mt-6">
<div v-if="data_loaded && chat_enabled" class="flex mt-6 justify-center">
<div class="card flex flex-col gap-4 w-full items-center">
<Button label="Return to scenario" @click="chatDisabled" size="large" iconPos="right" icon="pi pi-backward" class="w-auto"></Button>
</div>
</div>
<div v-else class="flex mt-6">
<div class="card flex flex-col gap-4 w-full">
<MdPreview :class="['markdown-content', 'ml-[-20px]']" v-model="scenario.hint" language="en-US" />
@@ -143,7 +148,16 @@
</div>
</div>
</div>
<div class="flex justify-center">
<div v-if="data_loaded && scenario.chatEnabled" class="flex justify-center">
<div v-if ="!chat_enabled" class="flex gap-4">
<Button :disabled="loadingStore.exectuion_loading || !isInputFilled" label="Execute" @click="execScenario" size="large" iconPos="right" icon="pi pi-cog"></Button>
<Button label="Open Chat" @click="chatEnabled" size="large" iconPos="right" icon="pi pi-comments"></Button>
</div>
<!-- <div v-else>
<Button label="Return to scenario" @click="chatDisabled" size="large" iconPos="right" icon="pi pi-backward"></Button>
</div> -->
</div>
<div v-else class="flex justify-center">
<Button :disabled="loadingStore.exectuion_loading || !isInputFilled" label="Execute" @click="execScenario" size="large" iconPos="right" icon="pi pi-cog"></Button>
</div>
</template>
@@ -163,7 +177,7 @@
<div id="timer" class="timer">00:00</div>
</div>
</div>
<div v-if="data_loaded">
<div v-if="data_loaded && !chat_enabled">
<Panel class="mt-6">
<template #header>
<div class="flex items-center gap-2">
@@ -234,6 +248,18 @@
</div>
</Dialog>
</div>
<div v-if="data_loaded && chat_enabled" class="mt-4">
<Panel class="mt-6">
<template #header>
<div class="flex items-center gap-2 mt-2">
<span class="font-bold">Chat with WizardAI</span>
</div>
</template>
<div class="card flex flex-col gap-4 w-full">
<ChatClient :scenarioExecutionId="exec_id"/>
</div>
</Panel>
</div>
</template>
<script setup>
@@ -256,13 +282,12 @@ import { computed, onMounted, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { JellyfishLoader } from 'vue3-spinner';
import { ScenarioService } from '../../service/ScenarioService';
import ChatClient from '@/components/ChatClient.vue';
const loadingStore = LoadingStore();
const toast = useToast();
const zip = ref(null);
const router = useRouter();
const route = useRoute();
const value = ref('');
const rating = ref(0);
const scenario = ref({});
const scenario_response = ref(null);
@@ -298,6 +323,7 @@ const files = ref([]);
const fileContent = ref('');
const fileType = ref('');
const reqMultiFile = ref(false);
const chat_enabled = ref(false);
let startTime = ref(null);
let timerInterval = ref(null);
@@ -387,6 +413,14 @@ const getInputComponent = (type) => {
}
};
const chatEnabled = () =>{
chat_enabled.value = true;
}
const chatDisabled = () =>{
chat_enabled.value = false;
}
const execScenario = () => {
if (numberPrFiles.value !== 1 && reqMultiFile.value) {
toast.add({

View File

@@ -1,9 +1,22 @@
<template>
<div class="flex items-center justify-between p-1">
<h1>
<i class="pi pi-comments p-mr-2"></i>
<div class="flex items-center justify-between p-1">
<h1 class="flex items-center">
<i class="pi pi-comments mr-2"></i>
<span>Chat with WizardAI</span>
</h1>
</div>
<div class="flex items-center justify-between p-1">
<h2>
<span>
Contextualized on
</span><br/>
<span>
Project: <strong>{{ userPrefStore.user.selectedProject.fe_name }}</strong>
</span><br/>
<span v-if="userPrefStore.user.selectedApplication">
Application: <strong>{{ userPrefStore.user.selectedApplication.fe_name}}</strong>
</span>
</h2>
</div>
<div className="card">
<ChatClient />
@@ -12,6 +25,15 @@
<script setup>
import ChatClient from '@/components/ChatClient.vue';
import { UserPrefStore } from '@/stores/UserPrefStore.js';
import { onMounted, computed, watch, ref} from 'vue';
const userPrefStore = UserPrefStore();
onMounted(() => {
console.log('userPrefStore', userPrefStore);
});
</script>