From d1e5a6b0c7d374efe977892be0b1298588c55bdb Mon Sep 17 00:00:00 2001 From: Robin COuret Date: Sun, 8 Mar 2026 01:33:21 +0100 Subject: [PATCH] add KnowledgeWorkFlow system --- server/src/app/api/v1/__init__.py | 2 + .../v1/__pycache__/__init__.cpython-311.pyc | Bin 666 -> 786 bytes .../v1/__pycache__/knowledges.cpython-311.pyc | Bin 3733 -> 3742 bytes .../v1/__pycache__/metrics.cpython-311.pyc | Bin 1167 -> 623 bytes .../v1/__pycache__/questions.cpython-311.pyc | Bin 0 -> 2330 bytes server/src/app/api/v1/knowledges.py | 2 +- server/src/app/api/v1/metrics.py | 10 +- server/src/app/api/v1/questions.py | 38 +++++ .../data/__pycache__/question.cpython-311.pyc | Bin 2692 -> 3072 bytes server/src/app/data/question.py | 12 +- .../models/__pycache__/metric.cpython-311.pyc | Bin 1639 -> 1612 bytes server/src/app/models/metric.py | 1 - user-interface/src/App.vue | 10 -- .../src/components/CollectKnowledge.vue | 17 +- .../src/components/EvaluateQuestion.vue | 47 +++--- .../src/components/GenerateQuestion.vue | 7 +- .../src/components/ReadKnowledges.vue | 147 +++++++++++++++++- user-interface/src/router/index.ts | 5 +- user-interface/src/services/api.ts | 101 +++++------- user-interface/src/services/apiAxios.ts | 45 ------ .../src/services/knowledgeWorklow.ts | 54 +++++++ user-interface/src/stores/step.ts | 13 +- user-interface/src/types/types.d.ts | 10 +- user-interface/src/views/LoginView.vue | 2 +- user-interface/src/views/RegisterView.vue | 2 +- 25 files changed, 351 insertions(+), 174 deletions(-) create mode 100644 server/src/app/api/v1/__pycache__/questions.cpython-311.pyc create mode 100644 server/src/app/api/v1/questions.py delete mode 100644 user-interface/src/services/apiAxios.ts create mode 100644 user-interface/src/services/knowledgeWorklow.ts diff --git a/server/src/app/api/v1/__init__.py b/server/src/app/api/v1/__init__.py index 37b1ddf..9713036 100644 --- a/server/src/app/api/v1/__init__.py +++ b/server/src/app/api/v1/__init__.py @@ -3,8 +3,10 @@ from fastapi import APIRouter from .knowledges import router as knowledge_router from .metrics import router as metric_router from .auth import router as auth_router +from .questions import router as question_router router = APIRouter(prefix="/v1") router.include_router(knowledge_router) +router.include_router(question_router) router.include_router(metric_router) router.include_router(auth_router) \ No newline at end of file diff --git a/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc b/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc index e682e78cf3ddf381cece4b979c8cc4377a68fb38..51b78f3495bf1565cb17f058e355cc041e14d513 100644 GIT binary patch delta 187 zcmbQmI*E;UIWI340}zyZuFjk=kynz5iE*Na30DeJ3Udx;E^`#u#3)%t?uiwe+T0*z z=pdDA86yM3Y9NLHMut@81>6%CX#(|}iDTrMtj!oJ!dY0FT3nKupI3Z~AI6;Az?jA* u0@MW3T6|*i2gU%Q8w>^)P|;)uCPS|LiwtU47}PE>s7+31(q|C@Y5)KdA}Xx_ delta 108 zcmbQlHj9;aIWI340}!OXTA67vkyn!G48ueX6B#CkRK^sRHLS}R85mXrF$6?0r!p_# zoYQklNx!lbx6XCNE(!ocxkWl|=w31OO*5 B7xVxC diff --git a/server/src/app/api/v1/__pycache__/knowledges.cpython-311.pyc b/server/src/app/api/v1/__pycache__/knowledges.cpython-311.pyc index 5d042c4373fd1aeb06f8e50501943fbaa8b4b8fb..c6e6378578464dbbe3e71eb6505484e6b63e7b21 100644 GIT binary patch delta 114 zcmbO#J5QE(IWI340}#v)TAk^-k@qwksbN~fx{Qf| zVKvj_I(AJ)<;g49dw7{{aTJ!O7MEn^=M~9JcH`(}?3i55{$gSOI}^9_|1D diff --git a/server/src/app/api/v1/__pycache__/metrics.cpython-311.pyc b/server/src/app/api/v1/__pycache__/metrics.cpython-311.pyc index 06ec38ff88d1ec54715f05a8c01be738d80c15ee..702e8575db3ef12deb18799ca765f94eeb21da90 100644 GIT binary patch delta 113 zcmeC@e9xl3oR^o20SKo1uFec+Vqka-;=lkil<_%bqIxo03P&)5Cg;Q-Wz2q>yp#Vh wHM6f|_zY4pxsb_5^cROsZhlH>PO4pzC{TM#iZ(wfXY+!3(D-s6s0b^hplmGw# delta 659 zcmaFQ($A^BoR^o20SK0VT9xU`#K7b58iZYXn^>1-QSn(-|C5g9ql1qzB9osc^DXYe($wOT z%>2Cg%#>SPd8w%>@tJuksTH?aKu*(SDPjW}Qp67;1V97_kN}GEfY_kWyu}9b*Db!} zqSV9^pmwN_Sr`~t85DqE@?=IE|66Pj=^{~}9&R8mHUtt43{OR+S7=|+a=Z`_eI+36 zLVooH(VB~*HCIGy8r<&6YOOH4C~I^@)~Lbn2Djt|7Rk>*O$>gT0+Trzn++DFPe2lA%ZpC|x81Bz|$&;p#jcIhVRj_8Yw zl2;fdKQJ*fO3mQ;zyPE+c7fK6rTO@`rr96PU0j`x3mG%Vi$^#D5Me)2uP5UkSgk`m9_CqoTa;KcXpjZ z>qr(tf@5!RM5>eqB?r_~jy-Z*OFmd5Aw{Zs=*@`sl2hNT|Kfnu9vIEOoq6wj^VaWu zZ~SXC8bUCBS=iD?6omfZo6ZQg7q7n&5PFO-!h(TJK@|kP77fvqRLPW8+4QMCQ&AO9 zFByI_pa#6PYy{1a8uHdYBWy<0h#6I*-d-_c=72ijt^LNJIiwDm!|JerL_{PUCAi;=$V}J@glkk``F-qGc5w%^^4~!{*lYg<{zul=){!iCEZX z(QAu~x31jHlaiwsEvDE2F5BQaSRhU=U#65;POfYd3cA32nb;hc7MXIBI8@KGNb7x> z5{LqrVea>R8<$(Ym(wwz!(3~69uPC{f!li)?4WsA+0hF2kE7YHF8h;?*Rvg0QO7K@ z|Eu!izI+1VkOa@`2Ou6Jr(0@-DySlC0Cua3Ul%K)eZ=AT_Hg9R3ZXk)Jr=NtC0*E- zH$b=5)!!GKU=Lpr`$@4bRU{~cZ*K3})goQiF(y_fwYW-Blz6|5;M8|k@dtz80!it1 z3d)qy)->JFmJO0xDbkb!;1Vsa=@_urXEJ+(OG`^9FP1@XIeH$_&6G<-ql7+_Sh!WV zT)v1wjfn#%G5;#jFrjwZM}t^@Sz27yt?W(BDjK?WQZq`cS{AsnMrhWi`K(qd!J}u_ zPW1|!Dcxf+AOP;N#HR1Tu>C8z7j>FTvcINn4)pYk#`@p9}) zee6^t{%s?1Y;(*#bh_!62b5O`#NS*rksMV1388`UC&z2CqxIO)>TDxCT%CIj2O}14 z{f->g94+6iBv`u4`3*L2Fh@MV9H{L(WDYR%j*ovH9~E)i*Uz(c+2NIGkBWSNXTFLL zc&5xzev{Dz7b9GZf=B}|IyHnoWFd%p?>1FXX}Yq5)zMfJewm(3_P5;KjB8FY67T_HN}uH{s<0)*V9Swwe$7w j_KVpUv+g3U-5~WF#GNJe=@rk`6a3ucodbGmuM+Wm(I(0LCWIoAL&BONV>v1sN>&_k8EO`dxD5@9 zQQmL?;$aN%-Qar-&kzcFbJ^B_I7~4Ka<+_<6R{+=-A`vRHDjsdSj@1+SqMh>+!vki zRnQc3S@CoIp!xz`iq%k>@=@bDxC%wRpikZAJDTPb@_HzpDyg^Vqc@ObDjem zDYtZch41DT+(0?KgTwMnQ28q~U`>M4nY!ZS0WL<*=h#0HVDsj_P; z5hHAd$HRcL;j)ET(j;uuoUS;E2}`<(j=&B~D+vQ$+B2YdW9*O`~q^YTu1U;mC&?3@aqGIvTTUBzAUP^Jt8r&viWZ0lEZM10l22nvVL9zXTYXNtByNo=i zvr+7P$nct;#T$IxB>aLgT}qTu4=;Esg`*JF0fe$bLaG7lDb$dH!3blOT2+|!24NvE zCRg+AMEU+G5V?yr0b1y*bz(ESo_~~&5Y}d&%a@SRhqF zhzk_j+Fe44NZzZ~)&k{Y83$1ppnzc#mIoGW@19e(vmN Z^xqSy3eId@ix7N{g3Y&`1$7F7`~eyxyQ%;H diff --git a/server/src/app/data/question.py b/server/src/app/data/question.py index 382d12e..29a3d6c 100644 --- a/server/src/app/data/question.py +++ b/server/src/app/data/question.py @@ -1,13 +1,21 @@ from sqlmodel import Session, select from src.app.models.question import Question +from src.app.models.user import User from src.app.database import engine -def get_question_by_id(question_id: int): +def get_question_by_id(question_id: int, user: User): with Session(engine) as session: - question = session.get(Question, question_id) + statement = select(Question).where(Question.id == question_id, Question.user_id == user.id) + results = session.exec(statement) + question = results.first() return question +# def get_question_by_id(question_id: int): +# with Session(engine) as session: +# question = session.get(Question, question_id) +# return question + def get_questions(knowledge): with Session(engine) as session: statement = select(Question).where(Question.knowledge_id == knowledge.id) diff --git a/server/src/app/models/__pycache__/metric.cpython-311.pyc b/server/src/app/models/__pycache__/metric.cpython-311.pyc index 5ec8f65c93dd093b25a6f061ad98e39d146d2daa..455cd1fd5b5c078d8e936c471e542d3bda5b400e 100644 GIT binary patch delta 151 zcmaFPbB2d^IWI340}wR&uFjmXk=L6=PYTGJ&XCFw#hAhn#T3er%ACrQ!e|2&O<@XV z&}6>FYl$uzQI(aIKy?`W8vH=Nh{=nkEXwJwe@__-9m|*!4B>Dx5RFNFe FAOM}}6Dej0PllLr~-b3kY3g5!rl|#evbB XiBaSO112%S@*_y}3l^y&IiR5ckWDC7 diff --git a/server/src/app/models/metric.py b/server/src/app/models/metric.py index 42ba569..54289a6 100644 --- a/server/src/app/models/metric.py +++ b/server/src/app/models/metric.py @@ -15,5 +15,4 @@ class Metric(SQLModel, table=True): user: User | None = Relationship(back_populates="metrics") class MetricCreate(BaseModel): - question_id: int need_index: int \ No newline at end of file diff --git a/user-interface/src/App.vue b/user-interface/src/App.vue index f9f122b..8a3f7fc 100644 --- a/user-interface/src/App.vue +++ b/user-interface/src/App.vue @@ -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; - } -} */ diff --git a/user-interface/src/components/CollectKnowledge.vue b/user-interface/src/components/CollectKnowledge.vue index 9ff87f6..a1c3976 100644 --- a/user-interface/src/components/CollectKnowledge.vue +++ b/user-interface/src/components/CollectKnowledge.vue @@ -1,11 +1,10 @@ @@ -80,7 +91,7 @@
- Share + Partager
diff --git a/user-interface/src/components/GenerateQuestion.vue b/user-interface/src/components/GenerateQuestion.vue index ca74258..58dddd8 100644 --- a/user-interface/src/components/GenerateQuestion.vue +++ b/user-interface/src/components/GenerateQuestion.vue @@ -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() } diff --git a/user-interface/src/components/ReadKnowledges.vue b/user-interface/src/components/ReadKnowledges.vue index 99bc9ee..e9d1088 100644 --- a/user-interface/src/components/ReadKnowledges.vue +++ b/user-interface/src/components/ReadKnowledges.vue @@ -1,17 +1,148 @@ \ No newline at end of file diff --git a/user-interface/src/router/index.ts b/user-interface/src/router/index.ts index 852944f..0b88ba3 100644 --- a/user-interface/src/router/index.ts +++ b/user-interface/src/router/index.ts @@ -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' } } }) diff --git a/user-interface/src/services/api.ts b/user-interface/src/services/api.ts index f6045f2..4250a94 100644 --- a/user-interface/src/services/api.ts +++ b/user-interface/src/services/api.ts @@ -1,74 +1,45 @@ -class ApiClient { - private baseURL: string - private defaultHeaders: Record +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(endpoint: string, options: RequestInit = {}): Promise { - 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(endpoint: string): Promise { - return this.request(endpoint, { method: 'GET' }) - } - - /** - * HTTP Post - * @param endpoint - * @param data - * @returns - */ - async post(endpoint: string, data: unknown): Promise { - return this.request(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) \ No newline at end of file +export default api; \ No newline at end of file diff --git a/user-interface/src/services/apiAxios.ts b/user-interface/src/services/apiAxios.ts deleted file mode 100644 index 828d996..0000000 --- a/user-interface/src/services/apiAxios.ts +++ /dev/null @@ -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; \ No newline at end of file diff --git a/user-interface/src/services/knowledgeWorklow.ts b/user-interface/src/services/knowledgeWorklow.ts new file mode 100644 index 0000000..28aecb1 --- /dev/null +++ b/user-interface/src/services/knowledgeWorklow.ts @@ -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{ + if (knowledge == null) + return ProcessStep.CollectKnowledge + + //Check questions exist + const apiGetQuestions: AxiosResponse = 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 = 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 + } + + + \ No newline at end of file diff --git a/user-interface/src/stores/step.ts b/user-interface/src/stores/step.ts index 503f363..e5aa3d4 100644 --- a/user-interface/src/stores/step.ts +++ b/user-interface/src/stores/step.ts @@ -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 } }) diff --git a/user-interface/src/types/types.d.ts b/user-interface/src/types/types.d.ts index b0e2d65..dbdd002 100644 --- a/user-interface/src/types/types.d.ts +++ b/user-interface/src/types/types.d.ts @@ -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} \ No newline at end of file +export type {KnowledgeCreate, Knowledge, Question, MetricCreate, Metric} \ No newline at end of file diff --git a/user-interface/src/views/LoginView.vue b/user-interface/src/views/LoginView.vue index fa380e4..8eeb529 100644 --- a/user-interface/src/views/LoginView.vue +++ b/user-interface/src/views/LoginView.vue @@ -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("") const password = ref("") diff --git a/user-interface/src/views/RegisterView.vue b/user-interface/src/views/RegisterView.vue index 57c593f..1b1d68e 100644 --- a/user-interface/src/views/RegisterView.vue +++ b/user-interface/src/views/RegisterView.vue @@ -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("") const password = ref("")