Ajout d'open router en fournisseur IA ; ajout de la possibilité de mettre des conditions de déverouillage pour les chapitres optionnels
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

This commit is contained in:
2026-06-05 00:23:19 +02:00
parent 211e26dae1
commit 9ea43a1889
19 changed files with 382 additions and 50 deletions

View File

@@ -32,6 +32,10 @@
<input type="radio" name="provider" value="onemin" [(ngModel)]="settings.llm_provider">
<span>1min.ai (cloud)</span>
</label>
<label class="radio">
<input type="radio" name="provider" value="openrouter" [(ngModel)]="settings.llm_provider">
<span>OpenRouter (cloud)</span>
</label>
</div>
</div>
</section>
@@ -173,8 +177,45 @@
</div>
</section>
<!-- Bloc Fenetre de contexte -->
<section class="card" *ngIf="settings">
<!-- Bloc OpenRouter -->
<section class="card" *ngIf="settings && settings.llm_provider === 'openrouter'">
<h2>Configuration OpenRouter</h2>
<div class="form-row">
<label for="openrouter-key">Cle API</label>
<input
id="openrouter-key"
type="password"
[(ngModel)]="openrouterApiKeyInput"
[placeholder]="settings.openrouter_api_key_set ? 'Cle configuree (laisser vide pour ne pas changer)' : 'Saisir votre cle API (sk-or-...)'">
<label class="checkbox" *ngIf="settings.openrouter_api_key_set">
<input type="checkbox" [(ngModel)]="clearOpenrouterKey">
<span>Effacer la cle enregistree</span>
</label>
</div>
<div class="form-row">
<label for="openrouter-model">Modele</label>
<label class="checkbox">
<input type="checkbox" [(ngModel)]="openrouterFreeOnly">
<span>Gratuits seulement</span>
</label>
<select id="openrouter-model" [(ngModel)]="settings.openrouter_model">
<option *ngFor="let o of openrouterSelectOptions" [value]="o.id">{{ o.label }}</option>
</select>
<p class="hint">
Liste chargee automatiquement depuis OpenRouter. Pour rester <strong>gratuit</strong> :
un modele marque <code>· gratuit</code> ou le routeur <code>openrouter/free</code>
(choisit un modele gratuit tout seul). Pour les imports, prefere un gratuit a
<strong>grand contexte</strong> (valeur « ctx »).
</p>
</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. -->
<section class="card" *ngIf="settings && settings.llm_provider === 'ollama'">
<h2>Fenetre de contexte</h2>
<!-- Ollama : slider borne par le max du modele -->
@@ -205,20 +246,6 @@
</p>
</div>
<!-- 1min.ai : saisie libre (pas d'introspection possible) -->
<div class="form-row" *ngIf="settings.llm_provider === 'onemin'">
<label for="llm-num-ctx-onemin">Fenetre de contexte (tokens)</label>
<input
id="llm-num-ctx-onemin"
type="number"
min="2048"
step="1024"
[(ngModel)]="settings.llm_num_ctx">
<p class="hint">
A regler selon la capacite du modele 1min.ai choisi (ex: 128 000 pour gpt-4o,
200 000 pour claude-sonnet). Sert de plafond a la jauge de contexte du chat.
</p>
</div>
</section>
<!-- Bloc Import de PDF -->

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, OllamaPullEvent } from '../services/settings.service';
import { SettingsService, AppSettings, AppSettingsUpdate, OneMinModelGroup, OpenRouterModel, OllamaPullEvent } from '../services/settings.service';
import { UpdatesService, UpdateStatus } from '../services/updates.service';
import { ConfigService } from '../services/config.service';
import { LayoutService } from '../services/layout.service';
@@ -103,6 +103,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
settings: AppSettings | null = null;
ollamaModels: string[] = [];
oneminGroups: OneMinModelGroup[] = [];
/** Catalogue OpenRouter (chargé dynamiquement) + filtre "gratuits seulement". */
openrouterModels: OpenRouterModel[] = [];
openrouterFreeOnly = true;
/** Fournisseur 1min.ai actuellement selectionne (filtre la liste des modeles). */
oneminProvider: string = '';
@@ -128,6 +132,11 @@ export class SettingsComponent implements OnInit, OnDestroy {
/** True si l'utilisateur a coche "effacer la cle". */
clearApiKey = false;
/** Cle OpenRouter saisie — vide = on ne touche pas a la cle persistee. */
openrouterApiKeyInput = '';
/** True si l'utilisateur a coche "effacer la cle OpenRouter". */
clearOpenrouterKey = false;
constructor(
private settingsService: SettingsService,
private router: Router,
@@ -457,6 +466,35 @@ export class SettingsComponent implements OnInit, OnDestroy {
},
error: () => this.oneminGroups = []
});
this.settingsService.listOpenRouterModels().subscribe({
next: (r) => this.openrouterModels = r.models,
error: () => this.openrouterModels = []
});
}
/** Modeles OpenRouter, filtres (gratuits seulement par defaut). */
get filteredOpenrouterModels(): OpenRouterModel[] {
return this.openrouterFreeOnly
? this.openrouterModels.filter(m => m.free)
: this.openrouterModels;
}
/** Options du select OpenRouter : routeur auto + liste filtree + valeur courante. */
get openrouterSelectOptions(): { id: string; label: string }[] {
const options: { id: string; label: string }[] = [
{ id: 'openrouter/free', label: 'openrouter/free — routeur auto (gratuit)' }
];
for (const m of this.filteredOpenrouterModels) {
const ctx = m.context_length ? `${m.context_length.toLocaleString()} ctx` : '';
options.push({ id: m.id, label: `${m.id}${ctx}${m.free ? ' · gratuit' : ''}` });
}
// Garantit que le modele actuellement configure reste selectionnable.
const cur = this.settings?.openrouter_model;
if (cur && !options.some(o => o.id === cur)) {
options.splice(1, 0, { id: cur, label: `${cur} (actuel)` });
}
return options;
}
/** Deduit le fournisseur a partir du modele actuellement configure. */
@@ -518,6 +556,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
ollama_base_url: this.settings.ollama_base_url,
llm_model: this.settings.llm_model,
onemin_model: this.settings.onemin_model,
openrouter_model: this.settings.openrouter_model,
llm_num_ctx: this.settings.llm_num_ctx,
import_chunk_tokens: this.settings.import_chunk_tokens,
llm_timeout_seconds: this.settings.llm_timeout_seconds
@@ -527,12 +566,19 @@ export class SettingsComponent implements OnInit, OnDestroy {
} else if (this.oneminApiKeyInput.trim()) {
patch.onemin_api_key = this.oneminApiKeyInput.trim();
}
if (this.clearOpenrouterKey) {
patch.openrouter_api_key = '';
} else if (this.openrouterApiKeyInput.trim()) {
patch.openrouter_api_key = this.openrouterApiKeyInput.trim();
}
this.settingsService.updateSettings(patch).subscribe({
next: (s) => {
this.settings = { ...s };
this.oneminApiKeyInput = '';
this.clearApiKey = false;
this.openrouterApiKeyInput = '';
this.clearOpenrouterKey = false;
this.successMessage = 'Parametres sauvegardes.';
this.saving = false;
},