Plusieurs gros ajouts :
- 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:
@@ -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. -->
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user