forked from hangshuo652/aurak
M3: console.log -> Logger + UI redesign (QuestionBank) + S7/A9/A10/A11/U11 bug fixes + #1/#2/#3/#4 enhancements + i18n for QuestionBank pages
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { ModelConfig } from '../types';
|
||||
import { I18nService } from '../i18n/i18n.service';
|
||||
|
||||
@Injectable()
|
||||
export class ApiService {
|
||||
private readonly logger = new Logger(ApiService.name);
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
// Simple health check method
|
||||
@@ -23,7 +25,7 @@ export class ApiService {
|
||||
const response = await llm.invoke(prompt);
|
||||
return response.content.toString();
|
||||
} catch (error) {
|
||||
console.error('LangChain call failed:', error);
|
||||
this.logger.error('LangChain call failed:', error);
|
||||
if (error.message?.includes('401')) {
|
||||
throw new Error(this.i18nService.getMessage('invalidApiKey'));
|
||||
}
|
||||
|
||||
@@ -561,7 +561,9 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
|
||||
`[startSession] Session ${savedSession.id} created and saved`,
|
||||
);
|
||||
|
||||
this.cleanupOldSessions(userId);
|
||||
// cleanupOldSessions permanently destroys data - disabled to preserve history.
|
||||
// Admins can use batch-delete endpoint for manual cleanup.
|
||||
// this.cleanupOldSessions(userId);
|
||||
|
||||
return savedSession;
|
||||
}
|
||||
@@ -777,6 +779,33 @@ const initialState: Partial<EvaluationState> = {
|
||||
});
|
||||
if (!session) throw new NotFoundException('Session not found');
|
||||
|
||||
if (session.status === AssessmentStatus.IN_PROGRESS) {
|
||||
const now = new Date();
|
||||
const startTime = session.startedAt ? new Date(session.startedAt) : now;
|
||||
const questionStartTime = session.currentQuestionStartedAt ? new Date(session.currentQuestionStartedAt) : now;
|
||||
const totalElapsed = Math.floor((now.getTime() - startTime.getTime()) / 1000);
|
||||
const questionElapsed = Math.floor((now.getTime() - questionStartTime.getTime()) / 1000);
|
||||
|
||||
if (totalElapsed >= session.totalTimeLimit || questionElapsed >= session.perQuestionTimeLimit) {
|
||||
session.status = AssessmentStatus.COMPLETED;
|
||||
session.finalReport = totalElapsed >= session.totalTimeLimit
|
||||
? '评测总时间已用尽,评估已自动结束'
|
||||
: '单题答题时间已用尽,评估已自动结束';
|
||||
if (session.finalScore === null || session.finalScore === undefined) {
|
||||
session.finalScore = 0;
|
||||
}
|
||||
await this.sessionRepository.save(session);
|
||||
this.logger.log(`[submitAnswer] Session ${sessionId} auto-ended due to timeout`);
|
||||
return {
|
||||
assessmentSessionId: sessionId,
|
||||
status: 'COMPLETED',
|
||||
timeout: true,
|
||||
finalScore: session.finalScore,
|
||||
finalReport: session.finalReport,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const model = await this.getModel(session.tenantId);
|
||||
await this.ensureGraphState(sessionId, session);
|
||||
const content = await this.getSessionContent(session);
|
||||
@@ -844,18 +873,18 @@ const initialState: Partial<EvaluationState> = {
|
||||
const scores = finalResult.scores as Record<string, number>;
|
||||
const questions = finalResult.questions || [];
|
||||
const weightConfig = session.templateJson?.weightConfig || { prompt: 50, other: 50 };
|
||||
const passingScore = session.templateJson?.passingScore || 90;
|
||||
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
|
||||
|
||||
if (questions.length > 0 && Object.keys(scores).length > 0) {
|
||||
const { finalScore, dimensionScores, radarData } = this.calculateScores(
|
||||
questions,
|
||||
scores,
|
||||
weightConfig,
|
||||
);
|
||||
session.finalScore = finalScore;
|
||||
(session as any).dimensionScores = dimensionScores;
|
||||
(session as any).radarData = radarData;
|
||||
(session as any).passed = finalScore >= passingScore;
|
||||
if (questions.length > 0 && Object.keys(scores).length > 0) {
|
||||
const { finalScore, dimensionScores, radarData } = this.calculateScores(
|
||||
questions,
|
||||
scores,
|
||||
weightConfig,
|
||||
);
|
||||
session.finalScore = finalScore;
|
||||
(session as any).dimensionScores = dimensionScores;
|
||||
(session as any).radarData = radarData;
|
||||
(session as any).passed = finalScore >= passingScore;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,6 +947,35 @@ const initialState: Partial<EvaluationState> = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.status === AssessmentStatus.IN_PROGRESS) {
|
||||
const now = new Date();
|
||||
const startTime = session.startedAt ? new Date(session.startedAt) : now;
|
||||
const questionStartTime = session.currentQuestionStartedAt ? new Date(session.currentQuestionStartedAt) : now;
|
||||
const totalElapsed = Math.floor((now.getTime() - startTime.getTime()) / 1000);
|
||||
const questionElapsed = Math.floor((now.getTime() - questionStartTime.getTime()) / 1000);
|
||||
|
||||
if (totalElapsed >= session.totalTimeLimit || questionElapsed >= session.perQuestionTimeLimit) {
|
||||
session.status = AssessmentStatus.COMPLETED;
|
||||
session.finalReport = totalElapsed >= session.totalTimeLimit
|
||||
? '评测总时间已用尽,评估已自动结束'
|
||||
: '单题答题时间已用尽,评估已自动结束';
|
||||
if (session.finalScore === null || session.finalScore === undefined) {
|
||||
session.finalScore = 0;
|
||||
}
|
||||
await this.sessionRepository.save(session);
|
||||
this.logger.log(`[submitAnswerStream] Session ${sessionId} auto-ended due to timeout`);
|
||||
observer.next({
|
||||
assessmentSessionId: sessionId,
|
||||
status: 'COMPLETED',
|
||||
timeout: true,
|
||||
finalScore: session.finalScore,
|
||||
finalReport: session.finalReport,
|
||||
});
|
||||
observer.complete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const model = await this.getModel(session.tenantId);
|
||||
const content = await this.getSessionContent(session);
|
||||
await this.ensureGraphState(sessionId, session);
|
||||
@@ -1037,7 +1095,7 @@ const initialState: Partial<EvaluationState> = {
|
||||
const scores = finalData.scores;
|
||||
const questions = finalData.questions || [];
|
||||
const weightConfig = session.templateJson?.weightConfig || { prompt: 50, other: 50 };
|
||||
const passingScore = session.templateJson?.passingScore || 90;
|
||||
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
|
||||
|
||||
if (questions.length > 0 && Object.keys(scores).length > 0) {
|
||||
const { finalScore, dimensionScores, radarData } = this.calculateScores(
|
||||
@@ -1049,6 +1107,7 @@ const initialState: Partial<EvaluationState> = {
|
||||
(session as any).dimensionScores = dimensionScores;
|
||||
(session as any).radarData = radarData;
|
||||
(session as any).passed = finalScore >= passingScore;
|
||||
|
||||
this.logger.log(
|
||||
`[DimensionScoring] Session ${sessionId} Final Score: ${finalScore}, Passed: ${finalScore >= passingScore}`,
|
||||
);
|
||||
@@ -1188,55 +1247,46 @@ const initialState: Partial<EvaluationState> = {
|
||||
const historicalMessages = this.hydrateMessages(session.messages);
|
||||
const existingQuestions = session.questions_json || [];
|
||||
const hasQuestionsFromBank = existingQuestions.length > 0;
|
||||
const scoresRecord: Record<string, number> = {};
|
||||
if (session.feedbackHistory) {
|
||||
for (const fh of session.feedbackHistory) {
|
||||
if (fh.score && fh.questionId) scoresRecord[fh.questionId] = fh.score;
|
||||
}
|
||||
}
|
||||
|
||||
const recoveredState: any = {
|
||||
assessmentSessionId: sessionId,
|
||||
knowledgeBaseId:
|
||||
session.knowledgeBaseId || session.knowledgeGroupId || '',
|
||||
messages: historicalMessages,
|
||||
feedbackHistory: this.hydrateMessages(
|
||||
session.feedbackHistory || [],
|
||||
),
|
||||
questions: existingQuestions,
|
||||
currentQuestionIndex: session.currentQuestionIndex || 0,
|
||||
followUpCount: session.followUpCount || 0,
|
||||
shouldFollowUp: false,
|
||||
scores: scoresRecord,
|
||||
questionCount: session.templateJson?.questionCount || 5,
|
||||
difficultyDistribution:
|
||||
session.templateJson?.difficultyDistribution,
|
||||
style: session.templateJson?.style,
|
||||
keywords: session.templateJson?.keywords,
|
||||
language: session.language || 'zh',
|
||||
report: session.finalReport || undefined,
|
||||
};
|
||||
|
||||
if (hasQuestionsFromBank) {
|
||||
this.logger.log(
|
||||
`[ensureGraphState] Using ${existingQuestions.length} questions from question bank`,
|
||||
);
|
||||
await this.graph.updateState(
|
||||
{ configurable: { thread_id: sessionId } },
|
||||
{
|
||||
assessmentSessionId: sessionId,
|
||||
knowledgeBaseId:
|
||||
session.knowledgeBaseId || session.knowledgeGroupId || '',
|
||||
messages: historicalMessages,
|
||||
feedbackHistory: this.hydrateMessages(
|
||||
session.feedbackHistory || [],
|
||||
),
|
||||
questions: existingQuestions,
|
||||
currentQuestionIndex: session.currentQuestionIndex || 0,
|
||||
followUpCount: session.followUpCount || 0,
|
||||
questionCount: session.templateJson?.questionCount || 5,
|
||||
difficultyDistribution:
|
||||
session.templateJson?.difficultyDistribution,
|
||||
style: session.templateJson?.style,
|
||||
keywords: session.templateJson?.keywords,
|
||||
},
|
||||
'grader',
|
||||
);
|
||||
} else {
|
||||
await this.graph.updateState(
|
||||
{ configurable: { thread_id: sessionId } },
|
||||
{
|
||||
assessmentSessionId: sessionId,
|
||||
knowledgeBaseId:
|
||||
session.knowledgeBaseId || session.knowledgeGroupId || '',
|
||||
messages: historicalMessages,
|
||||
feedbackHistory: this.hydrateMessages(
|
||||
session.feedbackHistory || [],
|
||||
),
|
||||
questions: session.questions_json || [],
|
||||
currentQuestionIndex: session.currentQuestionIndex || 0,
|
||||
followUpCount: session.followUpCount || 0,
|
||||
questionCount: session.templateJson?.questionCount || 5,
|
||||
difficultyDistribution:
|
||||
session.templateJson?.difficultyDistribution,
|
||||
style: session.templateJson?.style,
|
||||
keywords: session.templateJson?.keywords,
|
||||
},
|
||||
'grader',
|
||||
);
|
||||
}
|
||||
|
||||
await this.graph.updateState(
|
||||
{ configurable: { thread_id: sessionId } },
|
||||
recoveredState,
|
||||
'interviewer',
|
||||
);
|
||||
} else {
|
||||
this.logger.log(`Initializing new state for session ${sessionId}`);
|
||||
const content = await this.getSessionContent(session);
|
||||
@@ -1350,7 +1400,7 @@ const initialState: Partial<EvaluationState> = {
|
||||
}
|
||||
|
||||
if (session.status !== AssessmentStatus.COMPLETED) {
|
||||
throw new Error('Session not completed');
|
||||
throw new BadRequestException('Session not completed yet');
|
||||
}
|
||||
|
||||
const existing = await this.certificateRepository.findOne({
|
||||
@@ -1474,19 +1524,15 @@ const initialState: Partial<EvaluationState> = {
|
||||
|
||||
const sessions = await qb.take(100).getMany();
|
||||
|
||||
const dimensionScores: Record<string, number[]> = {
|
||||
PROMPT: [],
|
||||
LLM: [],
|
||||
IDE: [],
|
||||
DEV_PATTERN: [],
|
||||
WORK_CAPABILITY: [],
|
||||
};
|
||||
const dimensionScores: Record<string, number[]> = {};
|
||||
|
||||
for (const session of sessions) {
|
||||
const messages = session.messages || [];
|
||||
for (const msg of messages) {
|
||||
if (msg.dimension && msg.score !== undefined) {
|
||||
dimensionScores[msg.dimension]?.push(msg.score);
|
||||
const scores = (session as any).dimensionScores || {};
|
||||
for (const [dim, score] of Object.entries(scores)) {
|
||||
if (dimensionScores[dim]) {
|
||||
dimensionScores[dim].push(score as number);
|
||||
} else {
|
||||
dimensionScores[dim] = [score as number];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,7 +1616,7 @@ const initialState: Partial<EvaluationState> = {
|
||||
}
|
||||
|
||||
session.finalScore = newScore;
|
||||
const passingScore = session.templateJson?.passingScore || 90;
|
||||
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
|
||||
(session as any).passed = newScore >= passingScore;
|
||||
session.reviewedBy = reviewerId;
|
||||
session.reviewedAt = new Date();
|
||||
|
||||
@@ -97,4 +97,16 @@ export class CreateTemplateDto {
|
||||
@Max(100)
|
||||
@IsOptional()
|
||||
passingScore?: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(60)
|
||||
@Max(7200)
|
||||
@IsOptional()
|
||||
totalTimeLimit?: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(30)
|
||||
@Max(1800)
|
||||
@IsOptional()
|
||||
perQuestionTimeLimit?: number;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class QuestionBankService {
|
||||
page?: number,
|
||||
limit?: number,
|
||||
): Promise<{ data: QuestionBank[]; total: number } | QuestionBank[]> {
|
||||
console.log('[QuestionBank findAll] userId:', userId, 'tenantId:', tenantId);
|
||||
this.logger.log('[QuestionBank findAll] userId: ' + userId + ', tenantId: ' + tenantId);
|
||||
const queryBuilder = this.bankRepository
|
||||
.createQueryBuilder('bank')
|
||||
.leftJoinAndSelect('bank.template', 'template');
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
UnauthorizedException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
@@ -25,6 +26,8 @@ import * as path from 'path';
|
||||
*/
|
||||
@Injectable()
|
||||
export class CombinedAuthGuard implements CanActivate {
|
||||
private readonly logger = new Logger(CombinedAuthGuard.name);
|
||||
|
||||
// We extend AuthGuard('jwt') functionality by composition
|
||||
private jwtGuard: ReturnType<typeof AuthGuard>;
|
||||
|
||||
@@ -55,7 +58,7 @@ export class CombinedAuthGuard implements CanActivate {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(
|
||||
this.logger.log(
|
||||
`[CombinedAuthGuard] Checking auth for route: ${request.method} ${request.url}`,
|
||||
);
|
||||
|
||||
@@ -160,7 +163,7 @@ export class CombinedAuthGuard implements CanActivate {
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
console.error(`[CombinedAuthGuard] JWT Auth Error:`, e);
|
||||
this.logger.error('[CombinedAuthGuard] JWT Auth Error: ' + e);
|
||||
throw e instanceof UnauthorizedException
|
||||
? e
|
||||
: new UnauthorizedException('Authentication required');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Logger,
|
||||
Post,
|
||||
Request,
|
||||
Res,
|
||||
@@ -36,6 +37,8 @@ class StreamChatDto {
|
||||
@Controller('chat')
|
||||
@UseGuards(CombinedAuthGuard)
|
||||
export class ChatController {
|
||||
private readonly logger = new Logger(ChatController.name);
|
||||
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private modelConfigService: ModelConfigService,
|
||||
@@ -49,7 +52,7 @@ export class ChatController {
|
||||
@Res() res: Response,
|
||||
) {
|
||||
try {
|
||||
console.log('Full Request Body:', JSON.stringify(body, null, 2));
|
||||
this.logger.log('Full Request Body:', JSON.stringify(body, null, 2));
|
||||
const {
|
||||
message,
|
||||
history = [],
|
||||
@@ -71,22 +74,22 @@ export class ChatController {
|
||||
} = body;
|
||||
const userId = req.user.id;
|
||||
|
||||
console.log('=== Chat Debug Info ===');
|
||||
console.log('User ID:', userId);
|
||||
console.log('Message:', message);
|
||||
console.log('User Language:', userLanguage);
|
||||
console.log('Selected Embedding ID:', selectedEmbeddingId);
|
||||
console.log('Selected LLM ID:', selectedLLMId);
|
||||
console.log('Selected Groups:', selectedGroups);
|
||||
console.log('Selected Files:', selectedFiles);
|
||||
console.log('History ID:', historyId);
|
||||
console.log('Temperature:', temperature);
|
||||
console.log('Max Tokens:', maxTokens);
|
||||
console.log('Top K:', topK);
|
||||
console.log('Similarity Threshold:', similarityThreshold);
|
||||
console.log('Rerank Similarity Threshold:', rerankSimilarityThreshold);
|
||||
console.log('Query Expansion:', enableQueryExpansion);
|
||||
console.log('HyDE:', enableHyDE);
|
||||
this.logger.log('=== Chat Debug Info ===');
|
||||
this.logger.log('User ID:', userId);
|
||||
this.logger.log('Message:', message);
|
||||
this.logger.log('User Language:', userLanguage);
|
||||
this.logger.log('Selected Embedding ID:', selectedEmbeddingId);
|
||||
this.logger.log('Selected LLM ID:', selectedLLMId);
|
||||
this.logger.log('Selected Groups:', selectedGroups);
|
||||
this.logger.log('Selected Files:', selectedFiles);
|
||||
this.logger.log('History ID:', historyId);
|
||||
this.logger.log('Temperature:', temperature);
|
||||
this.logger.log('Max Tokens:', maxTokens);
|
||||
this.logger.log('Top K:', topK);
|
||||
this.logger.log('Similarity Threshold:', similarityThreshold);
|
||||
this.logger.log('Rerank Similarity Threshold:', rerankSimilarityThreshold);
|
||||
this.logger.log('Query Expansion:', enableQueryExpansion);
|
||||
this.logger.log('HyDE:', enableHyDE);
|
||||
|
||||
const role = req.user.role;
|
||||
const tenantId = req.user.tenantId;
|
||||
@@ -105,14 +108,14 @@ export class ChatController {
|
||||
if (selectedLLMId) {
|
||||
// Find specifically selected model
|
||||
llmModel = await this.modelConfigService.findOne(selectedLLMId);
|
||||
console.log('使用选中的LLM模型:', llmModel.name);
|
||||
this.logger.log('使用选中的LLM模型:', llmModel.name);
|
||||
} else {
|
||||
// Use organization's default LLM from Index Chat Config (strict)
|
||||
llmModel = await this.modelConfigService.findDefaultByType(
|
||||
tenantId,
|
||||
ModelType.LLM,
|
||||
);
|
||||
console.log(
|
||||
this.logger.log(
|
||||
'最终使用的LLM模型 (默认):',
|
||||
llmModel ? llmModel.name : '无',
|
||||
);
|
||||
@@ -162,7 +165,7 @@ export class ChatController {
|
||||
res.write('data: [DONE]\n\n');
|
||||
res.end();
|
||||
} catch (error) {
|
||||
console.error('Stream chat error:', error);
|
||||
this.logger.error('Stream chat error:', error);
|
||||
try {
|
||||
res.write(
|
||||
`data: ${JSON.stringify({ type: 'error', data: error.message || 'Server Error' })}\n\n`,
|
||||
@@ -170,7 +173,7 @@ export class ChatController {
|
||||
res.write('data: [DONE]\n\n');
|
||||
res.end();
|
||||
} catch (writeError) {
|
||||
console.error('Failed to write error response:', writeError);
|
||||
this.logger.error('Failed to write error response:', writeError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +223,7 @@ export class ChatController {
|
||||
res.write('data: [DONE]\n\n');
|
||||
res.end();
|
||||
} catch (error) {
|
||||
console.error('Stream assist error:', error);
|
||||
this.logger.error('Stream assist error:', error);
|
||||
res.write(
|
||||
`data: ${JSON.stringify({ type: 'error', data: error.message || 'Server Error' })}\n\n`,
|
||||
);
|
||||
|
||||
@@ -71,30 +71,30 @@ export class ChatService {
|
||||
enableHyDE?: boolean, // New
|
||||
tenantId?: string, // New: tenant isolation
|
||||
): AsyncGenerator<{ type: 'content' | 'sources' | 'historyId'; data: any }> {
|
||||
console.log('=== ChatService.streamChat ===');
|
||||
console.log('User ID:', userId);
|
||||
console.log('User language:', userLanguage);
|
||||
console.log('Selected embedding model ID:', selectedEmbeddingId);
|
||||
console.log('Selected groups:', selectedGroups);
|
||||
console.log('Selected files:', selectedFiles);
|
||||
console.log('History ID:', historyId);
|
||||
console.log('Temperature:', temperature);
|
||||
console.log('Max Tokens:', maxTokens);
|
||||
console.log('Top K:', topK);
|
||||
console.log('Similarity threshold:', similarityThreshold);
|
||||
console.log('Rerank threshold:', rerankSimilarityThreshold);
|
||||
console.log('Query expansion:', enableQueryExpansion);
|
||||
console.log('HyDE:', enableHyDE);
|
||||
console.log('Model configuration:', {
|
||||
this.logger.log('=== ChatService.streamChat ===');
|
||||
this.logger.log('User ID:', userId);
|
||||
this.logger.log('User language:', userLanguage);
|
||||
this.logger.log('Selected embedding model ID:', selectedEmbeddingId);
|
||||
this.logger.log('Selected groups:', selectedGroups);
|
||||
this.logger.log('Selected files:', selectedFiles);
|
||||
this.logger.log('History ID:', historyId);
|
||||
this.logger.log('Temperature:', temperature);
|
||||
this.logger.log('Max Tokens:', maxTokens);
|
||||
this.logger.log('Top K:', topK);
|
||||
this.logger.log('Similarity threshold:', similarityThreshold);
|
||||
this.logger.log('Rerank threshold:', rerankSimilarityThreshold);
|
||||
this.logger.log('Query expansion:', enableQueryExpansion);
|
||||
this.logger.log('HyDE:', enableHyDE);
|
||||
this.logger.log('Model configuration:', {
|
||||
name: modelConfig.name,
|
||||
modelId: modelConfig.modelId,
|
||||
baseUrl: modelConfig.baseUrl,
|
||||
});
|
||||
console.log(
|
||||
this.logger.log(
|
||||
'API Key prefix:',
|
||||
modelConfig.apiKey?.substring(0, 10) + '...',
|
||||
);
|
||||
console.log('API Key length:', modelConfig.apiKey?.length);
|
||||
this.logger.log('API Key length:', modelConfig.apiKey?.length);
|
||||
|
||||
// Get current language setting (keeping LANGUAGE_CONFIG for backward compatibility, now uses i18n service)
|
||||
// Use actual language based on user settings
|
||||
@@ -113,7 +113,7 @@ export class ChatService {
|
||||
selectedGroups,
|
||||
);
|
||||
currentHistoryId = searchHistory.id;
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage(
|
||||
'creatingHistory',
|
||||
effectiveUserLanguage,
|
||||
@@ -143,7 +143,7 @@ export class ChatService {
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage(
|
||||
'usingEmbeddingModel',
|
||||
effectiveUserLanguage,
|
||||
@@ -156,7 +156,7 @@ export class ChatService {
|
||||
);
|
||||
|
||||
// 2. Search using user's query directly
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage('startingSearch', effectiveUserLanguage),
|
||||
);
|
||||
yield {
|
||||
@@ -204,7 +204,7 @@ export class ChatService {
|
||||
// HybridSearch returns ES hit structure, but RagSearchResult is normalized
|
||||
// BuildContext expects {fileName, content}. RagSearchResult has these
|
||||
searchResults = ragResults;
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage(
|
||||
'searchResultsCount',
|
||||
effectiveUserLanguage,
|
||||
@@ -274,7 +274,7 @@ export class ChatService {
|
||||
};
|
||||
}
|
||||
} catch (searchError) {
|
||||
console.error(
|
||||
this.logger.error(
|
||||
this.i18nService.getMessage(
|
||||
'searchFailedLog',
|
||||
effectiveUserLanguage,
|
||||
@@ -461,14 +461,14 @@ ${instruction}`;
|
||||
try {
|
||||
// Join keywords into search string
|
||||
const combinedQuery = keywords.join(' ');
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage('searchString', userLanguage) +
|
||||
combinedQuery,
|
||||
);
|
||||
|
||||
// Check if embedding model ID is provided
|
||||
if (!embeddingModelId) {
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage(
|
||||
'embeddingModelIdNotProvided',
|
||||
userLanguage,
|
||||
@@ -478,7 +478,7 @@ ${instruction}`;
|
||||
}
|
||||
|
||||
// Use actual embedding vector
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage('generatingEmbeddings', userLanguage),
|
||||
);
|
||||
const queryEmbedding = await this.embeddingService.getEmbeddings(
|
||||
@@ -486,7 +486,7 @@ ${instruction}`;
|
||||
embeddingModelId,
|
||||
);
|
||||
const queryVector = queryEmbedding[0];
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage('embeddingsGenerated', userLanguage) +
|
||||
this.i18nService.getMessage('dimensions', userLanguage) +
|
||||
':',
|
||||
@@ -494,7 +494,7 @@ ${instruction}`;
|
||||
);
|
||||
|
||||
// Hybrid search
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage('performingHybridSearch', userLanguage),
|
||||
);
|
||||
const results = await this.elasticsearchService.hybridSearch(
|
||||
@@ -507,7 +507,7 @@ ${instruction}`;
|
||||
explicitFileIds, // Pass explicit file IDs
|
||||
tenantId, // Pass tenant ID
|
||||
);
|
||||
console.log(
|
||||
this.logger.log(
|
||||
this.i18nService.getMessage('esSearchCompleted', userLanguage) +
|
||||
this.i18nService.getMessage('resultsCount', userLanguage) +
|
||||
':',
|
||||
@@ -516,7 +516,7 @@ ${instruction}`;
|
||||
|
||||
return results.slice(0, 10);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
this.logger.error(
|
||||
this.i18nService.getMessage('hybridSearchFailed', userLanguage) + ':',
|
||||
error,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
const logger = new Logger('JsonUtils');
|
||||
|
||||
/**
|
||||
* Safely parses JSON from a string, handling markdown code blocks and leading/trailing text.
|
||||
*/
|
||||
@@ -40,9 +44,9 @@ export function safeParseJson<T = any>(text: string): T | null {
|
||||
try {
|
||||
return JSON.parse(jsonStr) as T;
|
||||
} catch (error) {
|
||||
console.error('[safeParseJson] Failed to parse JSON:', error);
|
||||
console.error('[safeParseJson] Original text:', text);
|
||||
console.error('[safeParseJson] Extracted string:', jsonStr);
|
||||
logger.error('[safeParseJson] Failed to parse JSON:', error);
|
||||
logger.error('[safeParseJson] Original text:', text);
|
||||
logger.error('[safeParseJson] Extracted string:', jsonStr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
UseGuards,
|
||||
Request,
|
||||
Query,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { CombinedAuthGuard } from '../auth/combined-auth.guard';
|
||||
import { RolesGuard } from '../auth/roles.guard';
|
||||
@@ -24,6 +25,8 @@ import { I18nService } from '../i18n/i18n.service';
|
||||
@Controller('knowledge-groups')
|
||||
@UseGuards(CombinedAuthGuard, RolesGuard)
|
||||
export class KnowledgeGroupController {
|
||||
private readonly logger = new Logger(KnowledgeGroupController.name);
|
||||
|
||||
constructor(
|
||||
private readonly groupService: KnowledgeGroupService,
|
||||
private readonly i18nService: I18nService,
|
||||
@@ -43,7 +46,7 @@ export class KnowledgeGroupController {
|
||||
@Post()
|
||||
@Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
|
||||
async create(@Body() createGroupDto: CreateGroupDto, @Request() req) {
|
||||
console.log('[KnowledgeGroup] create called, user:', req.user);
|
||||
this.logger.log('[KnowledgeGroup] create called, user: ' + JSON.stringify(req.user));
|
||||
return await this.groupService.create(
|
||||
req.user.id,
|
||||
req.user.tenantId,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
Injectable,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
ForbiddenException,
|
||||
Inject,
|
||||
@@ -47,6 +48,8 @@ export interface PaginatedGroups {
|
||||
|
||||
@Injectable()
|
||||
export class KnowledgeGroupService {
|
||||
private readonly logger = new Logger(KnowledgeGroupService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(KnowledgeGroup)
|
||||
private groupRepository: Repository<KnowledgeGroup>,
|
||||
@@ -62,7 +65,7 @@ export class KnowledgeGroupService {
|
||||
userId: string,
|
||||
tenantId: string,
|
||||
): Promise<GroupWithFileCount[]> {
|
||||
console.log('[KnowledgeGroup findAll] userId:', userId, 'tenantId:', tenantId);
|
||||
this.logger.log('[KnowledgeGroup findAll] userId: ' + userId + ', tenantId: ' + tenantId);
|
||||
// Return all groups for the tenant with file counts
|
||||
const queryBuilder = this.groupRepository
|
||||
.createQueryBuilder('group')
|
||||
@@ -147,7 +150,7 @@ export class KnowledgeGroupService {
|
||||
tenantId: string,
|
||||
createGroupDto: CreateGroupDto,
|
||||
): Promise<KnowledgeGroup> {
|
||||
console.log('[KnowledgeGroup create] userId:', userId, 'tenantId:', tenantId);
|
||||
this.logger.log('[KnowledgeGroup create] userId: ' + userId + ', tenantId: ' + tenantId);
|
||||
const group = this.groupRepository.create({
|
||||
...createGroupDto,
|
||||
parentId: createGroupDto.parentId ?? null,
|
||||
@@ -155,7 +158,7 @@ export class KnowledgeGroupService {
|
||||
});
|
||||
|
||||
const saved = await this.groupRepository.save(group);
|
||||
console.log('[KnowledgeGroup create] saved group tenantId:', saved.tenantId);
|
||||
this.logger.log('[KnowledgeGroup create] saved group tenantId: ' + saved.tenantId);
|
||||
return saved;
|
||||
}
|
||||
|
||||
@@ -229,7 +232,7 @@ export class KnowledgeGroupService {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
this.logger.error(
|
||||
`Failed to delete file ${file.id} when deleting group ${id}`,
|
||||
error,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Note } from './note.entity';
|
||||
@@ -11,6 +11,8 @@ import { I18nService } from '../i18n/i18n.service';
|
||||
|
||||
@Injectable()
|
||||
export class NoteService {
|
||||
private readonly logger = new Logger(NoteService.name);
|
||||
|
||||
// Directory will be created dynamically per user
|
||||
private getScreenshotsDir(userId: string) {
|
||||
return path.join(process.cwd(), 'uploads', 'notes-screenshots', userId);
|
||||
@@ -153,7 +155,7 @@ export class NoteService {
|
||||
}
|
||||
|
||||
// Optional: Add logging to help debug permission issues
|
||||
console.log(`User ${userId} attempting to add note to group ${groupId}`);
|
||||
this.logger.log('User ' + userId + ' attempting to add note to group ' + groupId);
|
||||
}
|
||||
|
||||
if (categoryId === '') {
|
||||
@@ -176,7 +178,7 @@ export class NoteService {
|
||||
screenshot.buffer,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('OCR extraction failed:', error);
|
||||
this.logger.error('OCR extraction failed:', error);
|
||||
// Continue without OCR text if extraction fails
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
Controller,
|
||||
Logger,
|
||||
Post,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
@@ -14,6 +15,8 @@ import { I18nService } from '../i18n/i18n.service';
|
||||
@UseGuards(CombinedAuthGuard)
|
||||
@UseGuards(CombinedAuthGuard)
|
||||
export class OcrController {
|
||||
private readonly logger = new Logger(OcrController.name);
|
||||
|
||||
constructor(
|
||||
private readonly ocrService: OcrService,
|
||||
private readonly i18n: I18nService,
|
||||
@@ -22,14 +25,14 @@ export class OcrController {
|
||||
@Post('recognize')
|
||||
@UseInterceptors(FileInterceptor('image'))
|
||||
async recognizeText(@UploadedFile() image: Express.Multer.File) {
|
||||
console.log('OCR recognition endpoint called');
|
||||
this.logger.log('OCR recognition endpoint called');
|
||||
if (!image) {
|
||||
console.error('No image uploaded');
|
||||
this.logger.error('No image uploaded');
|
||||
throw new Error(this.i18n.getMessage('noImageUploaded'));
|
||||
}
|
||||
console.log(`Received image. Size: ${image.size} bytes`);
|
||||
this.logger.log('Received image. Size: ' + image.size + ' bytes');
|
||||
const text = await this.ocrService.extractTextFromImage(image.buffer);
|
||||
console.log(`OCR extraction completed. Text length: ${text.length}`);
|
||||
this.logger.log('OCR extraction completed. Text length: ' + text.length);
|
||||
return { text };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export class UserService implements OnModuleInit {
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
console.log(
|
||||
this.logger.log(
|
||||
`[UserService] Creating user: ${username}, isAdmin: ${isAdmin}`,
|
||||
);
|
||||
const user = await this.usersRepository.save({
|
||||
@@ -403,10 +403,7 @@ export class UserService implements OnModuleInit {
|
||||
role: UserRole.SUPER_ADMIN,
|
||||
});
|
||||
|
||||
console.log('\n=== Admin account created ===');
|
||||
console.log('Username: admin');
|
||||
console.log('Password:', randomPassword);
|
||||
console.log('========================================\n');
|
||||
this.logger.log('Admin account created (username: admin, password: ' + randomPassword + ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user