Convert backend/web from submodule to regular directory

This commit is contained in:
fabien committed 2026-08-08 23:26:14 +02:00
1 parent 102a316d42
commit 0010c92103
89 files changed
+6559 -1

No files matched your search

@@ -0,0 +1,7 @@
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Conversation' };
}
@@ -0,0 +1,80 @@
<script lang="ts">
import type { LayoutProps } from './$types';
import { page } from '$app/state';
import {uiState} from "$lib/state/ui.svelte"
import { auth } from '$lib/state/auth.svelte'
import Sidebar from "$lib/components/Sidebar.svelte"
import { goto } from '$app/navigation'
import { onMount } from "svelte";
import { applyTheme, themeToLocalStorage } from '$lib/ui/themeswitcher';
let { data, children }: LayoutProps = $props();
const handleMenuDesktop = () => {
uiState.sidebarDesktopOpen = !uiState.sidebarDesktopOpen
if (uiState.sidebarDesktopOpen) {
setTimeout(() => { uiState.menuContentVisible = uiState.sidebarDesktopOpen }, 200)
} else {
uiState.menuContentVisible = uiState.sidebarDesktopOpen
}
}
const handleMenuMobile = () => {
uiState.sidebarMobileOpen = !uiState.sidebarMobileOpen
if (uiState.sidebarMobileOpen) {
setTimeout(() => { uiState.menuContentVisible = uiState.sidebarMobileOpen }, 200)
} else {
uiState.menuContentVisible = uiState.sidebarMobileOpen
}
}
const handleResize = () => {
if (window.innerWidth < 640) {
uiState.menuContentVisible = uiState.sidebarMobileOpen
} else {
uiState.menuContentVisible = uiState.sidebarDesktopOpen
}
}
onMount(async () => {
handleResize()
if (data.me) {
auth.user = data.me
themeToLocalStorage(auth.user.theme)
applyTheme()
auth.status = 'authenticated'
} else {
auth.status = 'unauthenticated'
}
})
$effect(() => {
if (auth.status == 'unauthenticated') {
goto("/login")
}
});
</script>
<svelte:window onresize={handleResize} />
<div id="outer" class={[uiState.sidebarDesktopOpen ? "desktopMenuOpen" : "desktopMenuClosed", uiState.sidebarMobileOpen ? "mobileMenuOpen" : "mobileMenuClosed" ]}>
<div id="sidebar" class="flex flex-col">
<Sidebar {handleMenuDesktop} {handleMenuMobile}/>
</div>
<div id="main">
<div class="flex flex-col h-screen ">
<div class={["header", page.data.theme]}>
<h1 class="flex">
<span class="flex-1">{page.data.title}</span>
{#if page.data.back}
<span><a href={page.data.back} title="retour"><i class="icon-left-big"></i></a></span>
{/if}
</h1>
</div>
<div class="overflow-scroll flex-1 p-2">
{@render children()}
</div>
</div>
</div>
</div>
@@ -0,0 +1,8 @@
import { userApi } from "$lib/api/endpoints/user"
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async () => {
return {
me: await userApi.me()
}
}
@@ -0,0 +1,6 @@
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Historique' };
}
Whitespace-only changes.
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Base de connaissance' };
}
@@ -0,0 +1,109 @@
<script lang="ts">
import { SvelteMap } from 'svelte/reactivity';
import type { PageProps } from './$types';
import { type User } from '$lib/types/api'
import { userApi } from '$lib/api/endpoints/user'
import FormError from '$lib/components/FormError.svelte';
import { applyTheme, themeToLocalStorage } from '$lib/ui/themeswitcher';
import { validateEmail } from '$lib/utilities/validators'
let { data }: PageProps = $props();
let form : User = $state(data.me)
let errors = new SvelteMap<string,string>()
$effect(()=>{
themeToLocalStorage(form.theme)
applyTheme()
})
let submitUser = (e: Event) => {
e.preventDefault()
if (!validateEmail(form.email)) {
errors.set("email","Adresse email invalide !")
}
if (!form.firstname) {
errors.set("firstname","Le prénom est obligatoire !")
}
if (!form.lastname) {
errors.set("lastname","Le nom est obligatoire !")
}
if (errors.size==0) {
userApi.update(form.id, form)
.then(()=>{ console.log('success')})
.catch((err: string)=>{ console.error(err)})
}
}
</script>
<div class="mb-4 formgroup">
<h2>Mon compte</h2>
<form onsubmit={submitUser} onchange={()=>{errors.clear()}}>
<label for="email" class="block my-1 flex-1">Email</label>
<input
id="email"
type="text"
bind:value={form.email}
class="textinput"
autofocus
/>
<FormError key="email" errors={errors} />
<div class="flex">
<div class="flex-1 mr-2">
<label for="firstname" class="block my-1 flex-1">Prénom</label>
<input
id="firstname"
type="text"
bind:value={form.firstname}
class="textinput"
/>
<FormError key="firstname" errors={errors} />
</div>
<div class="flex-1 ml-2">
<label for="lastname" class="block my-1 flex-1">Nom</label>
<input
id="lastname"
type="text"
bind:value={form.lastname}
class="textinput"
/>
<FormError key="lastname" errors={errors} />
</div>
</div>
<div class="flex">
<div class="flex-1 mr-2">
<label for="theme" class="block my-1 flex-1">Thème</label>
<select bind:value={form.theme} class="textinput">
<option value="auto">Automatic</option>
<option value="dark">Sombre</option>
<option value="light">Clair</option>
</select>
<FormError key="theme" errors={errors} />
</div>
<div class="flex-1 ml-2">
<label for="lang" class="block my-1 flex-1">Langue</label>
<select bind:value={form.lang} class="textinput">
<option value="en">English</option>
<option value="fr">Français</option>
</select>
<FormError key="lang" errors={errors} />
</div>
</div>
<button class="bt w-full">Enregistrer</button>
</form>
</div>
<div class="mb-4 formgroup">
<h2>Canaux de communication</h2>
</div>
@@ -0,0 +1,9 @@
import { userApi } from "$lib/api/endpoints/user"
import type { PageLoad } from './$types';
export const load: PageLoad = async () => {
return {
title: 'Profil',
me: await userApi.me()
}
}
@@ -0,0 +1,58 @@
<div class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
<a class="bt p-5 text-center"
title="Modèles"
href="/settings/models">
Modèles
<span class="ml-2 opacity-50 text-xs">mistral-medium-latest</span>
</a>
<a class="bt p-5 text-center"
title="Outils"
href="/settings/tools"
>
Outils
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">12</span>
</a>
<a class="bt p-5 text-center"
title="Skills"
href="/settings/skills">
Skills
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">3</span>
</a>
<a class="bt p-5 text-center"
title="Canaux de communication"
href="/settings/channels"
>
Canaux de communication
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">12</span>
</a>
<a class="bt p-5 text-center"
title="Bases de connaissance"
href="/settings/kb">
Bases de connaissance
</a>
<a class="bt p-5 text-center"
title="Agents"
href="/settings/agents">
Agents
</a>
<a class="bt p-5 text-center"
title="Mémoire"
href="/settings/memory">
Mémoire
<span class="ml-2 opacity-50 text-xs">standard</span>
</a>
<a class="bt p-5 text-center"
title="Utilisateurs"
href="/settings/users">
Utilisateurs
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">1</span>
</a>
</div>
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Paramétrage', theme: "settings" };
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Agents',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Canaux de communication',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Base de connaissance',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Mémoire',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Modèles',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Skills',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Outils',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Utilisateurs',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1,3 @@
<div>
Tâches
</div>
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Tâches' };
}
@@ -0,0 +1,105 @@
<script lang="ts">
import logo from "$lib/assets/logo-txt.png"
import { auth, setToken } from '$lib/state/auth.svelte';
import { goto } from '$app/navigation';
import { authApi } from "$lib/api/endpoints/auth"
type Credentials = {
email: string
validemail: boolean
password : string
}
let credentials: Credentials = $state({
email: "",
password: "",
validemail: false
})
const formSubmitEmail = () => {
credentials.validemail = true
}
const formSubmitPass = (e: Event) => {
e.preventDefault()
authApi.login(credentials)
.then((res)=>{
auth.user = res.user
setToken(res.accessToken)
goto("/")
})
.catch((err)=>{
console.log(err)
})
}
</script>
<div class="h-full w-full flex items-center public">
<div class="m-auto w-96 max-w-full p-4">
<img src={logo} class="h-10 mb-4" alt="logo"/>
<h1>Identifiez-vous ...</h1>
{#if !credentials.validemail}
<form onsubmit={formSubmitEmail}>
<div class="flex">
<label for="email" class="block mb-2 flex-1">Email</label>
<a href="/recover-password" class="text-xs">Mot de passe oublié ?</a>
</div>
<div class="flex flex-row">
<input
type="text"
bind:value={credentials.email}
class="textinput mr-1"
autofocus
/>
<button class="bt" title="page suivante">
<i class="icon-right-big"></i>
</button>
</div>
</form>
<div class="my-4 text-xs">
<a href="/register">Pas encore de compte ? Incrivez-vous.</a>
</div>
{:else}
<form onsubmit={formSubmitPass}>
<div class="flex">
<label for="email" class="block mb-2 flex-1">Mot de passe</label>
</div>
<div class="flex flex-row">
<input
type="password"
bind:value={credentials.password}
class="textinput mr-1"
autofocus
/>
<button class="bt">
Entrer
</button>
</div>
<a href="/login" class="text-xs" onclick={()=>{credentials.validemail=false}}>← Retour</a>
</form>
{/if}
<hr class="my-4">
<div class="grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] gap-4">
<button class="bt p-5 text-center" title="Connexion avec Google">
<i class="icon-google"></i>
</button>
<button class="bt p-5 text-center" title="Connexion avec Facebook">
<i class="icon-facebook"></i>
</button>
<button class="bt p-5 text-center" title="Connexion avec Github">
<i class="icon-github"></i>
</button>
<button class="bt p-5 text-center" title="Connexion avec Apple">
<i class="icon-apple"></i>
</button>
</div>
</div>
</div>
@@ -0,0 +1,120 @@
<script lang="ts">
import logo from "$lib/assets/logo-txt.png"
import { SvelteMap } from 'svelte/reactivity';
import { goto } from '$app/navigation';
import { validateEmail } from '$lib/utilities/validators'
import FormError from "$lib/components/FormError.svelte";
import { authApi } from "$lib/api/endpoints/auth"
type RegisterForm = {
email: string
firstname: string
lastname: string
password : string
verifPassword: string
}
let errors = new SvelteMap<string,string>()
let form: RegisterForm = $state({
email: "",
firstname: "",
lastname: "",
password: "",
verifPassword: ""
})
const handleSubmit = (e: Event) => {
e.preventDefault()
errors.clear()
if (!validateEmail(form.email)) {
errors.set("email","Adresse email invalide !")
}
if (form.password!==form.verifPassword) {
errors.set("verifPassword","Les mots de pass ne correspondent pas.")
}
if (errors.size==0) {
let data = {
method: "password",
email: form.email,
firstname: form.firstname,
lastname: form.lastname,
password: form.password
}
authApi.register(data)
.then(()=>{ goto("/") })
.catch((reason: string)=>{ errors.set("submit","Erreur: "+reason) })
}
}
</script>
<div class="h-full w-full flex items-center public">
<div class="m-auto w-96 max-w-full p-4">
<img src={logo} class="h-10 mb-4" alt="logo"/>
<h1>Inscription</h1>
<form onsubmit={handleSubmit} onchange={()=>{errors.clear()}}>
<label for="email" class="block my-1 flex-1">Email</label>
<input
id="email"
type="text"
bind:value={form.email}
class="textinput"
autofocus
/>
<FormError key="email" errors={errors} />
<label for="firstname" class="block my-1 flex-1">Prénom</label>
<input
id="firstname"
type="text"
bind:value={form.firstname}
class="textinput"
/>
<FormError key="firstname" {errors} />
<label for="lastname" class="block my-1 flex-1">Nom</label>
<input
id="lastname"
type="text"
bind:value={form.lastname}
class="textinput"
/>
<FormError key="lastname" {errors} />
<label for="password" class="block my-1 flex-1">Mot de passe</label>
<input
id="password"
type="password"
bind:value={form.password}
class="textinput"
/>
<FormError key="password" {errors} />
<label for="verifpassword" class="block my-1 flex-1">Vérification du mot de passe</label>
<input
id="verifpassword"
type="password"
bind:value={form.verifPassword}
class="textinput"
/>
<FormError key="verifPassword" {errors} />
<button class="bt my-2 w-full ">
Enregistrer
</button>
<FormError key="submit" {errors} />
</form>
</div>
</div>
+2
View File
@@ -0,0 +1,2 @@
export const ssr = false;
export const prerender = false;
+18
View File
@@ -0,0 +1,18 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
import { applyTheme } from '$lib/ui/themeswitcher';
import { onMount } from 'svelte';
let { children } = $props();
onMount(()=>{ applyTheme() })
</script>
<svelte:head>
<title>Lasssistanoque</title>
<link rel="icon" href={favicon} />
<link rel="stylesheet" href="/assets/fontello/css/fontello.css">
</svelte:head>
{@render children()}
+107
View File
@@ -0,0 +1,107 @@
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
@custom-variant dark (&:where(.dark, .dark *));
html {
@apply h-full;
}
body {
@apply h-full bg-stone-100 text-stone-900 dark:bg-stone-900 dark:text-stone-200;
}
label {
@apply text-xs text-stone-600 uppercase dark:text-stone-400;
}
hr {
@apply border-stone-200 dark:border-stone-800;
}
h2 {
@apply mb-4 font-bold;
}
.textinput {
@apply w-full rounded bg-white p-1 text-stone-900 dark:bg-stone-700 dark:text-stone-100;
}
.pointer {
@apply cursor-pointer;
}
.bt {
@apply cursor-pointer rounded bg-stone-200 px-4 py-2 hover:bg-stone-300 dark:bg-stone-800 dark:hover:bg-stone-700;
}
.menuitem {
@apply text-stone-800 text-stone-950 dark:text-stone-300 hover:dark:text-stone-50;
}
.header {
@apply p-2 text-lg;
h1 {
@apply rounded bg-stone-200 px-2 font-bold text-sky-600 dark:bg-stone-800 dark:text-sky-200;
}
&.settings {
h1 {
@apply rounded border-none bg-sky-200 px-2 text-sky-700 dark:bg-sky-950 dark:text-sky-300;
}
}
}
.public h1 {
@apply mb-4 text-3xl font-bold;
}
.profile a,
.public a {
@apply text-sky-500;
}
.formgroup {
@apply rounded border border-stone-300 bg-stone-100 p-2 dark:border-stone-700 dark:bg-stone-900;
}
#outer {
@apply flex h-full flex-row;
}
#main {
@apply flex w-full flex-col;
}
#sidebar {
@apply min-w-64 border-r border-r-stone-200 transition-all duration-300 dark:border-stone-800;
}
#content {
@apply p-2;
}
#hamburgerDesktop,
#hamburgerMobile {
@apply hidden;
}
@media (width >= 640px) {
#hamburgerDesktop {
@apply inline;
}
.desktopMenuClosed {
#sidebar {
@apply w-10 min-w-10;
}
}
}
@media (width < 640px) {
#hamburgerMobile {
@apply inline;
}
#main {
@apply ml-10;
}
#sidebar {
@apply fixed left-0 h-full w-full bg-stone-100 dark:bg-stone-900;
}
.mobileMenuClosed {
#sidebar {
@apply fixed w-10 min-w-10;
}
}
}