Plusieurs gros ajouts :
Some checks failed
Build & Push Images / build (brain) (push) Has been cancelled
Build & Push Images / build (core) (push) Has been cancelled
Build & Push Images / build (web) (push) Has been cancelled
Build & Push Images / build-switcher (push) Has been cancelled

- Possibilité de discuter avec un PDF ; RAG ou analyse approfondie. Enlèvement de l'autre outil PDF de discussion qui analysait d'abord un PDF en proposant directement une intégration sans attendre qu'on pose de question
- Mise en place de l'import directement dans les outils dans la sidebar
- Mise en place d'un outil pour créer des tables aléatoires avec possibilité d'utiliser pendant la partie
- Mise en place d'un outil pour mettre en place des PNJ, scènes, chapitre.... directement à partir de la discussion avec le PDF
- Mise en place RAG avec mistal-embeding ou nomic si on utilise ollama
- Mise en place mistral, google en fournisseurs alternatifs pour l'IA dans le cloud
- version 0.11.0-bêta
This commit is contained in:
2026-06-07 09:52:15 +02:00
parent 5eb15dc449
commit edc4434298
113 changed files with 6347 additions and 662 deletions

View File

@@ -36,6 +36,14 @@
<input type="radio" name="provider" value="openrouter" [(ngModel)]="settings.llm_provider">
<span>OpenRouter (cloud)</span>
</label>
<label class="radio">
<input type="radio" name="provider" value="mistral" [(ngModel)]="settings.llm_provider">
<span>Mistral (cloud)</span>
</label>
<label class="radio">
<input type="radio" name="provider" value="gemini" [(ngModel)]="settings.llm_provider">
<span>Gemini (cloud)</span>
</label>
</div>
</div>
</section>
@@ -212,6 +220,145 @@
</div>
</section>
<!-- Bloc Mistral -->
<section class="card" *ngIf="settings && settings.llm_provider === 'mistral'">
<h2>Configuration Mistral</h2>
<div class="form-row">
<label for="mistral-key">Cle API</label>
<input
id="mistral-key"
type="password"
[(ngModel)]="mistralApiKeyInput"
[placeholder]="settings.mistral_api_key_set ? 'Cle configuree (laisser vide pour ne pas changer)' : 'Saisir votre cle API Mistral'">
<label class="checkbox" *ngIf="settings.mistral_api_key_set">
<input type="checkbox" [(ngModel)]="clearMistralKey">
<span>Effacer la cle enregistree</span>
</label>
</div>
<div class="form-row">
<label for="mistral-model">Modele</label>
<select id="mistral-model" [(ngModel)]="settings.mistral_model">
<option *ngFor="let o of mistralSelectOptions" [value]="o.id">{{ o.label }}</option>
</select>
<p class="hint">
Cle gratuite sur <code>console.mistral.ai</code> (tier « Experiment », sans CB).
Pour les imports, prefere <code>mistral-large-latest</code> (128k contexte, fidele
et bon en francais) ou <code>mistral-small-latest</code> (plus rapide).
</p>
</div>
</section>
<!-- Bloc Gemini -->
<section class="card" *ngIf="settings && settings.llm_provider === 'gemini'">
<h2>Configuration Gemini</h2>
<div class="form-row">
<label for="gemini-key">Cle API</label>
<input
id="gemini-key"
type="password"
[(ngModel)]="geminiApiKeyInput"
[placeholder]="settings.gemini_api_key_set ? 'Cle configuree (laisser vide pour ne pas changer)' : 'Saisir votre cle API Gemini'">
<label class="checkbox" *ngIf="settings.gemini_api_key_set">
<input type="checkbox" [(ngModel)]="clearGeminiKey">
<span>Effacer la cle enregistree</span>
</label>
</div>
<div class="form-row">
<label for="gemini-model">Modele</label>
<select id="gemini-model" [(ngModel)]="settings.gemini_model">
<option *ngFor="let o of geminiSelectOptions" [value]="o.id">{{ o.label }}</option>
</select>
<p class="hint">
Cle gratuite sur <code>aistudio.google.com</code> (sans CB). Ideal pour les imports :
<code>gemini-2.0-flash</code> a un <strong>contexte ~1M</strong> → un livre entier tient
en 1-2 appels (plus de morceaux perdus). Pense a monter la <strong>taille des morceaux</strong>
d'import pour en profiter.
</p>
</div>
</section>
<!-- Bloc Embeddings (Atelier RAG) : indépendant du modèle de CHAT ci-dessus. -->
<section class="card" *ngIf="settings">
<h2>Embeddings (Atelier RAG)</h2>
<p class="hint">
Modèle utilisé pour <strong>indexer les sources PDF</strong> des ateliers et y faire
de la recherche. C'est un modèle <strong>séparé</strong> du modèle de chat ci-dessus.
</p>
<div class="form-row">
<label>Fournisseur d'embeddings</label>
<div class="radio-group">
<label class="radio">
<input type="radio" name="embprovider" value="ollama" [(ngModel)]="settings.embedding_provider">
<span>Ollama (local, gratuit)</span>
</label>
<label class="radio">
<input type="radio" name="embprovider" value="mistral" [(ngModel)]="settings.embedding_provider">
<span>Mistral (cloud, EU)</span>
</label>
</div>
</div>
<!-- Ollama embeddings -->
<div *ngIf="settings.embedding_provider === 'ollama'">
<div class="form-row">
<label for="emb-ollama-model">Modèle d'embedding Ollama</label>
<input id="emb-ollama-model" type="text" [(ngModel)]="settings.ollama_embedding_model"
placeholder="nomic-embed-text">
<p class="hint">
Recommandé : <code>nomic-embed-text</code> (léger). Gratuit et illimité (local).
</p>
</div>
<div class="form-row">
<label class="checkbox">
<input type="checkbox" [(ngModel)]="settings.auto_pull_embedding_model">
<span>Installer automatiquement le modèle au démarrage s'il manque</span>
</label>
</div>
</div>
<div class="form-row">
<label for="rag-topk">Extraits récupérés par question (couverture RAG)</label>
<input id="rag-topk" type="number" min="3" max="200" [(ngModel)]="settings.rag_top_k">
<p class="hint">
Plus haut = l'IA voit plus de passages par question (mieux pour les questions
larges « liste les… »), mais prompt plus long. Défaut 8. Avec un modèle
<strong>gros-contexte</strong> (Gemini), tu peux monter à <strong>50-150</strong> pour
une couverture quasi « document entier ». Pour les questions exhaustives, préfère
le bouton <strong>« Analyse approfondie »</strong> de l'atelier.
</p>
</div>
<!-- Mistral embeddings -->
<div *ngIf="settings.embedding_provider === 'mistral'">
<div class="form-row">
<label for="emb-mistral-key">
Clé API Mistral
<span class="key-state" *ngIf="settings.mistral_api_key_set">✓ configurée</span>
</label>
<input id="emb-mistral-key" type="password" [(ngModel)]="mistralApiKeyInput"
[placeholder]="settings.mistral_api_key_set ? 'Clé configurée (laisser vide pour ne pas changer)' : 'Saisir votre clé API Mistral'">
<label class="checkbox" *ngIf="settings.mistral_api_key_set">
<input type="checkbox" [(ngModel)]="clearMistralKey">
<span>Effacer la clé enregistrée</span>
</label>
<p class="hint">
C'est la <strong>même clé</strong> que pour le chat Mistral (une seule clé partagée).
Soumis au rate limit du tier gratuit.
</p>
</div>
<div class="form-row">
<label for="emb-mistral-model">Modèle d'embedding Mistral</label>
<input id="emb-mistral-model" type="text" [(ngModel)]="settings.mistral_embedding_model"
placeholder="mistral-embed">
</div>
</div>
</section>
<!-- Bloc Fenetre de contexte : seulement pour Ollama (num_ctx est un vrai
parametre du modele local). Pour le cloud (1min/OpenRouter), on ne fixe rien :
on utilise ce que le modele accepte, et la jauge du chat s'affiche sans max. -->

