2026-05-22 10:10:11 +08:00
|
|
|
import { Injectable, Logger } from '@nestjs/common';
|
2026-05-21 23:57:59 +08:00
|
|
|
import type { AiChatDto } from './dto/ai-chat.dto';
|
2026-05-22 10:43:18 +08:00
|
|
|
import { AdminConversationService } from '../admin-conversation/admin-conversation.service';
|
2026-05-21 23:57:59 +08:00
|
|
|
|
2026-05-22 00:20:34 +08:00
|
|
|
const HERMES_API_URL = 'http://10.2.0.7:8642/v1/chat/completions';
|
|
|
|
|
const HERMES_API_KEY = 'zhixi-hermes-key-2026';
|
|
|
|
|
|
2026-05-21 23:57:59 +08:00
|
|
|
@Injectable()
|
|
|
|
|
export class AdminAiChatService {
|
|
|
|
|
private readonly logger = new Logger(AdminAiChatService.name);
|
|
|
|
|
|
2026-05-22 10:43:18 +08:00
|
|
|
constructor(private readonly conversationService: AdminConversationService) {}
|
2026-05-21 23:57:59 +08:00
|
|
|
|
2026-05-22 10:43:18 +08:00
|
|
|
async chat(dto: AiChatDto, adminUserId: string) {
|
|
|
|
|
const sessionId = dto.conversationId
|
|
|
|
|
? await this.conversationService.getSessionId(dto.conversationId, adminUserId)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
// Auto-create conversation if none provided
|
|
|
|
|
const conversationId = dto.conversationId
|
|
|
|
|
?? (await this.conversationService.create(adminUserId)).id;
|
|
|
|
|
|
2026-05-22 11:03:24 +08:00
|
|
|
// Save user message
|
|
|
|
|
const userMsg = dto.messages[dto.messages.length - 1];
|
|
|
|
|
if (userMsg && userMsg.role === 'user') {
|
|
|
|
|
await this.conversationService.saveMessage(conversationId, 'user', userMsg.content);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:43:18 +08:00
|
|
|
const result = await this.callHermes(dto.messages, sessionId);
|
|
|
|
|
|
2026-05-22 11:03:24 +08:00
|
|
|
// Save assistant reply
|
|
|
|
|
await this.conversationService.saveMessage(conversationId, 'assistant', result.content);
|
|
|
|
|
|
2026-05-22 10:43:18 +08:00
|
|
|
return { ...result, conversationId };
|
2026-05-22 00:20:34 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:43:18 +08:00
|
|
|
private async callHermes(
|
|
|
|
|
messages: Array<{ role: string; content: string }>,
|
|
|
|
|
sessionId: string | null,
|
|
|
|
|
) {
|
2026-05-22 00:20:34 +08:00
|
|
|
const start = Date.now();
|
2026-05-22 10:43:18 +08:00
|
|
|
const headers: Record<string, string> = {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
Authorization: 'Bearer ' + HERMES_API_KEY,
|
|
|
|
|
};
|
|
|
|
|
if (sessionId) {
|
|
|
|
|
headers['X-Hermes-Session-Id'] = sessionId;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 00:20:34 +08:00
|
|
|
const resp = await fetch(HERMES_API_URL, {
|
|
|
|
|
method: 'POST',
|
2026-05-22 10:43:18 +08:00
|
|
|
headers,
|
2026-05-22 00:20:34 +08:00
|
|
|
body: JSON.stringify({
|
|
|
|
|
model: 'hermes-agent',
|
|
|
|
|
messages,
|
|
|
|
|
temperature: 0.7,
|
|
|
|
|
max_tokens: 4096,
|
|
|
|
|
}),
|
|
|
|
|
signal: AbortSignal.timeout(120_000),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!resp.ok) {
|
2026-05-22 10:10:11 +08:00
|
|
|
const text = await resp.text().catch(() => '');
|
|
|
|
|
throw new Error(`Hermes API error ${resp.status}: ${text}`);
|
2026-05-22 00:20:34 +08:00
|
|
|
}
|
2026-05-21 23:57:59 +08:00
|
|
|
|
2026-05-22 00:20:34 +08:00
|
|
|
const data = await resp.json();
|
|
|
|
|
const content = data.choices?.[0]?.message?.content || '';
|
|
|
|
|
const usage = data.usage || {};
|
|
|
|
|
|
|
|
|
|
this.logger.log('Hermes chat: ' + (Date.now() - start) + 'ms, tokens: ' +
|
|
|
|
|
(usage.prompt_tokens || 0) + '/' + (usage.completion_tokens || 0));
|
|
|
|
|
return { content, usage: { model: 'hermes-agent', inputTokens: usage.prompt_tokens, outputTokens: usage.completion_tokens } };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 00:02:14 +08:00
|
|
|
getDashboardConfig() {
|
|
|
|
|
return {
|
|
|
|
|
url: 'http://10.2.0.7:9119',
|
2026-05-22 00:20:34 +08:00
|
|
|
apiUrl: 'http://10.2.0.7:8642/v1',
|
|
|
|
|
description: 'Hermes Agent Dashboard — 4核4G 上的 AI Agent',
|
2026-05-22 00:02:14 +08:00
|
|
|
};
|
|
|
|
|
}
|
2026-05-22 10:10:11 +08:00
|
|
|
}
|