vedaco commited on
Commit
b2351ee
Β·
verified Β·
1 Parent(s): 1f959a2

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +134 -277
server.js CHANGED
@@ -1,225 +1,160 @@
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) {
@@ -227,146 +162,68 @@ app.post('/api/generate-audio-script', async (req, res) => {
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;
 
1
  const express = require('express');
2
  const cors = require('cors');
3
+ const http = require('http');
 
 
4
  require('dotenv').config();
5
 
6
  const app = express();
7
+ app.use(cors());
 
 
 
 
 
 
8
  app.use(express.json({ limit: '50mb' }));
 
9
 
10
  const PORT = process.env.PORT || 5000;
11
 
12
+ // Ollama Configuration
13
+ const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434';
14
+ const MODEL = process.env.MODEL || 'qwen3.5:0.8b';
 
 
 
15
 
16
+ // Tera V2 System Prompt
17
+ const SYSTEM_PROMPT = `You are Tera V2, an advanced AI document intelligence assistant.
18
 
19
  Your capabilities:
20
  - Analyze complex documents, contracts, and research papers
21
+ - Extract key insights and summarize content
22
  - Answer questions based on provided documents
23
  - Generate podcast scripts and audio content
 
24
 
25
  Response Style:
26
  - Professional yet approachable
27
  - Clear and concise
28
+ - Cite specific parts of documents when answering
29
+ - Format responses for readability`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  console.log('╔════════════════════════════════════════╗');
32
+ console.log('β•‘ Tera V2 Starting... β•‘');
33
  console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•');
34
+ console.log(`Model: ${MODEL}`);
35
+ console.log(`Ollama: ${OLLAMA_URL}\n`);
36
 
37
  // Health check
38
  app.get('/health', async (req, res) => {
39
  try {
40
+ const request = http.get(`${OLLAMA_URL}/api/tags`, (response) => {
41
+ let data = '';
42
+ response.on('data', chunk => data += chunk);
43
+ response.on('end', () => {
44
+ try {
45
+ const models = JSON.parse(data).models || [];
46
+ const hasModel = models.some(m => m.name.includes(MODEL));
47
+ res.json({
48
+ status: 'ok',
49
+ service: 'Tera V2',
50
+ model: MODEL,
51
+ modelLoaded: hasModel
52
+ });
53
+ } catch (e) {
54
+ res.status(503).json({ status: 'error', message: 'Ollama not responding' });
55
+ }
56
+ });
57
+ });
58
 
59
+ request.on('error', () => {
60
+ res.status(503).json({
61
+ status: 'error',
62
+ message: 'Ollama not running',
63
+ solution: `Run: ollama run ${MODEL}`
64
+ });
 
 
 
65
  });
66
+
67
+ request.setTimeout(5000, () => {
68
+ request.destroy();
69
+ res.status(503).json({ status: 'error', message: 'Ollama timeout' });
 
70
  });
71
+
72
+ } catch (error) {
73
+ res.status(503).json({ status: 'error', message: error.message });
74
  }
75
  });
76
 
 
 
 
 
 
 
 
 
 
 
77
  // Chat endpoint with streaming
78
  app.post('/api/chat', async (req, res) => {
 
 
79
  try {
80
+ const { messages, sources } = req.body;
81
 
82
  // Build context from sources
83
  let context = '';
84
  if (sources && sources.length > 0) {
85
  context = '\n\nDOCUMENTS:\n' + sources.map((s, i) =>
86
+ `[${i + 1}] ${s.name}:\n${s.content?.substring(0, 3000) || '[Image]'}`
87
  ).join('\n\n');
88
  }
89
 
90
  const lastMessage = messages[messages.length - 1];
91
+ const prompt = `${SYSTEM_PROMPT}${context}\n\nUser: ${lastMessage.content}\n\nTera V2:`;
 
 
 
 
 
 
 
 
 
92
 
93
  res.setHeader('Content-Type', 'text/event-stream');
94
  res.setHeader('Cache-Control', 'no-cache');
 
95
 
96
+ const requestData = JSON.stringify({
97
+ model: MODEL,
98
+ prompt: prompt,
99
+ stream: true,
100
+ options: { temperature: 0.7, num_predict: 4096 }
101
+ });
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ const options = {
104
+ hostname: 'localhost',
105
+ port: 11434,
106
+ path: '/api/generate',
107
+ method: 'POST',
108
+ headers: {
109
+ 'Content-Type': 'application/json',
110
+ 'Content-Length': Buffer.byteLength(requestData)
111
+ },
112
+ timeout: 120000
113
+ };
114
 
115
+ const proxyReq = http.request(options, (proxyRes) => {
116
+ proxyRes.on('data', (chunk) => {
117
+ const lines = chunk.toString().split('\n');
118
+ for (const line of lines) {
119
+ if (line.trim()) {
120
+ try {
121
+ const data = JSON.parse(line);
122
+ if (data.response) {
123
+ res.write(`data: {"content": ${JSON.stringify(data.response)}}\n\n`);
124
+ }
125
+ if (data.done) {
126
+ res.write('data: {"done": true}\n\n');
127
+ }
128
+ } catch (e) {}
129
+ }
 
 
 
 
 
 
 
130
  }
131
+ });
132
+
133
+ proxyRes.on('end', () => {
134
+ res.write('data: {"done": true}\n\n');
135
+ res.end();
136
+ });
137
  });
138
 
139
+ proxyReq.on('error', (error) => {
140
+ console.error('Ollama error:', error.message);
141
  res.write(`data: {"error": "${error.message}"}\n\n`);
142
  res.end();
143
  });
144
 
145
+ proxyReq.write(requestData);
146
+ proxyReq.end();
147
+
148
  } catch (error) {
149
  console.error('Chat error:', error.message);
150
+ res.status(500).json({ error: error.message });
 
 
 
 
 
 
 
151
  }
152
  });
153
 
154
  // Audio script generation
155
  app.post('/api/generate-audio-script', async (req, res) => {
156
  try {
157
+ const { sources, format, instructions } = req.body;
158
 
159
  let content = '';
160
  if (sources && sources.length > 0) {
 
162
  }
163
 
164
  const formatPrompts = {
165
+ deepdive: 'Create an in-depth podcast discussion between two hosts analyzing the content.',
166
  briefing: 'Create a brief news-style summary of the key points.',
167
+ story: 'Create a narrative-driven exploration of the content.'
168
  };
169
 
170
+ const customInstr = instructions ? ` ${instructions}` : '';
171
+ const prompt = `${SYSTEM_PROMPT}\n\nYou are creating a podcast script. ${formatPrompts[format] || formatPrompts.deepdive}${customInstr}\n\nDocuments:\n${content}\n\nScript:`;
 
 
 
172
 
173
+ const requestData = JSON.stringify({
174
+ model: MODEL,
175
+ prompt: prompt,
176
+ stream: false,
177
+ options: { temperature: 0.8, num_predict: 4096 }
 
 
 
 
 
 
 
 
 
178
  });
179
 
180
+ const options = {
181
+ hostname: 'localhost',
182
+ port: 11434,
183
+ path: '/api/generate',
184
+ method: 'POST',
185
+ headers: {
186
+ 'Content-Type': 'application/json',
187
+ 'Content-Length': Buffer.byteLength(requestData)
188
+ }
189
+ };
 
 
 
190
 
191
+ const proxyReq = http.request(options, (proxyRes) => {
192
+ let data = '';
193
+ proxyRes.on('data', chunk => data += chunk);
194
+ proxyRes.on('end', () => {
195
+ try {
196
+ const response = JSON.parse(data);
197
+ res.json({ script: response.response, success: true });
198
+ } catch (e) {
199
+ res.status(500).json({ error: 'Invalid response from Ollama' });
200
+ }
201
+ });
202
  });
203
 
204
+ proxyReq.on('error', (error) => {
205
+ res.status(500).json({ error: error.message });
206
+ });
 
 
207
 
208
+ proxyReq.write(requestData);
209
+ proxyReq.end();
 
 
 
 
 
 
 
 
 
 
210
 
 
 
 
 
 
 
211
  } catch (error) {
212
  res.status(500).json({ error: error.message });
213
  }
214
  });
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  app.listen(PORT, () => {
217
  console.log('╔════════════════════════════════════════╗');
218
+ console.log('β•‘ Tera V2 is Ready! β•‘');
219
  console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•');
220
  console.log(`Server: http://localhost:${PORT}`);
221
+ console.log(`Model: ${MODEL}`);
 
 
 
 
 
 
 
222
  console.log('');
223
+ console.log('Quick Start:');
224
+ console.log(`1. Keep Ollama running: ollama run ${MODEL}`);
225
+ console.log('2. Open tera-docs.html in browser');
226
  console.log('');
227
+ console.log('No API keys - runs locally!');
228
  console.log('╔════════════════════════════════════════╗\n');
229
+ });