1151 lines
35 KiB
Vue
1151 lines
35 KiB
Vue
<script setup>
|
|
import ChatClient from '@/components/ChatClient.vue';
|
|
import DynamicPicker from '@/components/DynamicPicker.vue';
|
|
import ExecutionTimer from '@/components/ExecutionTimer.vue';
|
|
import MarkdownViewer from '@/components/MarkdownViewer.vue';
|
|
import ScenarioFileUpload from '@/components/ScenarioFileUpload.vue';
|
|
import WorkflowResponsePanel from '@/components/WorkflowResponsePanel.vue';
|
|
import { KSDocumentService } from '@/service/KSDocumentService';
|
|
import { KSVideoService } from '@/service/KSVideoService';
|
|
import { FileUploadStore } from '@/stores/FileUploadStore';
|
|
import { KsVideoGroupStore } from '@/stores/KsVideoGroupStore';
|
|
import { LoadingStore } from '@/stores/LoadingStore';
|
|
import { ScenarioExecutionStore } from '@/stores/ScenarioExecutionStore';
|
|
import { UserPrefStore } from '@/stores/UserPrefStore';
|
|
import InputText from 'primevue/inputtext';
|
|
import MultiSelect from 'primevue/multiselect';
|
|
import Select from 'primevue/select';
|
|
import Textarea from 'primevue/textarea';
|
|
import { useToast } from 'primevue/usetoast';
|
|
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
|
import { useRoute } from 'vue-router';
|
|
|
|
// ============= Stores and Services =============
|
|
const loadingStore = LoadingStore();
|
|
const scenarioExecutionStore = ScenarioExecutionStore();
|
|
const fileUploadStore = FileUploadStore();
|
|
const ksVideoGroupStore = KsVideoGroupStore();
|
|
const userPrefStore = UserPrefStore();
|
|
const toast = useToast();
|
|
const route = useRoute();
|
|
|
|
// ============= Reactive State =============
|
|
const scenario = ref({});
|
|
const formData = ref({});
|
|
const exec_id = ref(null);
|
|
const scenario_response = ref(null);
|
|
const scenario_output = ref(null);
|
|
const scenario_response_message = ref(null);
|
|
const error_message = ref(null);
|
|
const data_loaded = ref(false);
|
|
const loading_data = ref(false);
|
|
const errored_execution = ref(false);
|
|
const chat_enabled = ref(false);
|
|
|
|
// ============= File Upload State =============
|
|
const folderName = ref('');
|
|
const uploadedFiles = ref([]);
|
|
const numberPrFiles = ref(0);
|
|
const acceptedFormats = ref('.docx');
|
|
const reqMultiFile = ref(false);
|
|
const uploadUrlBase = import.meta.env.VITE_BACKEND_URL;
|
|
const uploadUrlPR = computed(() => `${uploadUrlBase}/uploadListFiles/${folderName.value}/PR`);
|
|
const uploadUrlOther = computed(() => `${uploadUrlBase}/uploadListFiles/${folderName.value}/OTHER`);
|
|
|
|
// ============= Dynamic Options State =============
|
|
const loadingOptionsFor = reactive({});
|
|
const ksDocuments = ref([]);
|
|
const videoGroups = ref([]);
|
|
const ksFolders = ref([]);
|
|
const folderToItemsMap = ref({});
|
|
|
|
// ============= File Output State =============
|
|
const fileType = ref('');
|
|
const responsePanel = ref(null);
|
|
|
|
// ============= Polling State =============
|
|
let pollingInterval = null;
|
|
|
|
// ============= Computed Properties =============
|
|
const isInputFilled = computed(() => {
|
|
if (!scenario.value.inputs) {
|
|
return false;
|
|
}
|
|
|
|
return scenario.value.inputs.every((input) => {
|
|
const inputValue = formData.value[input.name];
|
|
|
|
if (input.type === 'multiselect') {
|
|
return inputValue && Array.isArray(inputValue) && inputValue.length > 0;
|
|
}
|
|
|
|
// For file uploads, check if the file has been uploaded
|
|
if (input.type === 'singlefile' || input.type === 'singlefile_acceptall') {
|
|
return inputValue !== undefined && inputValue !== '';
|
|
}
|
|
|
|
// For multifile, it's optional so always return true
|
|
if (input.type === 'multifile') {
|
|
return true;
|
|
}
|
|
|
|
return inputValue !== undefined && inputValue !== '';
|
|
});
|
|
});
|
|
|
|
// ============= Lifecycle Hooks =============
|
|
onMounted(() => {
|
|
fetchScenario(route.params.id);
|
|
const newFolderName = fileUploadStore.generateUniqueFolderId();
|
|
folderName.value = newFolderName;
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
stopPolling();
|
|
});
|
|
|
|
watch(() => route.params.id, fetchScenario);
|
|
|
|
// ============= Scenario Methods =============
|
|
async function fetchScenario(id) {
|
|
chatDisabled();
|
|
scenario.value.inputs = null;
|
|
data_loaded.value = false;
|
|
formData.value = {};
|
|
|
|
try {
|
|
const response = await scenarioExecutionStore.fetchScenario(id);
|
|
scenario.value = response;
|
|
|
|
await loadOptionsForScenario();
|
|
|
|
reqMultiFile.value = scenario.value.inputs?.some((input) => input.name === 'MultiFileUpload' || input.name === 'SingleFileUpload');
|
|
|
|
if (scenario.value.inputs?.some((input) => input.type === 'singlefile_acceptall')) {
|
|
acceptedFormats.value = '';
|
|
} else if (scenario.value.inputs?.some((input) => input.type === 'singlefile')) {
|
|
acceptedFormats.value = '.docx';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching scenario:', error);
|
|
}
|
|
}
|
|
|
|
// ============= Options Loading Methods =============
|
|
const loadVideoGroups = async () => {
|
|
await ksVideoGroupStore.fetchKsVideoGroup(userPrefStore.selectedProject.id);
|
|
videoGroups.value = [...(ksVideoGroupStore.ksVideoGroup || [])];
|
|
videoGroups.value = await Promise.all(videoGroups.value);
|
|
};
|
|
|
|
// Estrae tutte le folder/subfolder uniche dai documenti e video
|
|
const extractFoldersFromItems = (items) => {
|
|
const folderMap = {};
|
|
const folderSet = new Set();
|
|
|
|
items.forEach((item) => {
|
|
// Filtra solo gli items che hanno il campo ingestionStatusV2
|
|
if (!item.ingestionStatusV2) {
|
|
return;
|
|
}
|
|
|
|
const path = item.ingestionInfo?.metadata?.ksKnowledgePath;
|
|
|
|
// Gestisci item nella root (senza path o con path vuoto o "/")
|
|
if (!path || path === '/' || path.trim() === '') {
|
|
const rootPath = '/';
|
|
folderSet.add(rootPath);
|
|
|
|
if (!folderMap[rootPath]) {
|
|
folderMap[rootPath] = [];
|
|
}
|
|
|
|
folderMap[rootPath].push({
|
|
id: item.id,
|
|
type: item.fileName ? 'document' : 'video',
|
|
name: item.fileName || item.name
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Rimuovi il leading slash se presente
|
|
const cleanPath = path.startsWith('/') ? path.substring(1) : path;
|
|
if (!cleanPath) {
|
|
const rootPath = '/';
|
|
folderSet.add(rootPath);
|
|
|
|
if (!folderMap[rootPath]) {
|
|
folderMap[rootPath] = [];
|
|
}
|
|
|
|
folderMap[rootPath].push({
|
|
id: item.id,
|
|
type: item.fileName ? 'document' : 'video',
|
|
name: item.fileName || item.name
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Dividi il path in parti
|
|
const parts = cleanPath.split('/');
|
|
|
|
// Crea tutte le combinazioni di folder/subfolder
|
|
let currentPath = '';
|
|
parts.forEach((part, index) => {
|
|
currentPath = index === 0 ? part : `${currentPath}/${part}`;
|
|
const fullPath = `/${currentPath}`;
|
|
|
|
folderSet.add(fullPath);
|
|
|
|
if (!folderMap[fullPath]) {
|
|
folderMap[fullPath] = [];
|
|
}
|
|
|
|
if (fullPath === path) {
|
|
folderMap[fullPath].push({
|
|
id: item.id,
|
|
type: item.fileName ? 'document' : 'video',
|
|
name: item.fileName || item.name
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
return { folders: Array.from(folderSet).sort(), folderMap };
|
|
};
|
|
|
|
const loadKsFolders = async () => {
|
|
try {
|
|
// Carica sia documenti che video con query separate
|
|
const [docsResponse, videosResponse] = await Promise.all([
|
|
KSDocumentService.getKSDocuments(),
|
|
KSVideoService.getKSVideos()
|
|
]);
|
|
|
|
const documents = docsResponse.data || [];
|
|
const videos = videosResponse.data || [];
|
|
|
|
// Combina documenti e video per l'estrazione dei folder
|
|
// Facciamo distinct sui ksKnowledgePath
|
|
const allItems = [...documents, ...videos];
|
|
const { folders, folderMap } = extractFoldersFromItems(allItems);
|
|
|
|
// Calcola il numero di subfolder per ogni folder
|
|
const subfolderCounts = {};
|
|
folders.forEach((folderPath) => {
|
|
const subfolderCount = folders.filter((otherPath) => {
|
|
if (otherPath === folderPath) return false;
|
|
|
|
if (folderPath === '/') {
|
|
return otherPath !== '/' && otherPath.substring(1).indexOf('/') === -1;
|
|
}
|
|
|
|
const relativePath = otherPath.substring(folderPath.length);
|
|
return relativePath.startsWith('/') && relativePath.substring(1).split('/').length === 1;
|
|
}).length;
|
|
subfolderCounts[folderPath] = subfolderCount;
|
|
});
|
|
|
|
ksFolders.value = folders.map((path) => ({
|
|
id: path,
|
|
name: path === '/' ? '/ (Root) - ALL' : path,
|
|
path: path,
|
|
itemCount: folderMap[path]?.length || 0,
|
|
subfolderCount: subfolderCounts[path] || 0
|
|
}));
|
|
|
|
folderToItemsMap.value = folderMap;
|
|
|
|
console.log(`Loaded ${ksFolders.value.length} folders with items:`, folderToItemsMap.value);
|
|
} catch (error) {
|
|
console.error('Error loading KS folders:', error);
|
|
}
|
|
};
|
|
|
|
const loadOptionsForScenario = async () => {
|
|
if (!scenario.value.inputs) return;
|
|
|
|
const dataSources = new Set();
|
|
scenario.value.inputs.forEach((input) => {
|
|
if (input.type === 'multiselect' && input.dataSource) {
|
|
dataSources.add(input.dataSource);
|
|
}
|
|
});
|
|
|
|
const loadingPromises = Array.from(dataSources).map(async (dataSource) => {
|
|
try {
|
|
loadingOptionsFor[dataSource] = true;
|
|
|
|
switch (dataSource) {
|
|
case 'videoGroups':
|
|
await loadVideoGroups();
|
|
break;
|
|
|
|
case 'ksDocuments': {
|
|
const docsResponse = await KSDocumentService.getKSDocuments();
|
|
ksDocuments.value = docsResponse.data;
|
|
break;
|
|
}
|
|
|
|
case 'ksFolders':
|
|
if (videoGroups.value.length === 0) {
|
|
await loadVideoGroups();
|
|
}
|
|
await loadKsFolders();
|
|
break;
|
|
|
|
default:
|
|
console.warn(`Unknown dataSource: ${dataSource}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error loading options for ${dataSource}:`, error);
|
|
} finally {
|
|
loadingOptionsFor[dataSource] = false;
|
|
}
|
|
});
|
|
|
|
await Promise.all(loadingPromises);
|
|
};
|
|
|
|
const getOptionsForInput = (input) => {
|
|
switch (input.dataSource) {
|
|
case 'videoGroups':
|
|
return videoGroups.value;
|
|
case 'ksDocuments':
|
|
return ksDocuments.value;
|
|
case 'ksFolders':
|
|
return ksFolders.value;
|
|
default:
|
|
return [];
|
|
}
|
|
};
|
|
|
|
// ============= Input Component Methods =============
|
|
const getInputComponent = (type) => {
|
|
const components = {
|
|
text: InputText,
|
|
textarea: Textarea,
|
|
select: Select,
|
|
multiselect: MultiSelect
|
|
};
|
|
return components[type] || InputText;
|
|
};
|
|
|
|
const onDynamicPickerChange = (inputName, value, dataSource) => {
|
|
console.log(`Dynamic picker changed for ${inputName}:`, value);
|
|
|
|
// Gestione speciale per ksFolders: espande automaticamente le subfolder
|
|
if (dataSource === 'ksFolders' && Array.isArray(value)) {
|
|
const expandedSelection = new Set();
|
|
|
|
// Aggiungi tutte le folder selezionate dall'utente
|
|
value.forEach(folder => {
|
|
const folderPath = folder.path || folder.id || folder;
|
|
expandedSelection.add(folderPath);
|
|
|
|
// Trova e aggiungi tutte le subfolder ricorsivamente
|
|
ksFolders.value.forEach(subfolder => {
|
|
const subfolderPath = subfolder.path;
|
|
if (subfolderPath !== folderPath && subfolderPath.startsWith(folderPath + '/')) {
|
|
expandedSelection.add(subfolderPath);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Converti il Set in un array di oggetti folder completi
|
|
const expandedFolders = Array.from(expandedSelection).map(path =>
|
|
ksFolders.value.find(f => f.path === path)
|
|
).filter(f => f !== undefined);
|
|
|
|
console.log(`Expanded selection from ${value.length} to ${expandedFolders.length} folders`);
|
|
formData.value[inputName] = expandedFolders;
|
|
} else {
|
|
formData.value[inputName] = value;
|
|
}
|
|
};
|
|
|
|
// ============= File Upload Handlers =============
|
|
const handleFileUpload = (data) => {
|
|
console.log('handleFileUpload - data.fileName:', data.fileName);
|
|
console.log('handleFileUpload - data.response:', data.response);
|
|
console.log('handleFileUpload - data.inputName:', data.inputName);
|
|
|
|
if (data.inputName === 'SingleFileUpload') {
|
|
formData.value['SingleFileUpload'] = data.fileName;
|
|
formData.value['Folder'] = data.response;
|
|
numberPrFiles.value += 1;
|
|
} else {
|
|
formData.value['MultiFileUpload'] = data.response;
|
|
}
|
|
};
|
|
|
|
const handleFileRemove = (data) => {
|
|
if (data.inputName === 'SingleFileUpload') {
|
|
numberPrFiles.value -= 1;
|
|
formData.value['SingleFileUpload'] = '';
|
|
}
|
|
};
|
|
|
|
// ============= Execution Methods =============
|
|
const execScenario = async () => {
|
|
if (numberPrFiles.value !== 1 && reqMultiFile.value) {
|
|
toast.add({
|
|
severity: 'warn',
|
|
summary: 'Attention',
|
|
detail: 'You can upload only 1 PR file. Please remove others.'
|
|
});
|
|
return;
|
|
}
|
|
|
|
loading_data.value = true;
|
|
data_loaded.value = false;
|
|
loadingStore.exectuion_loading = true;
|
|
|
|
const processedData = processFormData();
|
|
const data = {
|
|
scenario_id: scenario.value.id,
|
|
inputs: processedData
|
|
};
|
|
|
|
try {
|
|
const response = await scenarioExecutionStore.executeScenario(data);
|
|
scenario_response.value = response;
|
|
scenario_response_message.value = response.message;
|
|
scenario_output.value = response.stringOutput;
|
|
exec_id.value = response.scenarioExecution_id;
|
|
loadingStore.setIdExecLoading(exec_id.value);
|
|
startPolling();
|
|
} catch (error) {
|
|
console.error('Error executing scenario:', error);
|
|
loadingStore.exectuion_loading = false;
|
|
}
|
|
};
|
|
|
|
const processFormData = () => {
|
|
const processedData = { ...formData.value };
|
|
|
|
if (scenario.value.inputs) {
|
|
scenario.value.inputs.forEach((input) => {
|
|
if (input.type === 'multiselect' && processedData[input.name]) {
|
|
const selectedItems = processedData[input.name];
|
|
|
|
if (Array.isArray(selectedItems) && selectedItems.length > 0) {
|
|
// Elaborazione speciale per ksFolders
|
|
if (input.dataSource === 'ksFolders') {
|
|
const allItemIds = [];
|
|
const allItemNames = [];
|
|
|
|
selectedItems.forEach((folder) => {
|
|
const folderPath = folder.path || folder.id || folder;
|
|
const items = folderToItemsMap.value[folderPath] || [];
|
|
|
|
items.forEach((item) => {
|
|
allItemIds.push(item.id);
|
|
allItemNames.push(item.name);
|
|
});
|
|
});
|
|
|
|
processedData[`${input.name}_id`] = JSON.stringify(allItemIds);
|
|
processedData[`${input.name}_name`] = JSON.stringify(allItemNames);
|
|
processedData[`${input.name}_folders`] = JSON.stringify(selectedItems.map((item) => item.path || item.id || item));
|
|
|
|
console.log(`Folder selection - Total items: ${allItemIds.length}`, allItemIds);
|
|
} else {
|
|
// Elaborazione standard per videoGroups e ksDocuments
|
|
processedData[`${input.name}_id`] = JSON.stringify(selectedItems.map((item) => item.id || item));
|
|
processedData[`${input.name}_name`] = JSON.stringify(selectedItems.map((item) => item.name || item.fileName || item));
|
|
}
|
|
|
|
delete processedData[input.name];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return processedData;
|
|
};
|
|
|
|
// ============= Polling Methods =============
|
|
const pollBackendAPI = async () => {
|
|
errored_execution.value = false;
|
|
|
|
try {
|
|
const response = await scenarioExecutionStore.getExecutionProgress(exec_id.value);
|
|
|
|
if (response.status === 'OK' || response.status === 'ERROR') {
|
|
stopPolling();
|
|
|
|
if (response.status === 'ERROR') {
|
|
errored_execution.value = true;
|
|
error_message.value = response.message;
|
|
}
|
|
|
|
loading_data.value = false;
|
|
data_loaded.value = true;
|
|
scenario_output.value = response.stringOutput;
|
|
exec_id.value = response.scenarioExecution_id;
|
|
scenario_response_message.value = null;
|
|
|
|
handleFileOutput(response);
|
|
} else {
|
|
scenario_response.value = response;
|
|
scenario_response_message.value = response.message;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error polling backend API:', error);
|
|
}
|
|
};
|
|
|
|
const handleFileOutput = (response) => {
|
|
if (scenario.value.inputs?.some((input) => input.name === 'MultiFileUpload')) {
|
|
if (response.status === 'OK') {
|
|
const firstStep = scenario.value.steps?.[0];
|
|
const outputType = firstStep?.attributes?.['codegenie_output_type'];
|
|
|
|
if (outputType === 'FILE') {
|
|
fileType.value = 'FILE';
|
|
} else if (outputType === 'MARKDOWN') {
|
|
fileType.value = 'MARKDOWN';
|
|
responsePanel.value?.showFileContent(scenario_output.value, 'MARKDOWN');
|
|
} else if (outputType === 'JSON') {
|
|
fileType.value = 'JSON';
|
|
responsePanel.value?.showFileContent(scenario_output.value, 'JSON');
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
function startPolling() {
|
|
pollingInterval = setInterval(pollBackendAPI, 2500);
|
|
}
|
|
|
|
function stopPolling() {
|
|
if (pollingInterval) {
|
|
clearInterval(pollingInterval);
|
|
pollingInterval = null;
|
|
}
|
|
loadingStore.exectuion_loading = false;
|
|
loadingStore.setIdExecLoading('');
|
|
}
|
|
|
|
// ============= Chat Methods =============
|
|
const chatEnabled = () => {
|
|
chat_enabled.value = true;
|
|
};
|
|
|
|
const chatDisabled = () => {
|
|
chat_enabled.value = false;
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="scenario-exec-container">
|
|
<!-- Enhanced Header Section -->
|
|
<div v-if="scenario.name" class="header-section">
|
|
<div class="header-content">
|
|
<div class="header-icon">
|
|
<i class="pi pi-bolt" style="font-size: 1.36rem"></i>
|
|
</div>
|
|
<div class="header-text">
|
|
<h1 class="page-title">{{ scenario.name }}</h1>
|
|
<p class="page-subtitle">{{ scenario.description }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chat Return Button -->
|
|
<div v-if="data_loaded && chat_enabled" class="chat-return-section">
|
|
<div class="return-card">
|
|
<Button label="Return to Scenario" @click="chatDisabled" size="large" iconPos="left" icon="pi pi-arrow-left" class="return-button" severity="secondary"></Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Scenario Form -->
|
|
<div v-else class="form-section">
|
|
<div class="form-card">
|
|
<!-- Hint Section -->
|
|
<div v-if="scenario.hint" class="hint-section">
|
|
<div class="hint-header">
|
|
<i class="pi pi-info-circle"></i>
|
|
<span>Scenario Information</span>
|
|
</div>
|
|
<MarkdownViewer class="hint-content" :modelValue="scenario.hint" />
|
|
</div>
|
|
|
|
<!-- Inputs Section -->
|
|
<template v-if="scenario.inputs">
|
|
<div class="inputs-section">
|
|
<div class="inputs-grid">
|
|
<div v-for="input in scenario.inputs" :key="input.name" class="input-item">
|
|
<!-- Single File Upload -->
|
|
<div v-if="input.type === 'singlefile' || input.type === 'singlefile_acceptall'" class="input-group">
|
|
<ScenarioFileUpload
|
|
:input-name="input.name"
|
|
:label="input.label"
|
|
tooltip-text="Upload one document from the suggested types. Mandatory if you want to execute scenario."
|
|
:upload-url="uploadUrlPR"
|
|
:is-multiple="false"
|
|
:accepted-formats="acceptedFormats"
|
|
:folder-name="folderName"
|
|
v-model:uploaded-files="uploadedFiles"
|
|
@upload="handleFileUpload"
|
|
@remove="handleFileRemove"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Multi File Upload -->
|
|
<div v-else-if="input.type === 'multifile'" class="input-group">
|
|
<ScenarioFileUpload
|
|
:input-name="input.name"
|
|
:label="input.label"
|
|
tooltip-text="Upload others documents of .docx, .msg, .text type. Optional."
|
|
:upload-url="uploadUrlOther"
|
|
:is-multiple="true"
|
|
accepted-formats=".msg,.txt,.docx"
|
|
:folder-name="folderName"
|
|
v-model:uploaded-files="uploadedFiles"
|
|
@upload="handleFileUpload"
|
|
@remove="handleFileRemove"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Multi Select with Dynamic Picker -->
|
|
<div v-else-if="input.type === 'multiselect'" class="input-group">
|
|
<DynamicPicker
|
|
v-model="formData[input.name]"
|
|
:input-name="input.name"
|
|
:label="input.label"
|
|
:data-source="input.dataSource || 'videoGroups'"
|
|
:options="getOptionsForInput(input)"
|
|
:disabled="loadingStore.exectuion_loading"
|
|
:loading="loadingOptionsFor[input.dataSource] || false"
|
|
:show-status="input.dataSource === 'ksDocuments'"
|
|
no-margin
|
|
@change="onDynamicPickerChange(input.name, $event, input.dataSource)"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Other Input Types -->
|
|
<div v-else class="input-group">
|
|
<label :for="input.name" class="input-label">
|
|
{{ input.label }}
|
|
</label>
|
|
<div class="input-wrapper">
|
|
<component :is="getInputComponent(input.type)" :id="input.name" v-model="formData[input.name]" :options="input.options" class="full-width-input" :disabled="loadingStore.exectuion_loading" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Action Buttons -->
|
|
<div class="action-buttons">
|
|
<div v-if="data_loaded && scenario.chatEnabled && !chat_enabled" class="button-group">
|
|
<Button
|
|
:disabled="loadingStore.exectuion_loading || !isInputFilled"
|
|
label="Execute Scenario"
|
|
@click="execScenario"
|
|
size="large"
|
|
iconPos="right"
|
|
icon="pi pi-play-circle"
|
|
class="execute-button"
|
|
severity="success"
|
|
></Button>
|
|
<Button label="Open Chat" @click="chatEnabled" size="large" iconPos="right" icon="pi pi-comments" severity="help"></Button>
|
|
</div>
|
|
<div v-else class="button-group">
|
|
<Button
|
|
:disabled="loadingStore.exectuion_loading || !isInputFilled"
|
|
label="Execute Scenario"
|
|
@click="execScenario"
|
|
size="large"
|
|
iconPos="right"
|
|
icon="pi pi-play-circle"
|
|
class="execute-button"
|
|
severity="success"
|
|
></Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Execution Timer -->
|
|
<div class="timer-section">
|
|
<ExecutionTimer :is-loading="loading_data" :message="scenario_response_message" />
|
|
</div>
|
|
|
|
<!-- Workflow Response Panel -->
|
|
<div v-if="data_loaded && !chat_enabled" class="response-section">
|
|
<WorkflowResponsePanel ref="responsePanel" :scenario="scenario" :scenario-output="scenario_output" :exec-id="exec_id" :error-message="error_message" :errored-execution="errored_execution" />
|
|
</div>
|
|
|
|
<!-- Enhanced Chat Panel -->
|
|
<div v-if="data_loaded && chat_enabled" class="chat-section">
|
|
<Panel class="chat-panel">
|
|
<template #header>
|
|
<div class="chat-header">
|
|
<i class="pi pi-comments chat-icon"></i>
|
|
<span class="chat-title">Chat with WizardAI</span>
|
|
</div>
|
|
</template>
|
|
<div class="chat-content">
|
|
<ChatClient :scenarioExecutionId="exec_id" />
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Container */
|
|
.scenario-exec-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
/* Enhanced Header Section */
|
|
.header-section {
|
|
margin-bottom: 2rem;
|
|
animation: fadeInDown 0.5s ease-out;
|
|
}
|
|
|
|
.header-content {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1.2rem;
|
|
padding: 1.36rem;
|
|
background: linear-gradient(135deg, #a100ff 0%, #7b00cc 100%);
|
|
border-radius: 16px;
|
|
box-shadow: 0 10px 30px rgba(161, 0, 255, 0.3);
|
|
color: white;
|
|
}
|
|
|
|
.header-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 48px;
|
|
height: 48px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 50%;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.header-text {
|
|
flex: 1;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 1.8rem !important;
|
|
font-weight: 700 !important;
|
|
margin: 0 0 0.5rem 0 !important;
|
|
color: white !important;
|
|
}
|
|
|
|
.page-subtitle {
|
|
font-size: 1.1rem;
|
|
margin: 0;
|
|
opacity: 0.95;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Chat Return Section */
|
|
.chat-return-section {
|
|
margin-bottom: 2rem;
|
|
animation: fadeIn 0.5s ease-out;
|
|
}
|
|
|
|
.return-card {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 2rem;
|
|
background: white;
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.return-card :deep(button) {
|
|
min-width: 250px;
|
|
justify-content: center;
|
|
}
|
|
|
|
.return-button {
|
|
min-width: 250px;
|
|
}
|
|
|
|
/* Form Section */
|
|
.form-section {
|
|
animation: fadeIn 0.5s ease-out;
|
|
}
|
|
|
|
.form-card {
|
|
background: white;
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Hint Section */
|
|
.hint-section {
|
|
padding: 2rem;
|
|
background: linear-gradient(135deg, rgba(161, 0, 255, 0.1) 0%, rgba(123, 0, 204, 0.1) 100%);
|
|
border-bottom: 2px solid #a100ff;
|
|
}
|
|
|
|
.hint-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 1rem;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
color: #a100ff;
|
|
}
|
|
|
|
.hint-header i {
|
|
font-size: 1.3rem;
|
|
}
|
|
|
|
.hint-content {
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
max-width: 100%;
|
|
overflow-x: auto;
|
|
background: transparent !important;
|
|
}
|
|
|
|
.hint-content :deep(*) {
|
|
background: transparent !important;
|
|
background-color: transparent !important;
|
|
}
|
|
|
|
.hint-content :deep(.markdown-content) {
|
|
background: transparent !important;
|
|
background-color: transparent !important;
|
|
}
|
|
|
|
.hint-content :deep(p),
|
|
.hint-content :deep(div),
|
|
.hint-content :deep(span),
|
|
.hint-content :deep(pre),
|
|
.hint-content :deep(code) {
|
|
background: transparent !important;
|
|
background-color: transparent !important;
|
|
}
|
|
|
|
/* Inputs Section */
|
|
.inputs-section {
|
|
padding: 2rem;
|
|
}
|
|
|
|
.inputs-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.inputs-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.input-item {
|
|
background: #f8f9fa;
|
|
padding: 1.5rem;
|
|
border-radius: 12px;
|
|
border: 1px solid #e2e8f0;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.input-item:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 6px 16px rgba(161, 0, 255, 0.15);
|
|
border-color: #a100ff;
|
|
}
|
|
|
|
.input-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
/* File Upload Specific Styling */
|
|
.input-item :deep(.p-fileupload) {
|
|
border: 2px dashed #cbd5e0;
|
|
border-radius: 8px;
|
|
background: white;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.input-item :deep(.p-fileupload:hover) {
|
|
border-color: #a100ff;
|
|
background: #f7fafc;
|
|
}
|
|
|
|
.input-item :deep(.p-fileupload-buttonbar) {
|
|
background: linear-gradient(135deg, #a100ff15 0%, #7b00cc15 100%);
|
|
border-bottom: 1px solid #e2e8f0;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.input-item :deep(.p-fileupload-content) {
|
|
padding: 1.5rem;
|
|
background: white;
|
|
}
|
|
|
|
/* Upload area styling */
|
|
.input-item :deep(.p-fileupload-choose) {
|
|
background: #a100ff;
|
|
border-color: #a100ff;
|
|
}
|
|
|
|
.input-item :deep(.p-fileupload-choose:hover) {
|
|
background: #5568d3;
|
|
border-color: #5568d3;
|
|
}
|
|
|
|
/* DynamicPicker Component Styling */
|
|
.input-item :deep(.dynamic-picker-label) {
|
|
font-weight: 600;
|
|
font-size: 1rem;
|
|
color: #2d3748;
|
|
margin-bottom: 0.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.input-item :deep(.dynamic-picker-label::before) {
|
|
content: '•';
|
|
color: #a100ff;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.input-label {
|
|
font-weight: 600;
|
|
font-size: 1rem;
|
|
color: #2d3748;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.input-label::before {
|
|
content: '•';
|
|
color: #a100ff;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.input-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.full-width-input {
|
|
width: 100%;
|
|
transition: all 0.3s ease;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.full-width-input:focus {
|
|
transform: scale(1.01);
|
|
box-shadow: 0 0 0 3px rgba(161, 0, 255, 0.1);
|
|
}
|
|
|
|
/* Style for all input fields within input-group */
|
|
.input-group :deep(.p-inputtext),
|
|
.input-group :deep(.p-textarea),
|
|
.input-group :deep(.p-dropdown),
|
|
.input-group :deep(.p-multiselect) {
|
|
background: white;
|
|
border: 1px solid #cbd5e0;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.input-group :deep(.p-inputtext:hover),
|
|
.input-group :deep(.p-textarea:hover),
|
|
.input-group :deep(.p-dropdown:hover),
|
|
.input-group :deep(.p-multiselect:hover) {
|
|
border-color: #a100ff;
|
|
}
|
|
|
|
.input-group :deep(.p-inputtext:focus),
|
|
.input-group :deep(.p-textarea:focus),
|
|
.input-group :deep(.p-dropdown:focus),
|
|
.input-group :deep(.p-multiselect:focus) {
|
|
border-color: #a100ff;
|
|
box-shadow: 0 0 0 3px rgba(161, 0, 255, 0.1);
|
|
}
|
|
|
|
/* Action Buttons */
|
|
.action-buttons {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding-top: 2rem;
|
|
border-top: 2px solid #e2e8f0;
|
|
}
|
|
|
|
.button-group {
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
|
|
.button-group :deep(button) {
|
|
min-width: 200px;
|
|
font-weight: 600;
|
|
justify-content: center;
|
|
}
|
|
|
|
.execute-button {
|
|
min-width: 200px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Timer Section */
|
|
.timer-section {
|
|
margin: 2rem 0;
|
|
}
|
|
|
|
/* Response Section */
|
|
.response-section {
|
|
margin-top: 2rem;
|
|
animation: fadeInUp 0.5s ease-out;
|
|
}
|
|
|
|
.response-section :deep(.card),
|
|
.response-section :deep(.p-panel) {
|
|
background: white;
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.response-section :deep(.p-panel-header) {
|
|
background: linear-gradient(135deg, #a100ff15 0%, #7b00cc15 100%);
|
|
border-bottom: 2px solid #a100ff;
|
|
padding: 1.5rem 2rem;
|
|
}
|
|
|
|
/* Chat Section */
|
|
.chat-section {
|
|
margin-top: 2rem;
|
|
animation: fadeInUp 0.5s ease-out;
|
|
}
|
|
|
|
.chat-panel {
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
overflow: hidden;
|
|
background: white;
|
|
}
|
|
|
|
.chat-panel :deep(.p-panel-header) {
|
|
background: linear-gradient(135deg, #a100ff15 0%, #7b00cc15 100%);
|
|
border-bottom: 2px solid #a100ff;
|
|
padding: 1.5rem 2rem;
|
|
}
|
|
|
|
.chat-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
font-size: 1.2rem;
|
|
font-weight: 600;
|
|
color: #a100ff;
|
|
}
|
|
|
|
.chat-icon {
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
.chat-title {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.chat-content {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
/* Animations */
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes fadeInDown {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes fadeInUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Responsive Design */
|
|
@media (max-width: 768px) {
|
|
.scenario-exec-container {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.header-content {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.scenario-title {
|
|
font-size: 1.4rem !important;
|
|
}
|
|
|
|
.scenario-description {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.inputs-section {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.button-group {
|
|
width: 100%;
|
|
}
|
|
|
|
.button-group button {
|
|
flex: 1;
|
|
min-width: auto;
|
|
}
|
|
}
|
|
|
|
/* Loading State */
|
|
.full-width-input:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
</style>
|