@beltoinc/slyos-sdk 1.5.4 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,370 +1,728 @@
1
- #!/usr/bin/env node
2
-
3
- import 'dotenv/config';
4
- import readline from 'readline';
5
- import fetch from 'node-fetch';
6
- import SlyOS from '@beltoinc/slyos-sdk';
7
-
8
- // Color codes for terminal output
9
- const colors = {
10
- reset: '\x1b[0m',
11
- bright: '\x1b[1m',
12
- dim: '\x1b[2m',
13
- cyan: '\x1b[36m',
14
- green: '\x1b[32m',
15
- yellow: '\x1b[33m',
16
- blue: '\x1b[34m',
17
- red: '\x1b[31m',
18
- magenta: '\x1b[35m'
19
- };
20
-
21
- // Configuration
22
- const config = {
23
- apiKey: process.env.SLYOS_API_KEY || 'YOUR_API_KEY',
24
- model: process.env.SLYOS_MODEL || 'quantum-1.7b',
25
- server: process.env.SLYOS_SERVER || 'https://api.slyos.world',
26
- kbId: process.env.SLYOS_KB_ID || ''
27
- };
28
-
29
- // Initialize SlyOS SDK
30
- let sdk;
31
- let authToken = null; // Store auth token for direct RAG API calls
32
- try {
33
- sdk = new SlyOS({
34
- apiKey: config.apiKey,
35
- onProgress: (e) => console.log(`${colors.dim}[${e.progress}%] ${e.message}${colors.reset}`)
36
- });
37
- } catch (error) {
38
- console.error(`${colors.red}Error initializing SDK:${colors.reset}`, error.message);
39
- process.exit(1);
40
- }
41
-
42
- // Get auth token directly for RAG API calls
43
- async function getAuthToken() {
44
- if (authToken) return authToken;
45
- try {
46
- const res = await fetch(`${config.server}/api/auth/sdk`, {
47
- method: 'POST',
48
- headers: { 'Content-Type': 'application/json' },
49
- body: JSON.stringify({ apiKey: config.apiKey })
50
- });
51
- if (!res.ok) {
52
- console.log(`${colors.yellow}Auth failed: ${res.status} ${res.statusText}${colors.reset}`);
53
- return null;
54
- }
55
- const data = await res.json();
56
- authToken = data.token;
57
- return authToken;
58
- } catch (e) {
59
- console.log(`${colors.yellow}Auth error: ${e.message}${colors.reset}`);
60
- return null;
61
- }
62
- }
63
-
64
- // Create readline interface
65
- const rl = readline.createInterface({
66
- input: process.stdin,
67
- output: process.stdout,
68
- terminal: true
69
- });
70
-
71
- // Note: conversation history is not used for generation with small models
72
- // They work better with single prompts
73
-
74
- /**
75
- * Print welcome banner
76
- */
77
- function printWelcome() {
78
- console.clear();
79
- console.log(`${colors.bright}${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
80
- console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
81
- console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}Welcome to the Slyos Interactive Chatbot${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
82
- console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
83
- console.log(`${colors.bright}${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}\n`);
84
-
85
- console.log(`${colors.blue}Model:${colors.reset} ${colors.yellow}${config.model}${colors.reset}`);
86
- console.log(`${colors.blue}Server:${colors.reset} ${colors.yellow}${config.server}${colors.reset}`);
87
- if (config.kbId) {
88
- console.log(`${colors.blue}Knowledge Base:${colors.reset} ${colors.green}${config.kbId}${colors.reset} ${colors.green}(RAG enabled)${colors.reset}`);
89
- } else {
90
- console.log(`${colors.blue}Knowledge Base:${colors.reset} ${colors.dim}None (plain generation)${colors.reset}`);
91
- }
92
- if (config.apiKey === 'YOUR_API_KEY') {
93
- console.log(`${colors.red}⚠ Using placeholder API key - set SLYOS_API_KEY environment variable${colors.reset}`);
94
- }
95
- console.log(`\n${colors.bright}Commands:${colors.reset}`);
96
- console.log(` ${colors.green}Type your message and press Enter to chat${colors.reset}`);
97
- console.log(` ${colors.green}Type 'clear' to clear conversation history${colors.reset}`);
98
- console.log(` ${colors.green}Type 'exit' or 'quit' to end the session${colors.reset}`);
99
- console.log(`\n${colors.bright}${colors.cyan}─────────────────────────────────────────────────────────────${colors.reset}\n`);
100
- }
101
-
102
- /**
103
- * Clean up model output — stop hallucinated Q&A chains, strip artifacts
104
- */
105
- function cleanResponse(text) {
106
- return text
107
- // CRITICAL: Cut at first hallucinated Q&A follow-up
108
- .split(/\n\s*Q\s*:/)[0]
109
- // Cut at hallucinated role prefixes
110
- .split(/\n\s*(User|Human|System|Question|A:|Answer):/i)[0]
111
- // Strip repeated garbage chars
112
- .replace(/(.)\1{5,}/g, '')
113
- // Strip leading role prefixes
114
- .replace(/^(assistant|system|answer|response|AI)\s*[:]\s*/i, '')
115
- // Strip any remaining mid-response role prefix
116
- .replace(/^\s*(assistant|AI)\s*[:]\s*/im, '')
117
- .trim();
118
- }
119
-
120
- /**
121
- * Send message to AI and get response — with timing metrics and streaming
122
- */
123
- async function sendMessage(userMessage) {
124
- try {
125
- const totalStart = Date.now();
126
- let assistantMessage = '';
127
- let sourceInfo = '';
128
- let retrievalMs = 0;
129
- let firstTokenMs = 0;
130
- let generationMs = 0;
131
- let tokensGenerated = 0;
132
-
133
- if (config.kbId) {
134
- // ── RAG MODE ──
135
- console.log(`${colors.dim}Searching knowledge base...${colors.reset}`);
136
- const retrievalStart = Date.now();
137
-
138
- try {
139
- const token = await getAuthToken();
140
- if (!token) throw new Error('Could not authenticate');
141
-
142
- const modelCtx = sdk.getModelContextWindow?.() || 2048;
143
- const memoryMB = 8192; // default; could read from sdk.getDeviceProfile()
144
- const cpuCores = 8;
145
-
146
- // Dynamic config based on device + model
147
- const deviceTier = (memoryMB >= 8192 && cpuCores >= 8) ? 'high' : (memoryMB >= 4096) ? 'mid' : 'low';
148
- const topK = deviceTier === 'high' ? 3 : deviceTier === 'mid' ? 2 : 1;
149
- const maxContextChars = modelCtx <= 2048
150
- ? (deviceTier === 'high' ? 600 : deviceTier === 'mid' ? 400 : 300)
151
- : modelCtx <= 4096
152
- ? (deviceTier === 'high' ? 1500 : 1000)
153
- : 2000;
154
- const maxGenTokens = modelCtx <= 2048
155
- ? (deviceTier === 'high' ? 200 : 150)
156
- : Math.min(400, Math.floor(modelCtx / 4));
157
-
158
- const ragRes = await fetch(`${config.server}/api/rag/knowledge-bases/${config.kbId}/query`, {
159
- method: 'POST',
160
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
161
- body: JSON.stringify({ query: userMessage, top_k: topK, model_id: config.model })
162
- });
163
- if (!ragRes.ok) throw new Error(`RAG ${ragRes.status}`);
164
- const ragData = await ragRes.json();
165
- const chunks = (ragData.retrieved_chunks || []).filter(c => (c.similarity_score || 0) > 0.3);
166
- retrievalMs = Date.now() - retrievalStart;
167
-
168
- if (chunks.length > 0) {
169
- const bestChunk = chunks[0];
170
- let context = bestChunk.content
171
- .replace(/[^\x20-\x7E\n]/g, ' ').replace(/\s{2,}/g, ' ')
172
- .replace(/<[^>]+>/g, ' ').replace(/https?:\/\/\S+/g, '')
173
- .replace(/[{}()\[\]]/g, '').trim();
174
- if (context.length > maxContextChars) context = context.substring(0, maxContextChars);
175
-
176
- console.log(`${colors.dim}Context: ${context.length} chars from "${bestChunk.document_name}" [retrieval: ${retrievalMs}ms, tier: ${deviceTier}]${colors.reset}`);
177
-
178
- // Instruction prompt — avoids Q&A chain hallucination
179
- const prompt = `Based on this information:\n${context}\n\nAnswer briefly: ${userMessage}\n\n`;
180
-
181
- // Stream tokens
182
- const genStart = Date.now();
183
- let firstToken = false;
184
- process.stdout.write(`\n${colors.bright}${colors.magenta}AI:${colors.reset} `);
185
-
186
- if (sdk.generateStream) {
187
- const result = await sdk.generateStream(config.model, prompt, {
188
- temperature: 0.6,
189
- maxTokens: maxGenTokens,
190
- onToken: (token, partial) => {
191
- if (!firstToken) {
192
- firstTokenMs = Date.now() - genStart;
193
- firstToken = true;
194
- }
195
- process.stdout.write(token);
196
- }
197
- });
198
- assistantMessage = result.text || '';
199
- if (!firstToken) firstTokenMs = result.firstTokenMs || 0;
200
- tokensGenerated = result.tokensGenerated || assistantMessage.split(/\s+/).length;
201
- } else {
202
- // Fallback: no streaming
203
- const response = await sdk.generate(config.model, prompt, {
204
- temperature: 0.6,
205
- maxTokens: maxGenTokens
206
- });
207
- assistantMessage = (typeof response === 'string' ? response : response?.text || '') || '';
208
- firstTokenMs = Date.now() - genStart;
209
- tokensGenerated = assistantMessage.split(/\s+/).length;
210
- process.stdout.write(assistantMessage);
211
- }
212
- generationMs = Date.now() - genStart;
213
-
214
- // Source info
215
- const sources = [...new Set(chunks.map(c => c.document_name || c.source).filter(Boolean))];
216
- if (sources.length > 0) sourceInfo = `\n${colors.dim}[Sources: ${sources.join(', ')}]${colors.reset}`;
217
- } else {
218
- console.log(`${colors.dim}No relevant context found [retrieval: ${retrievalMs}ms]${colors.reset}`);
219
- const genStart = Date.now();
220
- process.stdout.write(`\n${colors.bright}${colors.magenta}AI:${colors.reset} `);
221
-
222
- if (sdk.generateStream) {
223
- const result = await sdk.generateStream(config.model, `Answer briefly: ${userMessage}\n\n`, {
224
- temperature: 0.7, maxTokens: 100,
225
- onToken: (token) => {
226
- if (!firstTokenMs) firstTokenMs = Date.now() - genStart;
227
- process.stdout.write(token);
228
- }
229
- });
230
- assistantMessage = result.text || '';
231
- tokensGenerated = result.tokensGenerated || assistantMessage.split(/\s+/).length;
232
- } else {
233
- const response = await sdk.generate(config.model, `Answer briefly: ${userMessage}\n\n`, {
234
- temperature: 0.7, maxTokens: 100
235
- });
236
- assistantMessage = (typeof response === 'string' ? response : response?.text || '') || '';
237
- firstTokenMs = Date.now() - genStart;
238
- tokensGenerated = assistantMessage.split(/\s+/).length;
239
- process.stdout.write(assistantMessage);
240
- }
241
- generationMs = Date.now() - genStart;
242
- }
243
- } catch (ragErr) {
244
- console.log(`${colors.yellow}RAG failed: ${ragErr.message}${colors.reset}`);
245
- const genStart = Date.now();
246
- const response = await sdk.generate(config.model, `Answer briefly: ${userMessage}\n\n`, {
247
- temperature: 0.7, maxTokens: 100
248
- });
249
- assistantMessage = (typeof response === 'string' ? response : response?.text || '') || '';
250
- firstTokenMs = Date.now() - genStart;
251
- generationMs = firstTokenMs;
252
- tokensGenerated = assistantMessage.split(/\s+/).length;
253
- process.stdout.write(`\n${colors.bright}${colors.magenta}AI:${colors.reset} ${assistantMessage}`);
254
- }
255
- } else {
256
- // ── PLAIN MODE ──
257
- const genStart = Date.now();
258
- process.stdout.write(`\n${colors.bright}${colors.magenta}AI:${colors.reset} `);
259
-
260
- if (sdk.generateStream) {
261
- const result = await sdk.generateStream(config.model, `Answer briefly: ${userMessage}\n\n`, {
262
- temperature: 0.7, maxTokens: 150,
263
- onToken: (token) => {
264
- if (!firstTokenMs) firstTokenMs = Date.now() - genStart;
265
- process.stdout.write(token);
266
- }
267
- });
268
- assistantMessage = result.text || '';
269
- tokensGenerated = result.tokensGenerated || assistantMessage.split(/\s+/).length;
270
- } else {
271
- const response = await sdk.generate(config.model, `Answer briefly: ${userMessage}\n\n`, {
272
- temperature: 0.7, maxTokens: 150
273
- });
274
- assistantMessage = (typeof response === 'string' ? response : response?.text || '') || '';
275
- firstTokenMs = Date.now() - genStart;
276
- tokensGenerated = assistantMessage.split(/\s+/).length;
277
- process.stdout.write(assistantMessage);
278
- }
279
- generationMs = Date.now() - genStart;
280
- }
281
-
282
- // Clean up hallucinated Q&A chains
283
- assistantMessage = cleanResponse(assistantMessage);
284
-
285
- const totalMs = Date.now() - totalStart;
286
- const tokPerSec = generationMs > 0 ? (tokensGenerated / (generationMs / 1000)).toFixed(1) : '0';
287
-
288
- // Print timing summary
289
- console.log(sourceInfo);
290
- console.log(`${colors.dim}⏱ retrieval: ${retrievalMs}ms | first token: ${firstTokenMs}ms | generation: ${generationMs}ms | total: ${totalMs}ms | ${tokensGenerated} tokens @ ${tokPerSec} tok/s${colors.reset}\n`);
291
-
292
- if (!assistantMessage || assistantMessage.length < 3) {
293
- console.log(`${colors.yellow}(No response generated — try rephrasing your question)${colors.reset}\n`);
294
- }
295
- } catch (error) {
296
- console.error(`\n${colors.red}Error:${colors.reset} ${error.message}\n`);
297
- }
298
- }
299
-
300
- /**
301
- * Prompt user for input
302
- */
303
- function promptUser() {
304
- rl.question(`${colors.bright}${colors.green}You:${colors.reset} `, async (input) => {
305
- const message = input.trim();
306
-
307
- if (!message) {
308
- promptUser();
309
- return;
310
- }
311
-
312
- // Handle commands
313
- if (message.toLowerCase() === 'exit' || message.toLowerCase() === 'quit') {
314
- console.log(`\n${colors.bright}${colors.cyan}Thank you for chatting! Goodbye.${colors.reset}\n`);
315
- rl.close();
316
- process.exit(0);
317
- }
318
-
319
- if (message.toLowerCase() === 'clear') {
320
- console.clear();
321
- printWelcome();
322
- console.log(`${colors.green}✓ Screen cleared${colors.reset}\n`);
323
- promptUser();
324
- return;
325
- }
326
-
327
- // Send message to AI
328
- await sendMessage(message);
329
- promptUser();
330
- });
331
- }
332
-
333
- /**
334
- * Main entry point
335
- */
336
- async function main() {
337
- printWelcome();
338
-
339
- try {
340
- console.log(`${colors.cyan}Initializing SlyOS...${colors.reset}`);
341
- await sdk.initialize();
342
-
343
- console.log(`${colors.cyan}Loading model: ${config.model}...${colors.reset}`);
344
- await sdk.loadModel(config.model);
345
-
346
- console.log(`${colors.green}Ready! Start chatting below.${colors.reset}\n`);
347
- console.log(`${colors.bright}${colors.cyan}─────────────────────────────────────────────────────────────${colors.reset}\n`);
348
- } catch (error) {
349
- console.error(`${colors.red}Failed to initialize: ${error.message}${colors.reset}`);
350
- console.error(`${colors.dim}Make sure your API key is correct and you have internet access.${colors.reset}`);
351
- process.exit(1);
352
- }
353
-
354
- promptUser();
355
- }
356
-
357
- // Handle process termination gracefully
358
- process.on('SIGINT', () => {
359
- console.log(`\n${colors.bright}${colors.cyan}Session ended. Goodbye!${colors.reset}\n`);
360
- rl.close();
361
- process.exit(0);
362
- });
363
-
364
- process.on('SIGTERM', () => {
365
- rl.close();
366
- process.exit(0);
367
- });
368
-
369
- // Start the chatbot
370
- main();
1
+ #!/bin/bash
2
+
3
+ #################################################################################
4
+ # Slyos Chatbot Setup Script
5
+ #
6
+ # This script creates a fully functional interactive chatbot using the Slyos SDK.
7
+ # Supports both Mac and Windows (via bash/powershell).
8
+ #
9
+ # Usage:
10
+ # ./create-chatbot.sh [--api-key YOUR_KEY] [--model MODEL_NAME]
11
+ #
12
+ # Examples:
13
+ # ./create-chatbot.sh
14
+ # ./create-chatbot.sh --api-key sk_123456789 --model quantum-1.7b
15
+ #################################################################################
16
+
17
+ set -e
18
+
19
+ # Color codes for terminal output
20
+ RED='\033[0;31m'
21
+ GREEN='\033[0;32m'
22
+ YELLOW='\033[1;33m'
23
+ BLUE='\033[0;34m'
24
+ CYAN='\033[0;36m'
25
+ NC='\033[0m' # No Color
26
+
27
+ # Default values
28
+ API_KEY=""
29
+ MODEL="quantum-1.7b"
30
+ KB_ID=""
31
+ SLYOS_SERVER="https://api.slyos.world"
32
+ PROJECT_NAME="slyos-chatbot"
33
+
34
+ #################################################################################
35
+ # Helper Functions
36
+ #################################################################################
37
+
38
+ print_header() {
39
+ echo -e "\n${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
40
+ echo -e "${BLUE}║${NC} ${CYAN}Slyos Interactive Chatbot Setup${NC} ${BLUE}║${NC}"
41
+ echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}\n"
42
+ }
43
+
44
+ print_step() {
45
+ echo -e "${CYAN}▶${NC} $1"
46
+ }
47
+
48
+ print_success() {
49
+ echo -e "${GREEN}✓${NC} $1"
50
+ }
51
+
52
+ print_error() {
53
+ echo -e "${RED}✗${NC} $1" >&2
54
+ }
55
+
56
+ print_info() {
57
+ echo -e "${YELLOW}ℹ${NC} $1"
58
+ }
59
+
60
+ #################################################################################
61
+ # Argument Parsing
62
+ #################################################################################
63
+
64
+ while [[ $# -gt 0 ]]; do
65
+ case $1 in
66
+ --api-key)
67
+ API_KEY="$2"
68
+ shift 2
69
+ ;;
70
+ --model)
71
+ MODEL="$2"
72
+ shift 2
73
+ ;;
74
+ --kb-id)
75
+ KB_ID="$2"
76
+ shift 2
77
+ ;;
78
+ -h|--help)
79
+ echo "Usage: $0 [OPTIONS]"
80
+ echo ""
81
+ echo "Options:"
82
+ echo " --api-key KEY Slyos API key (prompted if not provided)"
83
+ echo " --model MODEL AI model to use (default: quantum-1.7b)"
84
+ echo " --kb-id ID Knowledge base ID for RAG (optional)"
85
+ echo " -h, --help Show this help message"
86
+ exit 0
87
+ ;;
88
+ *)
89
+ print_error "Unknown option: $1"
90
+ exit 1
91
+ ;;
92
+ esac
93
+ done
94
+
95
+ #################################################################################
96
+ # Main Setup
97
+ #################################################################################
98
+
99
+ print_header
100
+
101
+ # Prompt for API key if not provided
102
+ if [ -z "$API_KEY" ]; then
103
+ if [ -t 0 ]; then
104
+ print_step "Enter your Slyos API key (or press Enter for placeholder)"
105
+ read -r -p " API Key: " API_KEY
106
+ fi
107
+
108
+ if [ -z "$API_KEY" ]; then
109
+ API_KEY="YOUR_API_KEY"
110
+ print_info "Using placeholder API key — set SLYOS_API_KEY in .env later"
111
+ else
112
+ print_success "API key configured"
113
+ fi
114
+ else
115
+ print_success "API key provided via arguments"
116
+ fi
117
+
118
+ # Confirm model selection
119
+ print_step "AI Model Configuration"
120
+ echo -e " Current model: ${YELLOW}${MODEL}${NC}"
121
+
122
+ # Only prompt interactively if stdin is a terminal (not piped)
123
+ if [ -t 0 ]; then
124
+ read -p " Use this model? (y/n, default: y): " -r -n 1
125
+ echo
126
+ if [[ ! $REPLY =~ ^[Yy]?$ ]]; then
127
+ read -p " Enter model name: " -r MODEL
128
+ fi
129
+ fi
130
+ print_success "Model configured: ${YELLOW}${MODEL}${NC}"
131
+
132
+ # Check if project already exists
133
+ if [ -d "$PROJECT_NAME" ]; then
134
+ if [ -t 0 ]; then
135
+ print_error "Project folder '$PROJECT_NAME' already exists!"
136
+ read -p " Remove existing folder and continue? (y/n): " -r -n 1
137
+ echo
138
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
139
+ rm -rf "$PROJECT_NAME"
140
+ print_success "Existing folder removed"
141
+ else
142
+ print_error "Setup cancelled"
143
+ exit 1
144
+ fi
145
+ else
146
+ # Non-interactive: auto-remove
147
+ rm -rf "$PROJECT_NAME"
148
+ print_success "Existing folder removed"
149
+ fi
150
+ fi
151
+
152
+ # Create project directory
153
+ print_step "Creating project directory: ${CYAN}$PROJECT_NAME${NC}"
154
+ mkdir -p "$PROJECT_NAME"
155
+ cd "$PROJECT_NAME"
156
+ print_success "Project directory created"
157
+
158
+ # Initialize npm
159
+ print_step "Initializing npm package"
160
+ npm init -y > /dev/null 2>&1
161
+ print_success "npm initialized"
162
+
163
+ # Update package.json to use ES modules
164
+ print_step "Configuring ES module support"
165
+ cat > package.json << 'EOF'
166
+ {
167
+ "name": "slyos-chatbot",
168
+ "version": "1.0.0",
169
+ "description": "Interactive chatbot powered by Slyos SDK",
170
+ "main": "app.mjs",
171
+ "type": "module",
172
+ "scripts": {
173
+ "start": "node app.mjs",
174
+ "chat": "node app.mjs"
175
+ },
176
+ "keywords": ["chatbot", "slyos", "ai"],
177
+ "author": "",
178
+ "license": "MIT"
179
+ }
180
+ EOF
181
+ print_success "Package configuration updated"
182
+
183
+ # Install Slyos SDK + dotenv
184
+ print_step "Installing dependencies"
185
+ print_info "This may take a moment..."
186
+ npm install @beltoinc/slyos-sdk dotenv node-fetch > /dev/null 2>&1
187
+ print_success "Dependencies installed"
188
+
189
+ # Create the chatbot application
190
+ print_step "Creating interactive chatbot application: ${CYAN}app.mjs${NC}"
191
+
192
+ cat > app.mjs << 'CHATBOT_EOF'
193
+ #!/usr/bin/env node
194
+
195
+ import 'dotenv/config';
196
+ import readline from 'readline';
197
+ import fetch from 'node-fetch';
198
+ import SlyOS from '@beltoinc/slyos-sdk';
199
+
200
+ // Color codes for terminal output
201
+ const colors = {
202
+ reset: '\x1b[0m',
203
+ bright: '\x1b[1m',
204
+ dim: '\x1b[2m',
205
+ cyan: '\x1b[36m',
206
+ green: '\x1b[32m',
207
+ yellow: '\x1b[33m',
208
+ blue: '\x1b[34m',
209
+ red: '\x1b[31m',
210
+ magenta: '\x1b[35m'
211
+ };
212
+
213
+ // Configuration
214
+ const config = {
215
+ apiKey: process.env.SLYOS_API_KEY || 'YOUR_API_KEY',
216
+ model: process.env.SLYOS_MODEL || 'quantum-1.7b',
217
+ server: process.env.SLYOS_SERVER || 'https://api.slyos.world',
218
+ kbId: process.env.SLYOS_KB_ID || ''
219
+ };
220
+
221
+ // Initialize SlyOS SDK
222
+ let sdk;
223
+ let authToken = null; // Store auth token for direct RAG API calls
224
+ try {
225
+ sdk = new SlyOS({
226
+ apiKey: config.apiKey,
227
+ onProgress: (e) => console.log(`${colors.dim}[${e.progress}%] ${e.message}${colors.reset}`)
228
+ });
229
+ } catch (error) {
230
+ console.error(`${colors.red}Error initializing SDK:${colors.reset}`, error.message);
231
+ process.exit(1);
232
+ }
233
+
234
+ // Get auth token directly for RAG API calls
235
+ async function getAuthToken() {
236
+ if (authToken) return authToken;
237
+ try {
238
+ const res = await fetch(`${config.server}/api/auth/sdk`, {
239
+ method: 'POST',
240
+ headers: { 'Content-Type': 'application/json' },
241
+ body: JSON.stringify({ apiKey: config.apiKey })
242
+ });
243
+ if (!res.ok) {
244
+ console.log(`${colors.yellow}Auth failed: ${res.status} ${res.statusText}${colors.reset}`);
245
+ return null;
246
+ }
247
+ const data = await res.json();
248
+ authToken = data.token;
249
+ return authToken;
250
+ } catch (e) {
251
+ console.log(`${colors.yellow}Auth error: ${e.message}${colors.reset}`);
252
+ return null;
253
+ }
254
+ }
255
+
256
+ // Create readline interface
257
+ const rl = readline.createInterface({
258
+ input: process.stdin,
259
+ output: process.stdout,
260
+ terminal: true
261
+ });
262
+
263
+ // Note: conversation history is not used for generation with small models
264
+ // They work better with single prompts
265
+
266
+ /**
267
+ * Print welcome banner
268
+ */
269
+ function printWelcome() {
270
+ console.clear();
271
+ console.log(`${colors.bright}${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
272
+ console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
273
+ console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}Welcome to the Slyos Interactive Chatbot${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
274
+ console.log(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.cyan}║${colors.reset}`);
275
+ console.log(`${colors.bright}${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}\n`);
276
+
277
+ console.log(`${colors.blue}Model:${colors.reset} ${colors.yellow}${config.model}${colors.reset}`);
278
+ console.log(`${colors.blue}Server:${colors.reset} ${colors.yellow}${config.server}${colors.reset}`);
279
+ if (config.kbId) {
280
+ console.log(`${colors.blue}Knowledge Base:${colors.reset} ${colors.green}${config.kbId}${colors.reset} ${colors.green}(RAG enabled)${colors.reset}`);
281
+ } else {
282
+ console.log(`${colors.blue}Knowledge Base:${colors.reset} ${colors.dim}None (plain generation)${colors.reset}`);
283
+ }
284
+ if (config.apiKey === 'YOUR_API_KEY') {
285
+ console.log(`${colors.red}⚠ Using placeholder API key - set SLYOS_API_KEY environment variable${colors.reset}`);
286
+ }
287
+ console.log(`\n${colors.bright}Commands:${colors.reset}`);
288
+ console.log(` ${colors.green}Type your message and press Enter to chat${colors.reset}`);
289
+ console.log(` ${colors.green}Type 'clear' to clear conversation history${colors.reset}`);
290
+ console.log(` ${colors.green}Type 'exit' or 'quit' to end the session${colors.reset}`);
291
+ console.log(`\n${colors.bright}${colors.cyan}─────────────────────────────────────────────────────────────${colors.reset}\n`);
292
+ }
293
+
294
+ /**
295
+ * Clean up model output — stop hallucinated Q&A chains, strip artifacts
296
+ */
297
+ function cleanResponse(text) {
298
+ return text
299
+ // CRITICAL: Cut at first hallucinated Q&A follow-up
300
+ .split(/\n\s*Q\s*:/)[0]
301
+ // Cut at hallucinated role prefixes
302
+ .split(/\n\s*(User|Human|System|Question|A:|Answer):/i)[0]
303
+ // Strip repeated garbage chars
304
+ .replace(/(.)\1{5,}/g, '')
305
+ // Strip leading role prefixes
306
+ .replace(/^(assistant|system|answer|response|AI)\s*[:]\s*/i, '')
307
+ // Strip any remaining mid-response role prefix
308
+ .replace(/^\s*(assistant|AI)\s*[:]\s*/im, '')
309
+ .trim();
310
+ }
311
+
312
+ /**
313
+ * Send message to AI and get response with timing metrics and streaming
314
+ */
315
+ async function sendMessage(userMessage) {
316
+ try {
317
+ const totalStart = Date.now();
318
+ let assistantMessage = '';
319
+ let sourceInfo = '';
320
+ let retrievalMs = 0;
321
+ let firstTokenMs = 0;
322
+ let generationMs = 0;
323
+ let tokensGenerated = 0;
324
+
325
+ if (config.kbId) {
326
+ // ── RAG MODE ──
327
+ console.log(`${colors.dim}Searching knowledge base...${colors.reset}`);
328
+ const retrievalStart = Date.now();
329
+
330
+ try {
331
+ const token = await getAuthToken();
332
+ if (!token) throw new Error('Could not authenticate');
333
+
334
+ const modelCtx = sdk.getModelContextWindow?.() || 2048;
335
+ const memoryMB = 8192; // default; could read from sdk.getDeviceProfile()
336
+ const cpuCores = 8;
337
+
338
+ // Dynamic config based on device + model
339
+ const deviceTier = (memoryMB >= 8192 && cpuCores >= 8) ? 'high' : (memoryMB >= 4096) ? 'mid' : 'low';
340
+ const topK = deviceTier === 'high' ? 3 : deviceTier === 'mid' ? 2 : 1;
341
+ const maxContextChars = modelCtx <= 2048
342
+ ? (deviceTier === 'high' ? 600 : deviceTier === 'mid' ? 400 : 300)
343
+ : modelCtx <= 4096
344
+ ? (deviceTier === 'high' ? 1500 : 1000)
345
+ : 2000;
346
+ const maxGenTokens = modelCtx <= 2048
347
+ ? (deviceTier === 'high' ? 200 : 150)
348
+ : Math.min(400, Math.floor(modelCtx / 4));
349
+
350
+ const ragRes = await fetch(`${config.server}/api/rag/knowledge-bases/${config.kbId}/query`, {
351
+ method: 'POST',
352
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
353
+ body: JSON.stringify({ query: userMessage, top_k: topK, model_id: config.model })
354
+ });
355
+ if (!ragRes.ok) throw new Error(`RAG ${ragRes.status}`);
356
+ const ragData = await ragRes.json();
357
+ const chunks = (ragData.retrieved_chunks || []).filter(c => (c.similarity_score || 0) > 0.3);
358
+ retrievalMs = Date.now() - retrievalStart;
359
+
360
+ if (chunks.length > 0) {
361
+ const bestChunk = chunks[0];
362
+ let context = bestChunk.content
363
+ .replace(/[^\x20-\x7E\n]/g, ' ').replace(/\s{2,}/g, ' ')
364
+ .replace(/<[^>]+>/g, ' ').replace(/https?:\/\/\S+/g, '')
365
+ .replace(/[{}()\[\]]/g, '').trim();
366
+ if (context.length > maxContextChars) context = context.substring(0, maxContextChars);
367
+
368
+ console.log(`${colors.dim}Context: ${context.length} chars from "${bestChunk.document_name}" [retrieval: ${retrievalMs}ms, tier: ${deviceTier}]${colors.reset}`);
369
+
370
+ // Use messages format — the model's chat template handles formatting
371
+ // This is critical: raw text prompts cause instruct models to generate only 1 token
372
+ const messages = [
373
+ { role: 'system', content: 'Answer questions using only the following context. Be concise.\n\n' + context },
374
+ { role: 'user', content: userMessage }
375
+ ];
376
+
377
+ // Stream tokens
378
+ const genStart = Date.now();
379
+ let firstToken = false;
380
+ process.stdout.write(`\n${colors.bright}${colors.magenta}AI:${colors.reset} `);
381
+
382
+ if (sdk.generateStream) {
383
+ let streamedAny = false;
384
+ const result = await sdk.generateStream(config.model, messages, {
385
+ temperature: 0.6,
386
+ maxTokens: maxGenTokens,
387
+ onToken: (token, partial) => {
388
+ if (!firstToken) {
389
+ firstTokenMs = Date.now() - genStart;
390
+ firstToken = true;
391
+ }
392
+ streamedAny = true;
393
+ process.stdout.write(token);
394
+ }
395
+ });
396
+ assistantMessage = result.text || '';
397
+ if (!firstToken) firstTokenMs = result.firstTokenMs || 0;
398
+ tokensGenerated = result.tokensGenerated || assistantMessage.split(/\s+/).length;
399
+ // If streaming callback didn't fire, print the full response
400
+ if (!streamedAny && assistantMessage) {
401
+ process.stdout.write(assistantMessage);
402
+ }
403
+ } else {
404
+ // Fallback: no streaming
405
+ const response = await sdk.generate(config.model, messages, {
406
+ temperature: 0.6,
407
+ maxTokens: maxGenTokens
408
+ });
409
+ assistantMessage = (typeof response === 'string' ? response : response?.text || '') || '';
410
+ firstTokenMs = Date.now() - genStart;
411
+ tokensGenerated = assistantMessage.split(/\s+/).length;
412
+ process.stdout.write(assistantMessage);
413
+ }
414
+ generationMs = Date.now() - genStart;
415
+
416
+ // Source info
417
+ const sources = [...new Set(chunks.map(c => c.document_name || c.source).filter(Boolean))];
418
+ if (sources.length > 0) sourceInfo = `\n${colors.dim}[Sources: ${sources.join(', ')}]${colors.reset}`;
419
+ } else {
420
+ console.log(`${colors.dim}No relevant context found [retrieval: ${retrievalMs}ms]${colors.reset}`);
421
+ const genStart = Date.now();
422
+ process.stdout.write(`\n${colors.bright}${colors.magenta}AI:${colors.reset} `);
423
+
424
+ const noCtxMessages = [{ role: 'user', content: userMessage }];
425
+ if (sdk.generateStream) {
426
+ let streamedAny = false;
427
+ const result = await sdk.generateStream(config.model, noCtxMessages, {
428
+ temperature: 0.7, maxTokens: 100,
429
+ onToken: (token) => {
430
+ if (!firstTokenMs) firstTokenMs = Date.now() - genStart;
431
+ streamedAny = true;
432
+ process.stdout.write(token);
433
+ }
434
+ });
435
+ assistantMessage = result.text || '';
436
+ tokensGenerated = result.tokensGenerated || assistantMessage.split(/\s+/).length;
437
+ if (!streamedAny && assistantMessage) process.stdout.write(assistantMessage);
438
+ } else {
439
+ const response = await sdk.generate(config.model, noCtxMessages, {
440
+ temperature: 0.7, maxTokens: 100
441
+ });
442
+ assistantMessage = (typeof response === 'string' ? response : response?.text || '') || '';
443
+ firstTokenMs = Date.now() - genStart;
444
+ tokensGenerated = assistantMessage.split(/\s+/).length;
445
+ process.stdout.write(assistantMessage);
446
+ }
447
+ generationMs = Date.now() - genStart;
448
+ }
449
+ } catch (ragErr) {
450
+ console.log(`${colors.yellow}RAG failed: ${ragErr.message}${colors.reset}`);
451
+ const genStart = Date.now();
452
+ const response = await sdk.generate(config.model, [{ role: 'user', content: userMessage }], {
453
+ temperature: 0.7, maxTokens: 100
454
+ });
455
+ assistantMessage = (typeof response === 'string' ? response : response?.text || '') || '';
456
+ firstTokenMs = Date.now() - genStart;
457
+ generationMs = firstTokenMs;
458
+ tokensGenerated = assistantMessage.split(/\s+/).length;
459
+ process.stdout.write(`\n${colors.bright}${colors.magenta}AI:${colors.reset} ${assistantMessage}`);
460
+ }
461
+ } else {
462
+ // ── PLAIN MODE ──
463
+ const genStart = Date.now();
464
+ process.stdout.write(`\n${colors.bright}${colors.magenta}AI:${colors.reset} `);
465
+
466
+ const plainMessages = [{ role: 'user', content: userMessage }];
467
+ if (sdk.generateStream) {
468
+ let streamedAny = false;
469
+ const result = await sdk.generateStream(config.model, plainMessages, {
470
+ temperature: 0.7, maxTokens: 150,
471
+ onToken: (token) => {
472
+ if (!firstTokenMs) firstTokenMs = Date.now() - genStart;
473
+ streamedAny = true;
474
+ process.stdout.write(token);
475
+ }
476
+ });
477
+ assistantMessage = result.text || '';
478
+ tokensGenerated = result.tokensGenerated || assistantMessage.split(/\s+/).length;
479
+ if (!streamedAny && assistantMessage) process.stdout.write(assistantMessage);
480
+ } else {
481
+ const response = await sdk.generate(config.model, plainMessages, {
482
+ temperature: 0.7, maxTokens: 150
483
+ });
484
+ assistantMessage = (typeof response === 'string' ? response : response?.text || '') || '';
485
+ firstTokenMs = Date.now() - genStart;
486
+ tokensGenerated = assistantMessage.split(/\s+/).length;
487
+ process.stdout.write(assistantMessage);
488
+ }
489
+ generationMs = Date.now() - genStart;
490
+ }
491
+
492
+ // Clean up hallucinated Q&A chains
493
+ assistantMessage = cleanResponse(assistantMessage);
494
+
495
+ const totalMs = Date.now() - totalStart;
496
+ const tokPerSec = generationMs > 0 ? (tokensGenerated / (generationMs / 1000)).toFixed(1) : '0';
497
+
498
+ // Print timing summary
499
+ console.log(sourceInfo);
500
+ console.log(`${colors.dim}⏱ retrieval: ${retrievalMs}ms | first token: ${firstTokenMs}ms | generation: ${generationMs}ms | total: ${totalMs}ms | ${tokensGenerated} tokens @ ${tokPerSec} tok/s${colors.reset}\n`);
501
+
502
+ if (!assistantMessage || assistantMessage.length < 3) {
503
+ console.log(`${colors.yellow}(No response generated — try rephrasing your question)${colors.reset}\n`);
504
+ }
505
+ } catch (error) {
506
+ console.error(`\n${colors.red}Error:${colors.reset} ${error.message}\n`);
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Prompt user for input
512
+ */
513
+ function promptUser() {
514
+ rl.question(`${colors.bright}${colors.green}You:${colors.reset} `, async (input) => {
515
+ const message = input.trim();
516
+
517
+ if (!message) {
518
+ promptUser();
519
+ return;
520
+ }
521
+
522
+ // Handle commands
523
+ if (message.toLowerCase() === 'exit' || message.toLowerCase() === 'quit') {
524
+ console.log(`\n${colors.bright}${colors.cyan}Thank you for chatting! Goodbye.${colors.reset}\n`);
525
+ rl.close();
526
+ process.exit(0);
527
+ }
528
+
529
+ if (message.toLowerCase() === 'clear') {
530
+ console.clear();
531
+ printWelcome();
532
+ console.log(`${colors.green}✓ Screen cleared${colors.reset}\n`);
533
+ promptUser();
534
+ return;
535
+ }
536
+
537
+ // Send message to AI
538
+ await sendMessage(message);
539
+ promptUser();
540
+ });
541
+ }
542
+
543
+ /**
544
+ * Main entry point
545
+ */
546
+ async function main() {
547
+ printWelcome();
548
+
549
+ try {
550
+ console.log(`${colors.cyan}Initializing SlyOS...${colors.reset}`);
551
+ await sdk.initialize();
552
+
553
+ console.log(`${colors.cyan}Loading model: ${config.model}...${colors.reset}`);
554
+ await sdk.loadModel(config.model);
555
+
556
+ console.log(`${colors.green}Ready! Start chatting below.${colors.reset}\n`);
557
+ console.log(`${colors.bright}${colors.cyan}─────────────────────────────────────────────────────────────${colors.reset}\n`);
558
+ } catch (error) {
559
+ console.error(`${colors.red}Failed to initialize: ${error.message}${colors.reset}`);
560
+ console.error(`${colors.dim}Make sure your API key is correct and you have internet access.${colors.reset}`);
561
+ process.exit(1);
562
+ }
563
+
564
+ promptUser();
565
+ }
566
+
567
+ // Handle process termination gracefully
568
+ process.on('SIGINT', () => {
569
+ console.log(`\n${colors.bright}${colors.cyan}Session ended. Goodbye!${colors.reset}\n`);
570
+ rl.close();
571
+ process.exit(0);
572
+ });
573
+
574
+ process.on('SIGTERM', () => {
575
+ rl.close();
576
+ process.exit(0);
577
+ });
578
+
579
+ // Start the chatbot
580
+ main();
581
+ CHATBOT_EOF
582
+
583
+ chmod +x app.mjs
584
+ print_success "Chatbot application created"
585
+
586
+ # Create .env.example file
587
+ print_step "Creating environment configuration example"
588
+ cat > .env.example << 'ENV_EOF'
589
+ # Slyos SDK Configuration
590
+ SLYOS_API_KEY=your_api_key_here
591
+ SLYOS_MODEL=quantum-1.7b
592
+ SLYOS_SERVER=https://api.slyos.world
593
+ ENV_EOF
594
+ print_success "Environment configuration template created"
595
+
596
+ # Create README
597
+ print_step "Creating README documentation"
598
+ cat > README.md << 'README_EOF'
599
+ # Slyos Interactive Chatbot
600
+
601
+ A simple yet powerful interactive chatbot powered by the Slyos SDK.
602
+
603
+ ## Features
604
+
605
+ - Interactive command-line interface with colored output
606
+ - Conversation history management
607
+ - Easy API configuration
608
+ - Cross-platform support (Mac, Windows, Linux)
609
+
610
+ ## Installation
611
+
612
+ 1. Clone or download this project
613
+ 2. Install dependencies: `npm install`
614
+ 3. Configure your API key (see Configuration)
615
+
616
+ ## Configuration
617
+
618
+ ### Environment Variables
619
+
620
+ Set these environment variables before running:
621
+
622
+ ```bash
623
+ export SLYOS_API_KEY=your_api_key_here
624
+ export SLYOS_MODEL=quantum-1.7b
625
+ export SLYOS_SERVER=https://api.slyos.world
626
+ ```
627
+
628
+ Or create a `.env` file based on `.env.example`.
629
+
630
+ ## Running the Chatbot
631
+
632
+ ### Direct Method
633
+ ```bash
634
+ npm start
635
+ ```
636
+
637
+ ### With Environment Variables
638
+ ```bash
639
+ SLYOS_API_KEY=your_key npm start
640
+ ```
641
+
642
+ ### Manual
643
+ ```bash
644
+ node app.mjs
645
+ ```
646
+
647
+ ## Usage
648
+
649
+ Once the chatbot starts:
650
+
651
+ - **Chat**: Type your message and press Enter
652
+ - **Clear History**: Type `clear` to reset conversation
653
+ - **Exit**: Type `exit` or `quit` to end session
654
+ - **Interrupt**: Press Ctrl+C to exit anytime
655
+
656
+ ## API Response Format
657
+
658
+ The chatbot supports multiple response formats from the SDK:
659
+
660
+ - `response.content` - Primary response text
661
+ - `response.text` - Alternative response field
662
+ - Direct string response - Fallback format
663
+
664
+ ## Troubleshooting
665
+
666
+ ### "Error initializing SDK"
667
+ - Check that your API key is valid
668
+ - Verify the Slyos server is accessible
669
+ - Ensure internet connection is active
670
+
671
+ ### "Cannot find module '@beltoinc/slyos-sdk'"
672
+ - Run `npm install` to install dependencies
673
+ - Check npm log: `npm list`
674
+
675
+ ### Placeholder API Key Warning
676
+ - Set the `SLYOS_API_KEY` environment variable with your actual key
677
+ - Or update `config.apiKey` in `app.mjs`
678
+
679
+ ## System Requirements
680
+
681
+ - Node.js 14+ (14.17.0 or higher recommended)
682
+ - npm 6+
683
+ - Internet connection for API access
684
+
685
+ ## License
686
+
687
+ MIT
688
+ README_EOF
689
+ print_success "README created"
690
+
691
+ # Set up environment with provided values
692
+ print_step "Configuring environment variables"
693
+ cat > .env << ENV_SETUP_EOF
694
+ SLYOS_API_KEY=${API_KEY}
695
+ SLYOS_MODEL=${MODEL}
696
+ SLYOS_SERVER=${SLYOS_SERVER}
697
+ SLYOS_KB_ID=${KB_ID}
698
+ ENV_SETUP_EOF
699
+ print_success "Environment configured"
700
+
701
+ # Final summary
702
+ echo ""
703
+ echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
704
+ echo -e "${BLUE}║${NC} ${GREEN}✓ Setup Complete!${NC} ${BLUE}║${NC}"
705
+ echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
706
+ echo ""
707
+ echo -e "${CYAN}Project Details:${NC}"
708
+ echo " Location: ${YELLOW}$(pwd)${NC}"
709
+ echo " API Key: ${YELLOW}${API_KEY}${NC}"
710
+ echo " Model: ${YELLOW}${MODEL}${NC}"
711
+ if [ -n "$KB_ID" ]; then
712
+ echo " Knowledge Base: ${GREEN}${KB_ID} (RAG enabled)${NC}"
713
+ fi
714
+ echo ""
715
+ echo -e "${CYAN}Next Steps:${NC}"
716
+ echo " 1. Review the .env file and update your API key if needed"
717
+ echo " 2. Run the chatbot: ${YELLOW}npm start${NC}"
718
+ echo " 3. Type messages to chat with the AI"
719
+ echo " 4. Type 'exit' to quit"
720
+ echo ""
721
+ echo -e "${GREEN}Ready to chat! 🚀${NC}"
722
+ echo ""
723
+
724
+ # Tell user how to start (can't auto-run when piped because stdin is closed)
725
+ echo -e "${CYAN}To start chatting, run:${NC}"
726
+ echo ""
727
+ echo -e " ${YELLOW}cd ${PROJECT_NAME} && npm start${NC}"
728
+ echo ""