Files
connectai/src/bridge.ts
T

161 lines
6.8 KiB
TypeScript

import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';
import axios from 'axios';
import { getConfig, _getBrainDir, _isBrainDirExplicitlySet, findBrainFiles } from './utils';
export interface BridgeInterface {
injectSystemMessage(msg: string): void;
getHistoryText(): string;
sendPromptFromExtension(prompt: string): void;
brainEnabled: boolean;
findBrainFiles(dir: string): string[];
}
export class BridgeServer {
private server: http.Server | null = null;
constructor(private provider: BridgeInterface) {}
public start(port: number = 4825) {
this.server = http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
const url = req.url || '';
if (req.method === 'GET' && url === '/ping') {
this.handlePing(res);
} else if (req.method === 'POST' && url === '/api/exam') {
this.handlePost(req, res, this.processExam.bind(this));
} else if (req.method === 'POST' && url === '/api/evaluate') {
this.handlePost(req, res, this.processEvaluate.bind(this));
} else if (req.method === 'GET' && url === '/api/evaluate-history') {
this.processEvaluateHistory(res);
} else if (req.method === 'POST' && url === '/api/brain-inject') {
this.handlePost(req, res, this.processBrainInject.bind(this));
} else {
res.writeHead(404);
res.end();
}
});
this.server.listen(port, '127.0.0.1', () => {
console.log(`[G1nation] Bridge Server active on port ${port}`);
});
}
private handlePing(res: http.ServerResponse) {
const brainDir = _getBrainDir();
const brainCount = fs.existsSync(brainDir) ? findBrainFiles(brainDir).length : 0;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ok',
msg: 'G1nation Bridge Ready',
config: getConfig(),
brain: { fileCount: brainCount, enabled: this.provider.brainEnabled }
}));
}
private handlePost(req: http.IncomingMessage, res: http.ServerResponse, processor: (data: any, res: http.ServerResponse) => Promise<void>) {
let body = '';
req.on('data', chunk => body += chunk.toString());
req.on('end', async () => {
try {
const parsed = JSON.parse(body);
await processor(parsed, res);
} catch (e: any) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: e.message }));
}
});
}
private async processExam(data: any, res: http.ServerResponse) {
const prompt = data.prompt || 'Automatic Prompt Received';
this.provider.sendPromptFromExtension(`[Bridge Input] ${prompt}`);
const result = await this.callAI(prompt);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, rawOutput: result }));
}
private async processEvaluate(data: any, res: http.ServerResponse) {
const prompt = data.prompt || '';
this.provider.injectSystemMessage(`**[A.U Evaluation Started]**\nAnalyzing input: _"${prompt.substring(0, 60)}..."_`);
const evaluationPrompt = `[EVALUATION REQUEST]\nPlease evaluate the following input and provide a score/reasoning:\n\n${prompt}`;
const result = await this.callAI(evaluationPrompt);
this.provider.injectSystemMessage(`**[Evaluation Complete]**\n${result.substring(0, 300)}...`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ rawOutput: result }));
}
private async processEvaluateHistory(res: http.ServerResponse) {
const historyText = this.provider.getHistoryText();
if (!historyText || historyText.length < 50) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: "Insufficient chat history for evaluation." }));
return;
}
this.provider.injectSystemMessage(`**[History Evaluation]** Analyzing conversation flow...`);
const historyPrompt = `Analyze this conversation history and return a JSON score for Math, Logic, Creative, and Code (0-100):\n\n${historyText.slice(-6000)}`;
const result = await this.callAI(historyPrompt);
const jsonMatch = result.match(/\{[\s\S]*?\}/);
if (jsonMatch) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(jsonMatch[0]);
} else {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: "Failed to parse evaluation JSON", raw: result }));
}
}
private async processBrainInject(data: any, res: http.ServerResponse) {
const { title, markdown, prompt } = data;
let brainDir = _getBrainDir();
if (!fs.existsSync(brainDir)) {
fs.mkdirSync(brainDir, { recursive: true });
}
const today = new Date().toISOString().split('T')[0];
const datePath = path.join(brainDir, '00_Raw', today);
fs.mkdirSync(datePath, { recursive: true });
const safeTitle = title.replace(/[^a-zA-Z0-9가-힣]/gi, '_');
const filePath = path.join(datePath, `${safeTitle}.md`);
fs.writeFileSync(filePath, markdown, 'utf-8');
this.provider.injectSystemMessage(`**[Brain Inject]** Knowledge captured: ${title}`);
const result = await this.callAI(prompt || `Analyze this new knowledge: ${title}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, rawOutput: result }));
}
private async callAI(prompt: string): Promise<string> {
const config = getConfig();
const isLMStudio = config.ollamaUrl.includes('1234') || config.ollamaUrl.includes('v1');
const apiUrl = isLMStudio ? `${config.ollamaUrl}/v1/chat/completions` : `${config.ollamaUrl}/api/chat`;
const payload = {
model: config.defaultModel,
messages: [{ role: 'user', content: prompt }],
stream: false
};
const res = await axios.post(apiUrl, payload, { timeout: config.timeout });
return isLMStudio ? (res.data.choices?.[0]?.message?.content || '') : (res.data.message?.content || '');
}
}