| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- import { KnowledgeFile, Message, Role, Language, AppSettings, ModelConfig } from "../types";
- import { ragService } from './ragService';
- const buildSystemInstruction = (files: KnowledgeFile[], settings: AppSettings, langInstruction: string) => {
- const fileNames = files.map(f => f.name).join(", ");
- return `
- You are an intelligent knowledge base assistant operating as a RAG system.
-
- System Configuration:
- - Retrieval: Top ${settings.topK} chunks, Rerank: ${settings.enableRerank ? 'Enabled' : 'Disabled'}
- - Knowledge Base Files: ${fileNames}
-
- You have access to the content of these files in your context.
-
- Your goal is to answer user questions primarily based on the content of these files.
-
- Strict Citation Rules:
- 1. Whenever you use information from a file, you MUST cite the source filename at the end of the sentence or paragraph.
- 2. Citation format: [filename.extension].
- 3. If the answer is not found in the files, state so clearly.
- 4. ${langInstruction}
- `;
- };
- // --- OpenAI Compatible Implementation ---
- const callOpenAICompatible = async (
- currentPrompt: string,
- files: KnowledgeFile[],
- history: Message[],
- modelConfig: ModelConfig,
- settings: AppSettings,
- systemInstruction: string,
- apiKey: string
- ): Promise<string> => {
- if (!modelConfig.baseUrl) throw new Error("Base URL is required");
- // Construct OpenAI format messages
- const messages: any[] = [
- { role: "system", content: systemInstruction }
- ];
- // Add history
- history.forEach(msg => {
- messages.push({
- role: msg.role === Role.USER ? "user" : "assistant",
- content: msg.text
- });
- });
- // Current User Message Construction (Supports Vision)
- const contentParts: any[] = [];
- // 1. Add Text Prompt
- contentParts.push({ type: "text", text: currentPrompt });
- // 2. Add Images/Files
- files.forEach((file) => {
- if (file.type.startsWith("image/") && modelConfig.type === "vision") {
- contentParts.push({
- type: "image_url",
- image_url: {
- url: `data:${file.type};base64,${file.content}`
- }
- });
- } else {
- // For non-image files or if vision is not supported, append as text
- try {
- const decodedText = atob(file.content);
- contentParts.push({
- type: "text",
- text: `\n--- Context from file: ${file.name} ---\n${decodedText.substring(0, 10000)}... (truncated)\n`
- });
- } catch (e) {
- contentParts.push({
- type: "text",
- text: `\n[File attached: ${file.name} (${file.type}) - Content processing skipped]\n`
- });
- }
- }
- });
- messages.push({ role: "user", content: contentParts });
- const response = await fetch(`${modelConfig.baseUrl}/chat/completions`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "Authorization": `Bearer ${apiKey}`
- },
- body: JSON.stringify({
- model: modelConfig.modelId,
- messages: messages,
- temperature: settings.temperature,
- max_tokens: settings.maxTokens,
- stream: false
- })
- });
- if (!response.ok) {
- const err = await response.text();
- throw new Error(`API Error: ${response.status} - ${err}`);
- }
- const data = await response.json();
- return data.choices?.[0]?.message?.content || "NO_RESPONSE_TEXT";
- };
- export const generateResponse = async (
- currentPrompt: string,
- files: KnowledgeFile[],
- history: Message[],
- language: Language,
- modelConfig: ModelConfig,
- settings: AppSettings,
- authToken?: string,
- onSearchStart?: () => void,
- onSearchComplete?: (results: any) => void
- ): Promise<string> => {
- // 1. Resolve API Key: Use model specific key
- let apiKey = modelConfig.apiKey;
- console.log('Model config:', modelConfig);
- console.log('API Key present:', !!apiKey);
- const langInstructionMap: Record<Language, string> = {
- zh: "请始终使用Chinese回答。",
- en: "Please always answer in English.",
- ja: "常にJapaneseで答えてください。"
- };
- const langInstruction = langInstructionMap[language];
- // RAG search (when knowledge base files exist)
- let ragPrompt = currentPrompt;
- let ragSources: string[] = [];
- if (files.length > 0 && authToken) {
- try {
- onSearchStart?.();
- console.log('Starting RAG search with prompt:', currentPrompt);
- const ragResponse = await ragService.search(currentPrompt, {
- ...settings,
- language
- }, authToken);
- console.log('RAG search response:', ragResponse);
- if (ragResponse && ragResponse.hasRelevantContent) {
- ragPrompt = ragResponse.ragPrompt;
- ragSources = ragResponse.sources;
- console.log('Using RAG enhanced prompt');
- } else {
- console.log('No relevant content found, using original prompt');
- }
- onSearchComplete?.(ragResponse?.searchResults || []);
- } catch (error) {
- console.warn('RAG search failed, using original prompt:', error);
- onSearchComplete?.([]);
- } finally {
- // Ensure search status is reset
- setTimeout(() => {
- onSearchComplete?.([]);
- }, 100);
- }
- }
- const systemInstruction = buildSystemInstruction(files, settings, langInstruction);
- try {
- // API key is optional - allow local models
- // --- OpenAI Compatible API Logic ---
- return await callOpenAICompatible(
- ragPrompt,
- files,
- history,
- modelConfig,
- settings,
- systemInstruction,
- apiKey || "ollama",
- );
- } catch (error: any) {
- console.error("AI Service Error:", error);
- // Provide more detailed error information
- if (error.name === 'TypeError' && error.message.includes('fetch')) {
- throw new Error('Network connection failed. Please check server status');
- }
- throw new Error(error.message || "API_ERROR");
- }
- };
|