View File

@@ -4,7 +4,7 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { LucideAngularModule, ArrowLeft, RefreshCw, Save, Check, AlertCircle, Download, Trash2, Plus, X, Heart, Link2, Unlink } from 'lucide-angular';
import { SettingsService, AppSettings, AppSettingsUpdate, OneMinModelGroup, OpenRouterModel, OllamaPullEvent } from '../services/settings.service';
import { SettingsService, AppSettings, AppSettingsUpdate, OneMinModelGroup, OpenRouterModel, MistralModel, GeminiModel, OllamaPullEvent } from '../services/settings.service';
import { UpdatesService, UpdateStatus } from '../services/updates.service';
import { ConfigService } from '../services/config.service';
import { LayoutService } from '../services/layout.service';
@@ -107,6 +107,12 @@ export class SettingsComponent implements OnInit, OnDestroy {
/** Catalogue OpenRouter (chargé dynamiquement) + filtre "gratuits seulement". */
openrouterModels: OpenRouterModel[] = [];
openrouterFreeOnly = true;
/** Catalogue Mistral (dynamique si cle configuree, repli statique sinon). */
mistralModels: MistralModel[] = [];
/** Catalogue Gemini (dynamique si cle configuree, repli statique sinon). */
geminiModels: GeminiModel[] = [];
/** Fournisseur 1min.ai actuellement selectionne (filtre la liste des modeles). */
oneminProvider: string = '';
@@ -137,6 +143,16 @@ export class SettingsComponent implements OnInit, OnDestroy {
/** True si l'utilisateur a coche "effacer la cle OpenRouter". */
clearOpenrouterKey = false;
/** Cle Mistral saisie — vide = on ne touche pas a la cle persistee. */
mistralApiKeyInput = '';
/** True si l'utilisateur a coche "effacer la cle Mistral". */
clearMistralKey = false;
/** Cle Gemini saisie — vide = on ne touche pas a la cle persistee. */
geminiApiKeyInput = '';
/** True si l'utilisateur a coche "effacer la cle Gemini". */
clearGeminiKey = false;
constructor(
private settingsService: SettingsService,
private router: Router,
@@ -471,6 +487,38 @@ export class SettingsComponent implements OnInit, OnDestroy {
next: (r) => this.openrouterModels = r.models,
error: () => this.openrouterModels = []
});
this.settingsService.listMistralModels().subscribe({
next: (r) => this.mistralModels = r.models,
error: () => this.mistralModels = []
});
this.settingsService.listGeminiModels().subscribe({
next: (r) => this.geminiModels = r.models,
error: () => this.geminiModels = []
});
}
/** Options du select Mistral : liste + valeur courante garantie selectionnable. */
get mistralSelectOptions(): { id: string; label: string }[] {
const options: { id: string; label: string }[] =
this.mistralModels.map(m => ({ id: m.id, label: m.id }));
const cur = this.settings?.mistral_model;
if (cur && !options.some(o => o.id === cur)) {
options.unshift({ id: cur, label: `${cur} (actuel)` });
}
return options;
}
/** Options du select Gemini : liste + valeur courante garantie selectionnable. */
get geminiSelectOptions(): { id: string; label: string }[] {
const options: { id: string; label: string }[] =
this.geminiModels.map(m => ({ id: m.id, label: m.id }));
const cur = this.settings?.gemini_model;
if (cur && !options.some(o => o.id === cur)) {
options.unshift({ id: cur, label: `${cur} (actuel)` });
}
return options;
}
/** Modeles OpenRouter, filtres (gratuits seulement par defaut). */
@@ -557,6 +605,13 @@ export class SettingsComponent implements OnInit, OnDestroy {
llm_model: this.settings.llm_model,
onemin_model: this.settings.onemin_model,
openrouter_model: this.settings.openrouter_model,
mistral_model: this.settings.mistral_model,
gemini_model: this.settings.gemini_model,
embedding_provider: this.settings.embedding_provider,
ollama_embedding_model: this.settings.ollama_embedding_model,
mistral_embedding_model: this.settings.mistral_embedding_model,
auto_pull_embedding_model: this.settings.auto_pull_embedding_model,
rag_top_k: this.settings.rag_top_k,
llm_num_ctx: this.settings.llm_num_ctx,
import_chunk_tokens: this.settings.import_chunk_tokens,
llm_timeout_seconds: this.settings.llm_timeout_seconds
@@ -571,6 +626,16 @@ export class SettingsComponent implements OnInit, OnDestroy {
} else if (this.openrouterApiKeyInput.trim()) {
patch.openrouter_api_key = this.openrouterApiKeyInput.trim();
}
if (this.clearMistralKey) {
patch.mistral_api_key = '';
} else if (this.mistralApiKeyInput.trim()) {
patch.mistral_api_key = this.mistralApiKeyInput.trim();
}
if (this.clearGeminiKey) {
patch.gemini_api_key = '';
} else if (this.geminiApiKeyInput.trim()) {
patch.gemini_api_key = this.geminiApiKeyInput.trim();
}
this.settingsService.updateSettings(patch).subscribe({
next: (s) => {
@@ -579,6 +644,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
this.clearApiKey = false;
this.openrouterApiKeyInput = '';
this.clearOpenrouterKey = false;
this.mistralApiKeyInput = '';
this.clearMistralKey = false;
this.geminiApiKeyInput = '';
this.clearGeminiKey = false;
this.successMessage = 'Parametres sauvegardes.';
this.saving = false;
},