add KnowledgeWorkFlow system

This commit is contained in:
Robin COuret
2026-03-08 01:33:21 +01:00
parent 73fff0955b
commit d1e5a6b0c7
25 changed files with 351 additions and 174 deletions

View File

@@ -21,14 +21,4 @@ import AppTopbar from '@/components/AppTopbar.vue'
padding-inline: 5%;
height: 100vh;
}
/* main {
height: 100%;
}
@media screen and (min-width: 768px) {
#app {
height: 100vh;
}
} */
</style>

View File

@@ -1,11 +1,10 @@
<script setup lang="ts">
import { BField, BInput, BButton, useToast } from "buefy";
//import { apiClient } from "@/services/api";
import api from "@/services/apiAxios"
import type { AxiosResponse } from "axios";
import { ref } from "vue";
import { BField, BInput, BButton, useToast } from "buefy"
import api from "@/services/api"
import type { AxiosResponse } from "axios"
import { ref } from "vue"
import type { Knowledge, KnowledgeCreate } from "@/types/types";
import type { Knowledge, KnowledgeCreate } from "@/types/types"
import { useStepStore } from '@/stores/step'
import { useItemStore } from '@/stores/item'
@@ -45,8 +44,8 @@
<template>
<div class="container">
<h2>Collect Knowledge</h2>
<b-field label="Knowledge">
<h2>Ajouter une connaissance</h2>
<b-field label="Connaissance">
<!-- @vue-ignore -->
<b-input
v-model="knowledgeModel"
@@ -68,7 +67,7 @@
<div class="btn-container">
<b-field>
<div class="control">
<b-button type="is-primary" @click="postKnowledge" >Share</b-button>
<b-button type="is-primary" @click="postKnowledge" >Partager</b-button>
</div>
</b-field>
</div>

View File

