Canvas feature release 1

This commit is contained in:
andrea.terzani
2024-08-09 11:47:36 +02:00
parent acc5771bbf
commit 9bcab57044
12 changed files with 1651 additions and 21 deletions

1151
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,17 @@
"dependencies": {
"@heroicons/vue": "^2.1.5",
"@primevue/themes": "^4.0.0",
"@vavt/v3-extension": "^1.2.4",
"@websanova/vue-auth": "^4.2.1",
"axios": "^1.7.2",
"chart.js": "3.3.2",
"Hermione": "file:",
"json-editor-vue": "^0.15.1",
"md-editor-v3": "^4.18.0",
"primeicons": "^6.0.1",
"primevue": "^4.0.0",
"quill": "^1.3.7",
"showdown": "^2.1.0",
"vue": "^3.4.34",
"vue-authenticate-2": "^2.2.0",
"vue-markdown-render": "^2.2.1",

View File

@@ -20,8 +20,8 @@ const containerClass = computed(() => {
return {
'layout-theme-light': !layoutConfig.darkTheme,
'layout-theme-dark': layoutConfig.darkTheme,
//'layout-overlay': layoutConfig.menuMode === 'overlay',
//'layout-static': layoutConfig.menuMode === 'static',
//'layout-overlay': layoutConfig.menuMode === 'overlay',
// 'layout-static': layoutConfig.menuMode === 'static',
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
'layout-overlay-active': layoutState.overlayMenuActive,
'layout-mobile-active': layoutState.staticMenuMobileActive
@@ -54,9 +54,9 @@ const isOutsideClicked = (event) => {
<template>
<div class="layout-wrapper" :class="containerClass">
<app-topbar></app-topbar>
<!-- <div class="layout-sidebar">
<!-- <div class="layout-sidebar">
<app-sidebar></app-sidebar>
</div>-->
</div>-->
<div class="layout-main-container">
<div class="layout-main">
<router-view></router-view>

View File

@@ -6,8 +6,10 @@ import AppMenuItem from './AppMenuItem.vue';
const model = ref([
{
label: 'Scenarios',
items: [{ label: 'Scenario List', icon: 'pi pi-fw pi-id-card', to: '/scenario-list' }
]
items: [{ label: 'Scenario List', icon: 'pi pi-fw pi-id-card', to: '/scenario-list' }]
}, {
label: 'Canvas',
items: [{ label: 'New Canvas', icon: 'pi pi-fw pi-id-card', to: '/canvas' }]
}
]);
</script>

View File

@@ -1,10 +1,14 @@
<script setup>
import { useLayout } from '@/layout/composables/layout';
import { useAuth } from '@websanova/vue-auth/src/v3.js';
import { useRouter } from 'vue-router';
import AppConfigurator from './AppConfigurator.vue';
import AppProfileMenu from './AppProfileMenu.vue';
const auth = useAuth();
const router = useRouter();
const { onMenuToggle, toggleDarkMode, isDarkTheme } = useLayout();
</script>
@@ -33,16 +37,23 @@ const { onMenuToggle, toggleDarkMode, isDarkTheme } = useLayout();
<span>HERMIONE</span>
</router-link>
</div>
<!--
<button class="layout-menu-button layout-topbar-action" @click="onMenuToggle">
<!-- <button class="layout-menu-button layout-topbar-action" @click="onMenuToggle">
<i class="pi pi-bars"></i>
</button>
-->
<div class="layout-topbar-actions">
<div class="layout-config-menu">
<button @click="router.push('/canvas')" class="layout-topbar-action" >
<i class="pi pi-file-edit"></i>
</button >
<button @click="router.push('/mdcanvas')" class="layout-topbar-action" >
<i class="pi pi-pencil"></i>
</button >
<button type="button" class="layout-topbar-action" @click="toggleDarkMode">
<i :class="['pi', { 'pi-moon': isDarkTheme, 'pi-sun': !isDarkTheme }]"></i>
</button>
<div class="relative">
<button
v-styleclass="{ selector: '@next', enterFromClass: 'hidden', enterActiveClass: 'animate-scalein', leaveToClass: 'hidden', leaveActiveClass: 'animate-fadeout', hideOnOutsideClick: true }"

View File

@@ -17,7 +17,15 @@ import BlockViewer from '@/components/BlockViewer.vue';
import '@/assets/styles.scss';
import '@/assets/tailwind.css';
import { config } from 'md-editor-v3';
config({
editorConfig: {
renderDelay: 0,
zIndex: 200000000
}
});
var auth = createAuth({
plugins: {
http: axios,
@@ -38,7 +46,7 @@ var auth = createAuth({
}
});
axios.defaults.baseURL = import.meta.env.VITE_BACKEND_URL;
axios.defaults.baseURL ='https://hermione-nu6mvqujsq-ey.a.run.app/' // import.meta.env.VITE_BACKEND_URL;
const app = createApp(App);

View File

@@ -24,6 +24,16 @@ const router = createRouter({
path: 'exec/:id',
name: 'scenario-exec',
component: () => import('@/views/pages/ScenarioExec.vue')
},
{
path: '/canvas',
name: 'canvas',
component: () => import('@/views/pages/canvas/Canvas.vue')
},
{
path: '/mdcanvas',
name: 'mdcanvas',
component: () => import('@/views/pages/canvas/MdCanvas.vue')
}
]
}

View File

@@ -64,7 +64,7 @@
<Panel class="mt-6">
<template #header>
<div class="flex items-center gap-2">
<span class="font-bold">AI Response</span>
<span class="font-bold">Hermione Response</span>
</div>
</template>
<template #icons>
@@ -75,14 +75,12 @@
</div>
</template>
<div class="flex mt-2">
<div class="card flex flex-col gap-4 w-full">
<div v-if="scenario_output != null">
<vue-markdown :source="scenario_output" />
</div>
<div v-if="scenario_output != null">
<MdPreview class="editor" v-model="scenario_output" language="en-US" />
</div>
</div>
</Panel>
</Panel>
<Dialog v-model:visible="debug_modal" maximizable modal :header="scenario.name" :style="{ width: '75%' }" :breakpoints="{ '1199px': '75vw', '575px': '90vw' }">
<div class="flex">
@@ -107,8 +105,10 @@ import ProgressSpinner from 'primevue/progressspinner';
import Select from 'primevue/select';
import Textarea from 'primevue/textarea';
import { onMounted, ref } from 'vue';
import VueMarkdown from 'vue-markdown-render';
import { useRoute, useRouter } from 'vue-router';
import { MdPreview } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
const router = useRouter();
const route = useRoute();
@@ -205,4 +205,13 @@ onMounted(() => {
width: 100%;
}
.editor ol {
list-style-type: decimal !important;
}
.editor ul {
list-style-type: disc !important;
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<div className="card">
<Dialog v-model:visible="execScenarioDialogVisible" modal :header="execScenario.name" :style="{ width: '70rem' }">
<ScenarioExctuotionDialog :scenario="execScenario" @add="addFromDialog"/>
</Dialog>
<Editor v-model="content" editorStyle="height: 600px" @contextmenu="onRightClick" @selection-change="onSelectionChange" />
<ContextMenu ref="menu" :model="items" :style="{'white-space':'nowrap', 'width': 'auto'}" />
</div>
</template>
<script setup>
import { ref ,computed} from 'vue';
import { onMounted } from 'vue';
import { ScenarioService } from '@/service/ScenarioService.js';
import ScenarioExctuotionDialog from '@/views/pages/canvas/ScenarioExecutionDialog.vue';
const menu = ref();
const currentSelection = ref(null);
const currentPosition = ref(null);
const content = ref('Welcome to Olympus Canvas....');
const scenarios = ref([]);
const items = ref([]);
const execScenarioDialogVisible = ref(false);
const execScenario = ref({name:""});
const addFromDialog = (data) => {
console.log(data);
execScenarioDialogVisible.value = false;
content.value = content.value.substring(0, currentPosition.value) + data + content.value.substring(currentPosition.value);
}
const onRightClick = (event) => {
if(currentSelection.value && currentSelection.value.length > 0) {
items.value=[
{label: 'Summarize',icon: 'pi pi-language'},
{label: 'Rewrite', icon: 'pi pi-language'}
]
}else {
items.value=[
{ label: 'Ask Hermione', icon: 'pi pi-language' },
{ separator: true},
{ label: 'Execute Scenario', icon: 'pi pi-volume-up',items: scenarioMenuItems}
]
}
menu.value.show(event);
};
const onSelectionChange = (event) => {
if(event.range.length === 0){
currentSelection.value = null;
} else{
currentSelection.value = event.textValue;
}
currentPosition.value = event.range.index;
};
const scenarioMenuItems = computed(() => {
const scenario_items = [];
for (let i = 0; i < scenarios.value.length; i++) {
scenario_items.push({
label: scenarios.value[i].name,
icon: 'pi pi-file',
command: () => {
executeScenario(scenarios.value[i]);
}
});
}
return scenario_items;
});
const executeScenario = (scenario) => {
console.log(scenario.id);
execScenarioDialogVisible.value = true;
execScenario.value = scenario;
}
onMounted(() => {
ScenarioService.getScenarios().then(resp=>{
scenarios.value = resp.data
})
});
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,147 @@
<template>
<div className="card">
<Dialog v-model:visible="execScenarioDialogVisible" modal :header="execScenario.name" :style="{ width: '70rem' }">
<MdScenarioExctuotionDialog :scenario="execScenario" @add="addFromDialog"/>
</Dialog>
<ContextMenu ref="menu" :model="items" :style="{'white-space':'nowrap', 'width': 'auto'}"/>
<MdEditor class="editor" v-model="content" language="en-US" ref="editor" @contextmenu="onRightClick" previewTheme="github" :toolbars="toolbars">
<template #defToolbars>
<Mark />
<Emoji />
<ExportPDF :modelValue="content" height="700px" />
</template>
</MdEditor>
</div>
</template>
<script setup>
import { ref ,computed} from 'vue';
import { onMounted } from 'vue';
import { ScenarioService } from '@/service/ScenarioService.js';
import MdScenarioExctuotionDialog from '@/views/pages/canvas/MdScenarioExecutionDialog.vue';
import { MdEditor,NormalToolbar ,ModalToolbar } from 'md-editor-v3';
import { Emoji, Mark, ExportPDF } from '@vavt/v3-extension';
import '@vavt/v3-extension/lib/asset/style.css';
import 'md-editor-v3/lib/style.css';
const menu = ref();
const currentSelection = ref(null);
const currentPosition = ref(null);
const content = ref('# Welcome to Olympus Canvas....');
const scenarios = ref([]);
const items = ref([]);
const execScenarioDialogVisible = ref(false);
const execScenario = ref({name:""});
const editor = ref();
const addFromDialog = (data) => {
console.log(data);
execScenarioDialogVisible.value = false;
editor.value.insert((selectedText) => {
return {
targetValue: data,
select: true,
deviationStart: 0,
deviationEnd: 0,
};
});
}
const onRightClick = (event) => {
if(editor.value.getSelectedText().length > 0) {
items.value=[
{label: 'Summarize',icon: 'pi pi-language'},
{label: 'Rewrite', icon: 'pi pi-language'}
]
}else {
items.value=[
{ label: 'Ask Hermione', icon: 'pi pi-language' },
{ separator: true},
{ label: 'Execute Scenario', icon: 'pi pi-volume-up',items: scenarioMenuItems}
]
}
menu.value.show(event);
};
const scenarioMenuItems = computed(() => {
const scenario_items = [];
for (let i = 0; i < scenarios.value.length; i++) {
scenario_items.push({
label: scenarios.value[i].name,
icon: 'pi pi-file',
command: () => {
executeScenario(scenarios.value[i]);
}
});
}
return scenario_items;
});
const executeScenario = (scenario) => {
console.log(scenario.id);
execScenarioDialogVisible.value = true;
execScenario.value = scenario;
}
onMounted(() => {
ScenarioService.getScenarios().then(resp=>{
scenarios.value = resp.data
})
});
const toolbars = ref([
'bold',
'underline',
'italic',
'strikeThrough',
'-',
'title',
'sub',
'sup',
'quote',
'unorderedList',
'orderedList',
'task',
'-',
'codeRow',
'code',
'link',
'image',
'table',
'mermaid',
'katex',
0,
1,
2,
3,
'-',
'revoke',
'next',
'save',
'=',
'prettier',
'preview',
'previewOnly',
'htmlPreview'
]);
</script>
<style >
.editor ol {
list-style-type: decimal !important;
}
.editor ul {
list-style-type: disc !important;
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div>
<div v-if="!data_loaded && !loading_data" class="card flex flex-col gap-4 p-1">
<div v-for="input in scenario.inputs" :key="input.name" class="flex flex-col gap-2" >
<label :for="input.name">{{ input.label }}</label>
<component
:is="getInputComponent(input.type)"
:id="input.name"
v-model="inputData[input.name]"
class="full-width-input"
/>
</div>
<div class="flex justify-center">
<Button label="Submit" @click="execScenario" class=""/>
</div>
</div>
<ProgressSpinner v-if="loading_data" style="width: 50px; height: 50px" strokeWidth="8" fill="transparent"
animationDuration=".5s" aria-label="Custom ProgressSpinner" />
<div v-if="data_loaded" class="card flex flex-col gap-4 p-1">
<div class="flex items-center gap-2 p-0">
<button class="p-button p-button-primary" @click="addToCanvas">Add to Canvas</button>
</div>
<div class="flex mt-2">
<MdPreview class="editor" v-model="scenario_output" language="en-US" />
</div>
</div>
</div>
</template>
<script setup>
import { ref ,defineEmits} from 'vue';
import InputText from 'primevue/inputtext';
import ProgressSpinner from 'primevue/progressspinner';
import Select from 'primevue/select';
import Textarea from 'primevue/textarea';
import axios from 'axios';
import { MdEditor } from 'md-editor-v3';
import { MdPreview, MdCatalog } from 'md-editor-v3';
import 'md-editor-v3/lib/preview.css';
const loading_data = ref(false);
const data_loaded = ref(false);
const props = defineProps(['scenario']);
const emit = defineEmits(['add'])
const inputData = ref({});
const scenario_output = ref("");
const getInputComponent = (type) => {
switch (type) {
case 'text':
return InputText;
case 'textarea':
return Textarea;
case 'select':
return Select;
default:
return InputText;
}
};
const execScenario = () => {
loading_data.value = true;
data_loaded.value = false;
const data = {
scenario_id: props.scenario.id,
inputs: { ...inputData.value }
};
console.log(data);
axios.post('/scenarios/execute', data)
.then(response => {
loading_data.value = false;
data_loaded.value = true;
console.log(response);
scenario_output.value = response.data.stringOutput;
})
.catch(error => {
console.error('Error executing scenario:', error);
});
};
const addToCanvas = () => {
console.log("Aggiunto");
emit('add',scenario_output.value)
}
</script>
<style >
.editor ol {
list-style-type: decimal !important;
}
.editor ul {
list-style-type: disc !important;
}
</style>

View File

@@ -0,0 +1,99 @@
<template>
<div>
<div v-if="!data_loaded && !loading_data" class="card flex flex-col gap-4 p-1">
<div v-for="input in scenario.inputs" :key="input.name" class="flex flex-col gap-2" >
<label :for="input.name">{{ input.label }}</label>
<component
:is="getInputComponent(input.type)"
:id="input.name"
v-model="inputData[input.name]"
class="full-width-input"
/>
</div>
<div class="flex justify-center">
<Button label="Submit" @click="execScenario" class=""/>
</div>
</div>
<ProgressSpinner v-if="loading_data" style="width: 50px; height: 50px" strokeWidth="8" fill="transparent"
animationDuration=".5s" aria-label="Custom ProgressSpinner" />
<div v-if="data_loaded" class="card flex flex-col gap-4 p-1">
<div class="flex items-center gap-2 p-0">
<button class="p-button p-button-primary" @click="addToCanvas">Add to Canvas</button>
</div>
<div class="flex mt-2">
<Editor v-model="scenario_output" editorStyle="height: 320px" />
</div>
</div>
</div>
</template>
<script setup>
import { ref ,defineEmits} from 'vue';
import InputText from 'primevue/inputtext';
import ProgressSpinner from 'primevue/progressspinner';
import Select from 'primevue/select';
import Textarea from 'primevue/textarea';
import axios from 'axios';
import showdown from 'showdown';
const loading_data = ref(false);
const data_loaded = ref(false);
const props = defineProps(['scenario']);
const emit = defineEmits(['add'])
const inputData = ref({});
const scenario_output = ref("Questo è un testo di prova");
const testConverter=()=>{
var converter = new showdown.Converter(),
text = '# hello, markdown!',
html = converter.makeHtml(text);
console.log(html);
}
const getInputComponent = (type) => {
switch (type) {
case 'text':
return InputText;
case 'textarea':
return Textarea;
case 'select':
return Select;
default:
return InputText;
}
};
const execScenario = () => {
loading_data.value = true;
data_loaded.value = false;
const data = {
scenario_id: props.scenario.id,
inputs: { ...inputData.value }
};
console.log(data);
axios.post('/scenarios/execute', data)
.then(response => {
loading_data.value = false;
data_loaded.value = true;
console.log(response);
var converter = new showdown.Converter();
scenario_output.value = converter.makeHtml(response.data.stringOutput);
})
.catch(error => {
console.error('Error executing scenario:', error);
});
};
const addToCanvas = () => {
console.log("Aggiunto");
emit('add',scenario_output.value)
}
</script>