vedaco commited on
Commit
7d7d13d
Β·
verified Β·
1 Parent(s): c7e3f75

Upload server.js

Browse files
Files changed (1) hide show
  1. server.js +372 -0
server.js ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const axios = require('axios');
4
+ const fs = require('fs').promises;
5
+ const path = require('path');
6
+ require('dotenv').config();
7
+
8
+ const app = express();
9
+
10
+ // Security middleware
11
+ app.use(cors({
12
+ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost', 'http://127.0.0.1', 'null'],
13
+ credentials: true
14
+ }));
15
+
16
+ app.use(express.json({ limit: '50mb' }));
17
+ app.use(express.static('public'));
18
+
19
+ const PORT = process.env.PORT || 5000;
20
+
21
+ // Tera V2 Configuration
22
+ const AI_CONFIG = {
23
+ url: process.env.AI_MODEL_URL || 'http://localhost:11434/api/generate',
24
+ model: process.env.AI_MODEL_NAME || 'qwen3.5:0.8b',
25
+ displayName: process.env.DISPLAY_NAME || 'Tera V2'
26
+ };
27
+
28
+ // Tera V2 System Prompt - Customizes the AI behavior
29
+ const TERAV2_PROMPT = `You are Tera V2, an advanced AI document intelligence assistant created by Tera Labs.
30
+
31
+ Your capabilities:
32
+ - Analyze complex documents, contracts, and research papers
33
+ - Extract key insights and summarize content
34
+ - Answer questions based on provided documents
35
+ - Generate podcast scripts and audio content
36
+ - Support multiple document formats (PDF, TXT, MD, images)
37
+
38
+ Response Style:
39
+ - Professional yet approachable
40
+ - Clear and concise
41
+ - Always cite specific parts of documents when answering
42
+ - Format responses for readability (bullet points, sections)
43
+ - If information isn't in the documents, say so clearly
44
+
45
+ When analyzing documents:
46
+ 1. Identify the document type and purpose
47
+ 2. Extract key information and data points
48
+ 3. Highlight important clauses or findings
49
+ 4. Flag any potential issues or concerns
50
+ 5. Provide actionable recommendations
51
+
52
+ Remember: You are Tera V2. You are helpful, accurate, and efficient.`;
53
+
54
+ // Dataset collection for future training
55
+ const DATASET_PATH = path.join(__dirname, 'dataset');
56
+ const LOGS_PATH = path.join(__dirname, 'logs');
57
+
58
+ // Ensure directories exist
59
+ async function ensureDirectories() {
60
+ try {
61
+ await fs.mkdir(DATASET_PATH, { recursive: true });
62
+ await fs.mkdir(LOGS_PATH, { recursive: true });
63
+ } catch (e) {}
64
+ }
65
+ ensureDirectories();
66
+
67
+ // Logging function
68
+ async function logConversation(data) {
69
+ try {
70
+ const date = new Date().toISOString().split('T')[0];
71
+ const logFile = path.join(LOGS_PATH, `conversations-${date}.jsonl`);
72
+ await fs.appendFile(logFile, JSON.stringify(data) + '\n');
73
+ } catch (e) {}
74
+ }
75
+
76
+ console.log('╔════════════════════════════════════════╗');
77
+ console.log('β•‘ Tera V2 - Production β•‘');
78
+ console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•');
79
+ console.log(`Model: ${AI_CONFIG.displayName} (${AI_CONFIG.model})`);
80
+ console.log(`Status: Initializing...\n`);
81
+
82
+ // Health check
83
+ app.get('/health', async (req, res) => {
84
+ try {
85
+ const response = await axios.get('http://localhost:11434/api/tags', { timeout: 5000 });
86
+ const models = response.data.models || [];
87
+ const hasModel = models.some(m => m.name.includes(AI_CONFIG.model));
88
+
89
+ res.json({
90
+ status: 'ok',
91
+ service: 'Tera V2 Backend',
92
+ version: '2.0.0',
93
+ provider: 'Ollama (Local)',
94
+ model: AI_CONFIG.model,
95
+ displayName: AI_CONFIG.displayName,
96
+ modelLoaded: hasModel,
97
+ mode: 'production'
98
+ });
99
+ } catch (error) {
100
+ res.status(503).json({
101
+ status: 'error',
102
+ message: 'Ollama not running',
103
+ solution: `Run: ollama run ${AI_CONFIG.model}`
104
+ });
105
+ }
106
+ });
107
+
108
+ // Get Tera V2 info
109
+ app.get('/api/info', (req, res) => {
110
+ res.json({
111
+ name: 'Tera V2',
112
+ version: '2.0.0',
113
+ model: AI_CONFIG.model,
114
+ capabilities: ['document-analysis', 'chat', 'audio-generation', 'multimodal']
115
+ });
116
+ });
117
+
118
+ // Chat endpoint with streaming
119
+ app.post('/api/chat', async (req, res) => {
120
+ const startTime = Date.now();
121
+
122
+ try {
123
+ const { messages, sources, docId } = req.body;
124
+
125
+ // Build context from sources
126
+ let context = '';
127
+ if (sources && sources.length > 0) {
128
+ context = '\n\nDOCUMENTS:\n' + sources.map((s, i) =>
129
+ `[${i + 1}] ${s.name}:\n${s.content?.substring(0, 3000) || '[Image content]'}`
130
+ ).join('\n\n');
131
+ }
132
+
133
+ const lastMessage = messages[messages.length - 1];
134
+ const prompt = `${TERAV2_PROMPT}${context}\n\nUser: ${lastMessage.content}\n\nTera V2:`;
135
+
136
+ // Log for dataset collection
137
+ logConversation({
138
+ timestamp: new Date().toISOString(),
139
+ type: 'chat',
140
+ docId,
141
+ hasSources: sources?.length > 0,
142
+ query: lastMessage.content
143
+ });
144
+
145
+ res.setHeader('Content-Type', 'text/event-stream');
146
+ res.setHeader('Cache-Control', 'no-cache');
147
+ res.setHeader('Connection', 'keep-alive');
148
+
149
+ const response = await axios.post(
150
+ AI_CONFIG.url,
151
+ {
152
+ model: AI_CONFIG.model,
153
+ prompt: prompt,
154
+ stream: true,
155
+ options: {
156
+ temperature: 0.7,
157
+ num_predict: 4096,
158
+ top_p: 0.9
159
+ }
160
+ },
161
+ {
162
+ responseType: 'stream',
163
+ timeout: 120000
164
+ }
165
+ );
166
+
167
+ let fullResponse = '';
168
+
169
+ response.data.on('data', (chunk) => {
170
+ const lines = chunk.toString().split('\n');
171
+ for (const line of lines) {
172
+ if (line.trim()) {
173
+ try {
174
+ const data = JSON.parse(line);
175
+ if (data.response) {
176
+ fullResponse += data.response;
177
+ res.write(`data: {"content": ${JSON.stringify(data.response)}}\n\n`);
178
+ }
179
+ if (data.done) {
180
+ const duration = Date.now() - startTime;
181
+ logConversation({
182
+ timestamp: new Date().toISOString(),
183
+ type: 'response',
184
+ docId,
185
+ responseLength: fullResponse.length,
186
+ duration
187
+ });
188
+ res.write('data: {"done": true}\n\n');
189
+ }
190
+ } catch (e) {}
191
+ }
192
+ }
193
+ });
194
+
195
+ response.data.on('end', () => {
196
+ res.write('data: {"done": true}\n\n');
197
+ res.end();
198
+ });
199
+
200
+ response.data.on('error', (error) => {
201
+ console.error('Stream error:', error);
202
+ res.write(`data: {"error": "${error.message}"}\n\n`);
203
+ res.end();
204
+ });
205
+
206
+ } catch (error) {
207
+ console.error('Chat error:', error.message);
208
+ if (error.code === 'ECONNREFUSED') {
209
+ res.status(503).json({
210
+ error: 'Tera V2 AI is not running',
211
+ solution: `Run: ollama run ${AI_CONFIG.model}`
212
+ });
213
+ } else {
214
+ res.status(500).json({ error: error.message });
215
+ }
216
+ }
217
+ });
218
+
219
+ // Audio script generation
220
+ app.post('/api/generate-audio-script', async (req, res) => {
221
+ try {
222
+ const { sources, format, instructions, docId } = req.body;
223
+
224
+ let content = '';
225
+ if (sources && sources.length > 0) {
226
+ content = sources.map(s => s.content?.substring(0, 4000) || '').join('\n\n');
227
+ }
228
+
229
+ const formatPrompts = {
230
+ deepdive: 'Create an in-depth podcast discussion between two hosts (Host 1: Alex, Host 2: Sam) analyzing the content. Include different perspectives and detailed insights.',
231
+ briefing: 'Create a brief news-style summary of the key points.',
232
+ story: 'Create a narrative-driven exploration of the content, telling it as an engaging story.'
233
+ };
234
+
235
+ const customInstr = instructions ? ` Additional instructions: ${instructions}` : '';
236
+
237
+ const prompt = `${TERAV2_PROMPT}
238
+
239
+ You are creating a podcast script. ${formatPrompts[format] || formatPrompts.deepdive}${customInstr}
240
+
241
+ Format each speaker line as "Host 1: text" or "Host 2: text".
242
+
243
+ Documents to analyze:
244
+ ${content}
245
+
246
+ Generate a compelling podcast script:`;
247
+
248
+ // Log
249
+ logConversation({
250
+ timestamp: new Date().toISOString(),
251
+ type: 'audio-generation',
252
+ docId,
253
+ format,
254
+ hasCustomInstructions: !!instructions
255
+ });
256
+
257
+ const response = await axios.post(
258
+ AI_CONFIG.url,
259
+ {
260
+ model: AI_CONFIG.model,
261
+ prompt: prompt,
262
+ stream: false,
263
+ options: {
264
+ temperature: 0.8,
265
+ num_predict: 4096
266
+ }
267
+ },
268
+ { timeout: 120000 }
269
+ );
270
+
271
+ res.json({
272
+ script: response.data.response,
273
+ model: AI_CONFIG.displayName,
274
+ success: true
275
+ });
276
+
277
+ } catch (error) {
278
+ console.error('Audio script error:', error.message);
279
+ res.status(500).json({ error: error.message });
280
+ }
281
+ });
282
+
283
+ // Save training data endpoint (for future fine-tuning)
284
+ app.post('/api/save-training-data', async (req, res) => {
285
+ try {
286
+ const { prompt, response, quality } = req.body;
287
+
288
+ const trainingExample = {
289
+ instruction: prompt,
290
+ input: "",
291
+ output: response,
292
+ quality: quality || 'good',
293
+ timestamp: new Date().toISOString()
294
+ };
295
+
296
+ const date = new Date().toISOString().split('T')[0];
297
+ const file = path.join(DATASET_PATH, `training-${date}.jsonl`);
298
+
299
+ await fs.appendFile(file, JSON.stringify(trainingExample) + '\n');
300
+
301
+ res.json({ success: true });
302
+ } catch (error) {
303
+ res.status(500).json({ error: error.message });
304
+ }
305
+ });
306
+
307
+ // Export dataset
308
+ app.get('/api/dataset', async (req, res) => {
309
+ try {
310
+ const files = await fs.readdir(DATASET_PATH);
311
+ const allData = [];
312
+
313
+ for (const file of files) {
314
+ if (file.endsWith('.jsonl')) {
315
+ const content = await fs.readFile(path.join(DATASET_PATH, file), 'utf-8');
316
+ const lines = content.trim().split('\n');
317
+ lines.forEach(line => {
318
+ try {
319
+ allData.push(JSON.parse(line));
320
+ } catch (e) {}
321
+ });
322
+ }
323
+ }
324
+
325
+ res.json({
326
+ count: allData.length,
327
+ data: allData
328
+ });
329
+ } catch (error) {
330
+ res.status(500).json({ error: error.message });
331
+ }
332
+ });
333
+
334
+ // List available Ollama models
335
+ app.get('/api/models', async (req, res) => {
336
+ try {
337
+ const response = await axios.get('http://localhost:11434/api/tags');
338
+ res.json(response.data);
339
+ } catch (error) {
340
+ res.status(503).json({ error: 'Ollama not running' });
341
+ }
342
+ });
343
+
344
+ // Error handling
345
+ app.use((err, req, res, next) => {
346
+ console.error(err.stack);
347
+ res.status(500).json({ error: 'Internal server error' });
348
+ });
349
+
350
+ app.listen(PORT, () => {
351
+ console.log('╔════════════════════════════════════════╗');
352
+ console.log('β•‘ Tera V2 is Ready! πŸš€ β•‘');
353
+ console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•');
354
+ console.log(`Server: http://localhost:${PORT}`);
355
+ console.log(`AI Model: ${AI_CONFIG.displayName}`);
356
+ console.log(`Status: Production Ready`);
357
+ console.log('');
358
+ console.log('Features:');
359
+ console.log(' βœ… Document Analysis');
360
+ console.log(' βœ… Audio Generation');
361
+ console.log(' βœ… Dataset Collection (for training)');
362
+ console.log(' βœ… 100% Offline (No API keys)');
363
+ console.log('');
364
+ console.log('To use Tera V2:');
365
+ console.log(`1. Keep Ollama running: ollama run ${AI_CONFIG.model}`);
366
+ console.log('2. Open tera-docs.html in your browser');
367
+ console.log('');
368
+ console.log('Legal: Qwen license allows full commercial use & rebranding');
369
+ console.log('╔════════════════════════════════════════╗\n');
370
+ });
371
+
372
+ module.exports = app;