@@ -1,35 +1,43 @@
<script setup lang="ts">
import { ref } from "vue";
import { onBeforeMount } from 'vue'
import { onBeforeMount, ref, watch } from "vue";
import type { Knowledge, Question, MetricCreate } from "@/types/types"
import type { Knowledge, Question, MetricCreate, Metric } from "@/types/types"
//import { useStepStore } from '@/stores/step'
import { useStepStore } from '@/stores/step'
import { useItemStore } from '@/stores/item'
import api from "@/services/apiAxios"
import api from "@/services/api"
import type { AxiosResponse } from "axios";
//const stepStore = useStepStore()
const stepStore = useStepStore()
const itemStore = useItemStore()
const questions = ref<Question[]>()
const metrics = ref<MetricCreate[]>([])
const metrics = ref<Metric[]>([])
onBeforeMount(async () => {
if(itemStore.knowledge != undefined){
questions.value = await getQuestions(itemStore.knowledge)
}
else{
if(!itemStore.knowledge){
throw new Error("There is no knowledge element in itemStore.");
}
if(questions.value != undefined){
initializeMetrics(questions.value)
}
else{
questions.value = await getQuestions(itemStore.knowledge)
if(!questions.value){
throw new Error("There is no questions element from API.");
}
initializeMetrics(questions.value)
})
watch( () => itemStore.knowledge, async () =>{
metrics.value = []
if(!itemStore.knowledge){
throw new Error("There is no knowledge element in itemStore.");
}
questions.value = await getQuestions(itemStore.knowledge)
if(!questions.value){
throw new Error("There is no questions element from API.");
}
initializeMetrics(questions.value)
})
async function getQuestions(knowledge: Knowledge): Promise<Question[]>{
@@ -40,7 +48,7 @@
function initializeMetrics(questions: Question[]){
questions.forEach((q)=>{
const metric: MetricCreate = {
const metric: Metric = {
question_id: q.id!,
need_index: -1
}
@@ -58,11 +66,14 @@
}
async function postMetrics(){
console.log( metrics.value)
metrics.value?.forEach(async (metric) => {
const response = await api.post(`api/v1/metrics/`, metric)
const metricCreate: MetricCreate = { need_index: metric.need_index }
const response = await api.post(`api/v1/questions/${metric.question_id}/metrics`, metricCreate)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const metric_data = response.data
})
stepStore.nextStep()
}
</script>
@@ -80,7 +91,7 @@
<div class="btn-container">
<b-field>
<div class="control">
<b-button type="is-primary" @click="postMetrics" >Share</b-button>
<b-button type="is-primary" @click="postMetrics" >Partager</b-button>
</div>
</b-field>
</div>

View File

@@ -3,10 +3,12 @@
import { BProgress } from "buefy";
import type { Knowledge } from "@/types/types";
import api from "@/services/apiAxios";
import api from "@/services/api";
import { useItemStore } from '@/stores/item'
import { useStepStore } from '@/stores/step'
import { ProcessStep } from '@/services/knowledgeWorklow'
const stepStore = useStepStore()
const itemStore = useItemStore()
@@ -18,7 +20,8 @@
async function generateQuestions (knowledge: Knowledge) {
await api.post(`api/v1/knowledges/${knowledge.id}/questions`)
stepStore.nextStep()
if(stepStore.indexStep == ProcessStep.WaitGeneration)
stepStore.nextStep()
}
</script>

View File

@@ -1,17 +1,148 @@
<script setup lang="ts">
import { reactive, onMounted, onBeforeMount } from "vue"
import api from "@/services/api"
import { BButton } from "buefy"
import { identifyProcessStep, ProcessStep }from "@/services/knowledgeWorklow"
import type { AxiosResponse } from "axios"
import type { Knowledge } from "@/types/types"
import { useStepStore } from '@/stores/step'
import { useItemStore } from '@/stores/item'
const stepStore = useStepStore()
const itemStore = useItemStore()
interface KnowledgesWorkflow {
knowledge: Knowledge,
processStep: ProcessStep,
isSelected: boolean
}
const knowledgesWorkflow = reactive<KnowledgesWorkflow[]>([])
async function getKnowledges(): Promise<Knowledge[]>{
const response: AxiosResponse<Knowledge[]> = await api.get("api/v1/knowledges/")
return response.data
}
function truncateString(str: string){
return (str.length <= 18) ? str : str.slice(0, 9) + "..." + str.slice(-9)
}
async function initializeKnowledgeWorkflow(){
const knowledges: Knowledge[] = await getKnowledges()
knowledges.forEach(async (knowledge) => {
const kWorkflow: KnowledgesWorkflow = {
knowledge,
processStep: await identifyProcessStep(knowledge as Knowledge),
isSelected: false
}
knowledgesWorkflow.push(kWorkflow)
})
}
async function getKnowledgeWorkflow(){
if(knowledgesWorkflow.length == 0){
initializeKnowledgeWorkflow()
} else {
const knowledges: Knowledge[] = await getKnowledges()
knowledges.forEach(async (knowledge) => {
const kWorkflow: KnowledgesWorkflow = {
knowledge,
processStep: await identifyProcessStep(knowledge as Knowledge),
isSelected: false
}
const indexKW = knowledgesWorkflow.findIndex((kW: KnowledgesWorkflow) => kW.knowledge.id === knowledge.id)
indexKW != -1 ? knowledgesWorkflow[indexKW] = kWorkflow : knowledgesWorkflow.push(kWorkflow)
})
}
}
onBeforeMount(() => getKnowledgeWorkflow())
onMounted(()=>{
setInterval(() => getKnowledgeWorkflow(), 10000)
})
function goToEvaluateQuestion(knowledge_data: Knowledge){
itemStore.$patch({ knowledge: knowledge_data })
stepStore.goToStep(ProcessStep.EvaluateQuestion)
}
function goToCollectKnowledge(){
stepStore.goToStep(ProcessStep.CollectKnowledge)
}
</script>
<template>
<div class="container">
<h2>Knowledge</h2>
</div>
<div class="container">
<h2>Connaissances</h2>
<ul>
<li v-for="(kW, index) in knowledgesWorkflow" :key="index" class="list-element">
<div class="list-element-index">
{{ index + 1 }}
</div>
<div class="list-element-body">
<span class="list-element-text">
{{ truncateString(kW.knowledge.uri) }}
</span>
<!-- <b-button v-if="kW.processStep == ProcessStep.CollectKnowledge" type="is-primary" @click="goTo" >
Collect
</b-button> -->
<b-button v-if="kW.processStep == ProcessStep.WaitGeneration" type="is-info is-light" loading >
...
</b-button>
<b-button v-if="kW.processStep == ProcessStep.EvaluateQuestion" type="is-warning" @click="goToEvaluateQuestion(kW.knowledge)" >
Evaluer
</b-button>
<b-button v-if="kW.processStep == ProcessStep.ProcessDone" type="is-success is-light" disabled >
Fait
</b-button>
</div>
</li>
<li >
<b-button type="is-primary" outlined @click="goToCollectKnowledge()" >
Ajouter une connaissance
</b-button>
</li>
</ul>
</div>
</template>
<style scoped>
.container{
background-color: #ffffff;
border-radius: 16px;
padding: 34px;
}
.container{
background-color: #ffffff;
border-radius: 16px;
padding: 34px;
}
.list-element{
margin-bottom: 8px;
border: 1px solid #D6D9E0;
border-radius: 8px;
display: grid;
grid-template-columns: 40px 1fr;
}
.list-element-index{
display: flex;
justify-content: center;
align-items: center;
border-right: 1px solid #D6D9E0;
}
.list-element-body{
display: grid;
grid-template-columns: 66% 34%;
}
.list-element-text{
display: flex;
align-items: center;
padding-left: 12px;
}
</style>

View File

@@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
import { isAuthenticated } from '@/services/apiAxios'
import { isAuthenticated } from '@/services/api'
const pagesWithoutGuard = ['login', 'app', 'register']
@@ -33,9 +33,10 @@ const router = createRouter({
],
})
// Guard system
router.beforeEach(async (to, from) => {
const isAuth = await isAuthenticated()
if (!isAuth && pagesWithoutGuard.includes(to.name!.toString())) {
if (!isAuth && !pagesWithoutGuard.includes(to.name!.toString())) {
return { name: 'login' }
}
})

View File

@@ -1,74 +1,45 @@
class ApiClient {
private baseURL: string
private defaultHeaders: Record<string, string>
import axios from "axios"
import type { AxiosResponse } from "axios";
constructor(baseURL: string) {
this.baseURL = baseURL
this.defaultHeaders = {
'Content-Type': 'application/json',
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL
});
api.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token')
if (token){
config.headers.Authorization = `Bearer ${token}`
}
}
return config
})
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseURL}${endpoint}`
const config: RequestInit = {
headers: { ...this.defaultHeaders, ...options.headers },
...options,
}
let response
export const authAPI = {
register: (username: string, password: string) =>
api.post(
'/api/v1/auth/register',
{ "username":username, "plain_password":password }
),
login: (username: string, password: string) =>
api.post(
'/api/v1/auth/login',
new URLSearchParams({ username, password }),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }}
),
getMe: () => api.get('/api/v1/auth/me')
}
export const isAuthenticated = async () => {
try {
response = await fetch(url, config)
} catch (error) {
if (error instanceof Error) {
throw new HTTPError(`Network error: ${error.message}`)
}
throw new HTTPError('Unknown network error')
const response: AxiosResponse = await authAPI.getMe()
if (response.status==200)
return true
else
return false
}
if (response?.ok) {
return response.json()
catch{
return false
}
const errorData = await response.json().catch(() => ({}))
throw new HTTPError(
errorData.message || `HTTP ${response.status}: ${response.statusText}`,
response.status,
response,
)
}
/**
* HTTP Get
* @param endpoint
* @returns
*/
async get<T>(endpoint: string): Promise<T> {
return this.request<T>(endpoint, { method: 'GET' })
}
/**
* HTTP Post
* @param endpoint
* @param data
* @returns
*/
async post<T>(endpoint: string, data: unknown): Promise<T> {
return this.request<T>(endpoint, { method: 'POST', body: JSON.stringify(data) })
}
}
class HTTPError extends Error {
constructor(
message: string,
private status: number | null = null,
private reponse: Response | null = null,
) {
super(message)
this.name = 'HTTPError'
}
}
export const apiClient = new ApiClient(import.meta.env.VITE_API_URL)
export default api;

View File

@@ -1,45 +0,0 @@
import axios from "axios"
import type { AxiosResponse } from "axios";
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL
});
api.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token')
if (token){
config.headers.Authorization = `Bearer ${token}`
}
return config
})
export const authAPI = {
register: (username: string, password: string) =>
api.post(
'/api/v1/auth/register',
{ "username":username, "plain_password":password }
),
login: (username: string, password: string) =>
api.post(
'/api/v1/auth/login',
new URLSearchParams({ username, password }),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }}
),
getMe: () => api.get('/api/v1/auth/me')
}
export const isAuthenticated = async () => {
try {
const response: AxiosResponse = await authAPI.getMe()
if (response.status==200)
return true
else
return false
}
catch{
return false
}
}
export default api;

View File

@@ -0,0 +1,54 @@
import type { Knowledge, Metric, Question } from "@/types/types"
import api from "@/services/api"
import type { AxiosResponse } from "axios"
const processFlow = ["CollectKnowledge", "GenerateQuestion", "EvaluateQuestion", "Done"]
//From user action in worlfow
export enum ProcessStep {
CollectKnowledge,
WaitGeneration,
EvaluateQuestion,
ProcessDone
}
export async function identifyProcessStep(knowledge: Knowledge | null): Promise<ProcessStep>{
if (knowledge == null)
return ProcessStep.CollectKnowledge
//Check questions exist
const apiGetQuestions: AxiosResponse<Question[]> = await api.get(
`api/v1/knowledges/${knowledge.id}/questions/`,
{
validateStatus: function (status) {
return status < 500
},
})
if (apiGetQuestions.status >= 400 && apiGetQuestions.status < 500)
return ProcessStep.WaitGeneration
const questions: Question[] = apiGetQuestions.data
if (questions.length == 0)
return ProcessStep.WaitGeneration
//Check metrics exist
const apiGetMetrics: AxiosResponse<Metric[]> = await api.get(
`api/v1/questions/${questions[0]!.id}/metrics/`,
{
validateStatus: function (status) {
return status < 500
},
})
if (apiGetMetrics.status >= 400 && apiGetMetrics.status < 500)
return ProcessStep.EvaluateQuestion
const metrics: Metric[] = apiGetMetrics.data
if (metrics.length == 0)
return ProcessStep.EvaluateQuestion
//Data is complete
return ProcessStep.ProcessDone
}

View File

@@ -5,6 +5,8 @@ import CollectKnowledge from '@/components/CollectKnowledge.vue'
import EvaluateQuestion from '@/components/EvaluateQuestion.vue'
import GenerateQuestion from '@/components/GenerateQuestion.vue'
import { ProcessStep } from '@/services/knowledgeWorklow'
const steps: Component = [
CollectKnowledge,
GenerateQuestion,
@@ -17,8 +19,15 @@ export const useStepStore = defineStore('step', () => {
const getCurrentComponent = computed(() => steps[indexStep.value])
function nextStep() {
indexStep.value++
if(indexStep.value + 1 < steps.length)
indexStep.value++
else
indexStep.value = 0
}
return { steps, getCurrentComponent, nextStep }
function goToStep(processStep: ProcessStep){
indexStep.value = processStep
}
return { indexStep, steps, getCurrentComponent, nextStep, goToStep }
})

View File

@@ -18,9 +18,15 @@ interface Question {
user: User
}
interface MetricCreate {
interface Metric {
//id
question_id: number,
need_index: number
//user
}
interface MetricCreate {
need_index: number
}
interface User {
@@ -28,4 +34,4 @@ interface User {
token: string
}
export type {KnowledgeCreate, Knowledge, Question, MetricCreate}
export type {KnowledgeCreate, Knowledge, Question, MetricCreate, Metric}

View File

@@ -2,7 +2,7 @@
import router from '@/router/index'
import { BField, BInput, BButton, useToast } from "buefy";
import { ref } from "vue"
import { authAPI } from '@/services/apiAxios'
import { authAPI } from '@/services/api'
const username = ref<string>("")
const password = ref<string>("")

View File

@@ -2,7 +2,7 @@
import router from '@/router/index'
import { BField, BInput, BButton, useToast } from "buefy";
import { ref } from "vue"
import { authAPI } from '@/services/apiAxios'
import { authAPI } from '@/services/api'
const username = ref<string>("")
const password = ref<string>("")