Prise en compte du langage de l'utilisateur pour le prompt de réponse. Si par exemple l'interface est en anglais, les IA vont favoriser l'anglais pour la réponse
This commit is contained in:
@@ -5,11 +5,6 @@ COPY package*.json ./
|
||||
RUN npm ci --include=dev --ignore-scripts --no-audit --no-fund --no-progress
|
||||
COPY . .
|
||||
|
||||
# Neutralise les URLs absolues hardcodees dans les services (dette assumee :
|
||||
# une refacto propre passerait par src/environments/*.ts + fileReplacements).
|
||||
# Le reverse proxy nginx route /api/ vers core:8080, donc chemin relatif OK.
|
||||
RUN find src -type f -name "*.ts" -exec sed -i "s|http://localhost:8080||g" {} +
|
||||
|
||||
RUN npm run build -- --configuration production
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
18
web/src/app/interceptors/language.interceptor.ts
Normal file
18
web/src/app/interceptors/language.interceptor.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { HttpInterceptorFn } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { LanguageService } from '../services/language.service';
|
||||
|
||||
/**
|
||||
* Ajoute l'entête `X-User-Language` (langue choisie dans l'UI : `fr`/`en`) à
|
||||
* toutes les requêtes HttpClient. Le Core la relaie au Brain, qui rédige alors
|
||||
* ses réponses IA dans cette langue.
|
||||
*
|
||||
* NB : les appels SSE en `fetch()` (chat, imports, notebooks) ne passent PAS par
|
||||
* les intercepteurs Angular — ils ajoutent l'entête manuellement de leur côté.
|
||||
*/
|
||||
export const languageInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const language = inject(LanguageService);
|
||||
return next(
|
||||
req.clone({ setHeaders: { 'X-User-Language': language.current } })
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { LanguageService } from './language.service';
|
||||
|
||||
/**
|
||||
* Un message d'une conversation IA (vue front).
|
||||
@@ -47,6 +48,7 @@ export type NarrativeEntityType = 'arc' | 'chapter' | 'scene' | 'character' | 'n
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AiChatService {
|
||||
private readonly translate = inject(TranslateService);
|
||||
private readonly language = inject(LanguageService);
|
||||
private readonly loreEndpoint = '/api/ai/chat/stream';
|
||||
private readonly campaignEndpoint = '/api/ai/chat/stream-campaign';
|
||||
private readonly sessionEndpoint = '/api/ai/chat/stream-session';
|
||||
@@ -110,7 +112,8 @@ export class AiChatService {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream'
|
||||
'Accept': 'text/event-stream',
|
||||
'X-User-Language': this.language.current
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal: controller.signal
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
CampaignImportProposal,
|
||||
CampaignImportStreamEvent
|
||||
} from './campaign-import.model';
|
||||
import { LanguageService } from './language.service';
|
||||
|
||||
/**
|
||||
* Service HTTP pour l'import d'un PDF de campagne.
|
||||
@@ -17,7 +18,7 @@ import {
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CampaignImportService {
|
||||
constructor(private http: HttpClient, private translate: TranslateService) {}
|
||||
constructor(private http: HttpClient, private translate: TranslateService, private language: LanguageService) {}
|
||||
|
||||
importStructureStream(campaignId: string, file: File): Observable<CampaignImportStreamEvent> {
|
||||
return new Observable<CampaignImportStreamEvent>((subscriber) => {
|
||||
@@ -27,7 +28,7 @@ export class CampaignImportService {
|
||||
|
||||
fetch(`/api/campaigns/${campaignId}/import-structure/stream`, {
|
||||
method: 'POST',
|
||||
headers: { 'Accept': 'text/event-stream' },
|
||||
headers: { 'Accept': 'text/event-stream', 'X-User-Language': this.language.current },
|
||||
body: form,
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { GameSystem, GameSystemCreate, RulesImportResponse, RulesImportStreamEvent } from './game-system.model';
|
||||
import { LanguageService } from './language.service';
|
||||
|
||||
/**
|
||||
* Service HTTP pour les GameSystems (systèmes de JDR).
|
||||
@@ -11,7 +12,7 @@ import { GameSystem, GameSystemCreate, RulesImportResponse, RulesImportStreamEve
|
||||
export class GameSystemService {
|
||||
private apiUrl = '/api/game-systems';
|
||||
|
||||
constructor(private http: HttpClient, private translate: TranslateService) {}
|
||||
constructor(private http: HttpClient, private translate: TranslateService, private language: LanguageService) {}
|
||||
|
||||
getAll(): Observable<GameSystem[]> {
|
||||
return this.http.get<GameSystem[]>(this.apiUrl);
|
||||
@@ -63,7 +64,7 @@ export class GameSystemService {
|
||||
|
||||
fetch(`${this.apiUrl}/import-rules/stream`, {
|
||||
method: 'POST',
|
||||
headers: { 'Accept': 'text/event-stream' },
|
||||
headers: { 'Accept': 'text/event-stream', 'X-User-Language': this.language.current },
|
||||
body: form,
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Notebook, NotebookArchive, NotebookDetail, NotebookSource, NotebookChatEvent } from './notebook.model';
|
||||
import { LanguageService } from './language.service';
|
||||
|
||||
/**
|
||||
* Service des notebooks (atelier RAG) : CRUD, upload/indexation de sources,
|
||||
@@ -12,7 +13,7 @@ import { Notebook, NotebookArchive, NotebookDetail, NotebookSource, NotebookChat
|
||||
export class NotebookService {
|
||||
private readonly apiUrl = '/api/notebooks';
|
||||
|
||||
constructor(private http: HttpClient, private zone: NgZone, private translate: TranslateService) {}
|
||||
constructor(private http: HttpClient, private zone: NgZone, private translate: TranslateService, private language: LanguageService) {}
|
||||
|
||||
listByCampaign(campaignId: string): Observable<Notebook[]> {
|
||||
return this.http.get<Notebook[]>(`${this.apiUrl}/campaign/${campaignId}`);
|
||||
@@ -73,7 +74,7 @@ export class NotebookService {
|
||||
try {
|
||||
const response = await fetch(`${this.apiUrl}/${notebookId}/chat/stream`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
|
||||
headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream', 'X-User-Language': this.language.current },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
message, deep,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { provideTranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { ConfigService } from './app/services/config.service';
|
||||
import { LanguageService } from './app/services/language.service';
|
||||
import { sessionExpiredInterceptor } from './app/interceptors/session-expired.interceptor';
|
||||
import { languageInterceptor } from './app/interceptors/language.interceptor';
|
||||
|
||||
// withPreloading(PreloadAllModules) : une fois l'app initiale rendue, Angular
|
||||
// telecharge en arriere-plan tous les chunks lazy-loades. Consequence : la
|
||||
@@ -18,7 +19,7 @@ import { sessionExpiredInterceptor } from './app/interceptors/session-expired.in
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
provideZoneChangeDetection(),provideRouter(routes, withPreloading(PreloadAllModules)),
|
||||
provideHttpClient(withInterceptors([sessionExpiredInterceptor])),
|
||||
provideHttpClient(withInterceptors([sessionExpiredInterceptor, languageInterceptor])),
|
||||
provideTranslateService({
|
||||
loader: provideTranslateHttpLoader({
|
||||
prefix: 'assets/i18n/',
|
||||
|
||||
Reference in New Issue
Block a user