Files
hermione-fe/src/views/pages/ScenarioExecHistory.vue
Andrea Terzani af8a8b67c3 feat: Add Execution Response Section component and related composables for file handling and error management
- Implemented ExecutionResponseSection.vue to display execution results and handle file downloads.
- Created useBase64Decoder.js for base64 decoding utilities.
- Developed useChatToggle.js for managing chat panel state.
- Added useErrorHandler.js for standardized error handling with toast notifications.
- Introduced useFileDownload.js for various file download operations.
- Created useFileProcessing.js for processing files, including zip extraction and content display.
- Implemented usePolling.js for polling backend API for execution status.
- Added useScenarioRating.js for managing scenario execution ratings.
- Developed CSV export utilities in csvExport.js for generating and downloading CSV files.
- Created formDataProcessor.js for processing and validating multiselect form data.
- Implemented inputComponents.js for mapping input types to PrimeVue components.
- Enhanced ScenarioExecHistory.vue to integrate new components and functionalities.
2025-12-12 19:28:17 +01:00

365 lines
10 KiB
Vue

<script setup>
import ExecutionChatSection from '@/components/ExecutionChatSection.vue';
import ExecutionInputSection from '@/components/ExecutionInputSection.vue';
import ExecutionResponseSection from '@/components/ExecutionResponseSection.vue';
import { useChatToggle } from '@/composables/useChatToggle';
import { useFileDownload } from '@/composables/useFileDownload';
import { useFileProcessing } from '@/composables/useFileProcessing';
import { ScenarioService } from '@/service/ScenarioService.js';
import { LoadingStore } from '@/stores/LoadingStore.js';
import { ScenarioExecutionStore } from '@/stores/ScenarioExecutionStore.js';
import ProgressSpinner from 'primevue/progressspinner';
import { onMounted, ref } from 'vue';
// ============= Stores and Services =============
const loadingStore = LoadingStore();
const scenarioExecutionStore = ScenarioExecutionStore();
// ============= Composables =============
const { extractFiles, showFileContent } = useFileProcessing();
const { downloadFile, downloadCodegenieFile } = useFileDownload();
const { chatEnabled, enableChat, disableChat } = useChatToggle();
// ============= Reactive State =============
const scenario = ref({});
const exec_scenario = ref({});
const scenario_output = ref(null);
const inputs = ref(null);
const execution_id = ref(null);
const rating = ref(null);
const loading = ref(false);
const data_loaded = ref(false);
// ============= Lifecycle Hooks =============
onMounted(() => {
const execution = scenarioExecutionStore.getSelectedExecScenario;
if (execution) {
execution_id.value = execution.id;
} else {
const url = window.location.href;
execution_id.value = new URL(url).searchParams.get('id');
}
retrieveScenarioExec(execution_id.value);
});
// ============= Data Fetching Methods =============
const retrieveScenarioExec = async (id) => {
loading.value = true;
try {
const response = await ScenarioService.getScenarioExecutionById(id);
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;
inputs.value = response.data.scenarioExecutionInput.inputs;
// Handle file processing for MultiFileUpload scenarios
await handleFileProcessing();
} catch (error) {
console.error('Error retrieving scenario execution:', error);
} finally {
loading.value = false;
}
};
// ============= File Processing Methods =============
const handleFileProcessing = async () => {
if (inputs.value['MultiFileUpload'] && scenario.value.steps?.[0]?.attributes?.['codegenie_output_type']) {
try {
// Extract input files
await extractFiles(inputs.value['MultiFileUpload'], 'input');
const outputType = scenario.value.steps[0].attributes['codegenie_output_type'];
// Show file content for MARKDOWN or JSON types
if (outputType === 'MARKDOWN' || outputType === 'JSON') {
showFileContent(scenario_output.value, outputType);
}
} catch (error) {
console.error('Error processing files:', error);
}
}
};
// ============= Download Methods =============
const handleDownloadFile = async (filePath) => {
await downloadFile(filePath, execution_id.value);
};
const handleDownloadCodegenieFile = (base64String) => {
downloadCodegenieFile(base64String, execution_id.value);
};
// ============= Rating Methods =============
const handleRatingUpdate = (newRating) => {
rating.value = newRating;
};
</script>
<template>
<div class="scenario-history-container">
<!-- Loading Spinner -->
<div v-if="loading" class="loading-section">
<ProgressSpinner style="width: 50px; height: 50px" strokeWidth="3" fill="transparent" />
</div>
<!-- Main Content -->
<div v-if="data_loaded">
<!-- Enhanced Header Section -->
<div class="header-section">
<div class="header-content">
<div class="header-icon">
<i class="pi pi-history" style="font-size: 1.36rem"></i>
</div>
<div class="header-text">
<h1 class="page-title">{{ scenario.name }}</h1>
<p class="page-subtitle">Execution History</p>
</div>
</div>
</div>
<!-- Execution Input Section -->
<div class="input-section">
<ExecutionInputSection :execution-id="execution_id" :inputs="inputs" :scenario="scenario" :exec-scenario="exec_scenario" :rating="rating" :show-rating="true" @download-file="handleDownloadFile" @rating-updated="handleRatingUpdate" />
</div>
<!-- Chat Toggle Button -->
<div v-if="scenario.chatEnabled && exec_scenario.latestStepStatus !== 'ERROR'" class="chat-toggle-section">
<div class="toggle-card">
<div v-if="!chatEnabled" class="button-group">
<Button label="Open Chat" @click="enableChat" size="large" iconPos="right" icon="pi pi-comments" severity="help" />
</div>
<div v-else class="button-group">
<Button label="Close Chat" @click="disableChat" size="large" iconPos="left" icon="pi pi-times" severity="help" />
</div>
</div>
</div>
<!-- Workflow Response Section -->
<div v-if="!chatEnabled" class="response-section">
<ExecutionResponseSection
:scenario="scenario"
:exec-scenario="exec_scenario"
:scenario-output="scenario_output"
:execution-id="execution_id"
:is-loading="loadingStore.exectuion_loading && loadingStore.getExecIdLoading === execution_id"
mode="history"
@download-file="handleDownloadCodegenieFile"
/>
</div>
<!-- Enhanced Chat Section -->
<div v-if="chatEnabled" class="chat-section">
<ExecutionChatSection :execution-id="execution_id" :scenario-name="scenario.name" />
</div>
</div>
</div>
</template>
<style scoped>
/* Container */
.scenario-history-container {
max-width: 1400px;
margin: 0 auto;
padding: 1.5rem;
}
/* Loading Section */
.loading-section {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
animation: fadeIn 0.5s ease-out;
}
/* 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;
}
/* Input Section */
.input-section {
margin-bottom: 2rem;
animation: fadeIn 0.5s ease-out;
}
.input-section :deep(.card),
.input-section :deep(.p-panel) {
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.input-section :deep(.p-panel-header) {
background: linear-gradient(135deg, #a100ff15 0%, #7b00cc15 100%);
border-bottom: 2px solid #a100ff;
padding: 1.5rem 2rem;
}
/* Chat Toggle Section */
.chat-toggle-section {
margin-bottom: 2rem;
animation: fadeIn 0.5s ease-out;
}
.toggle-card {
display: flex;
justify-content: center;
padding: 1.5rem;
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.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;
}
/* 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;
}
/* 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-history-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;
}
.button-group {
width: 100%;
}
.button-group button {
flex: 1;
min-width: auto;
}
}
</style>