vigthoria-cli 1.0.0
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.
- package/README.md +413 -0
- package/dist/commands/auth.d.ts +24 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +194 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/chat.d.ts +64 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +596 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/config.d.ts +25 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +291 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/edit.d.ts +28 -0
- package/dist/commands/edit.d.ts.map +1 -0
- package/dist/commands/edit.js +257 -0
- package/dist/commands/edit.js.map +1 -0
- package/dist/commands/explain.d.ts +21 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +98 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/generate.d.ts +25 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +155 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/review.d.ts +24 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +153 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +205 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/api.d.ts +88 -0
- package/dist/utils/api.d.ts.map +1 -0
- package/dist/utils/api.js +431 -0
- package/dist/utils/api.js.map +1 -0
- package/dist/utils/config.d.ts +57 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +167 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/files.d.ts +31 -0
- package/dist/utils/files.d.ts.map +1 -0
- package/dist/utils/files.js +217 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/logger.d.ts +23 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +104 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/session.d.ts +61 -0
- package/dist/utils/session.d.ts.map +1 -0
- package/dist/utils/session.js +172 -0
- package/dist/utils/session.js.map +1 -0
- package/dist/utils/tools.d.ts +145 -0
- package/dist/utils/tools.d.ts.map +1 -0
- package/dist/utils/tools.js +781 -0
- package/dist/utils/tools.js.map +1 -0
- package/install.sh +248 -0
- package/package.json +52 -0
- package/src/commands/auth.ts +225 -0
- package/src/commands/chat.ts +690 -0
- package/src/commands/config.ts +297 -0
- package/src/commands/edit.ts +310 -0
- package/src/commands/explain.ts +115 -0
- package/src/commands/generate.ts +177 -0
- package/src/commands/review.ts +186 -0
- package/src/index.ts +221 -0
- package/src/types/marked-terminal.d.ts +31 -0
- package/src/utils/api.ts +531 -0
- package/src/utils/config.ts +224 -0
- package/src/utils/files.ts +212 -0
- package/src/utils/logger.ts +125 -0
- package/src/utils/session.ts +167 -0
- package/src/utils/tools.ts +933 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare module 'marked-terminal' {
|
|
2
|
+
import { MarkedExtension } from 'marked';
|
|
3
|
+
|
|
4
|
+
interface MarkedTerminalOptions {
|
|
5
|
+
code?: (code: string) => string;
|
|
6
|
+
blockquote?: (text: string) => string;
|
|
7
|
+
html?: (html: string) => string;
|
|
8
|
+
heading?: (text: string, level: number) => string;
|
|
9
|
+
firstHeading?: (text: string) => string;
|
|
10
|
+
hr?: () => string;
|
|
11
|
+
listitem?: (text: string) => string;
|
|
12
|
+
list?: (body: string, ordered: boolean) => string;
|
|
13
|
+
paragraph?: (text: string) => string;
|
|
14
|
+
table?: (header: string, body: string) => string;
|
|
15
|
+
tablerow?: (content: string) => string;
|
|
16
|
+
tablecell?: (content: string, flags: { header: boolean; align: string }) => string;
|
|
17
|
+
strong?: (text: string) => string;
|
|
18
|
+
em?: (text: string) => string;
|
|
19
|
+
codespan?: (text: string) => string;
|
|
20
|
+
br?: () => string;
|
|
21
|
+
del?: (text: string) => string;
|
|
22
|
+
link?: (href: string, title: string, text: string) => string;
|
|
23
|
+
image?: (href: string, title: string, text: string) => string;
|
|
24
|
+
reflowText?: boolean;
|
|
25
|
+
width?: number;
|
|
26
|
+
showSectionPrefix?: boolean;
|
|
27
|
+
tab?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function markedTerminal(options?: MarkedTerminalOptions): MarkedExtension;
|
|
31
|
+
}
|
package/src/utils/api.ts
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client for Vigthoria Backend
|
|
3
|
+
* Connects to coder.vigthoria.io API endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
7
|
+
import WebSocket from 'ws';
|
|
8
|
+
import { Config } from './config.js';
|
|
9
|
+
import { Logger } from './logger.js';
|
|
10
|
+
|
|
11
|
+
export interface ChatMessage {
|
|
12
|
+
role: 'user' | 'assistant' | 'system';
|
|
13
|
+
content: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ChatResponse {
|
|
17
|
+
id: string;
|
|
18
|
+
message: string;
|
|
19
|
+
model: string;
|
|
20
|
+
usage?: {
|
|
21
|
+
prompt_tokens: number;
|
|
22
|
+
completion_tokens: number;
|
|
23
|
+
total_tokens: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface StreamChunk {
|
|
28
|
+
type: 'content' | 'done' | 'error';
|
|
29
|
+
content?: string;
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface VigthoriUser {
|
|
34
|
+
id: string;
|
|
35
|
+
username: string;
|
|
36
|
+
email: string;
|
|
37
|
+
isAdmin: boolean;
|
|
38
|
+
subscription: {
|
|
39
|
+
plan: string;
|
|
40
|
+
projectLimit: number;
|
|
41
|
+
storageLimit: number;
|
|
42
|
+
viagen6Access: boolean;
|
|
43
|
+
aiModelsLimit: number;
|
|
44
|
+
prioritySupport: boolean;
|
|
45
|
+
teamCollaboration: boolean;
|
|
46
|
+
adminAccess: boolean;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class APIClient {
|
|
51
|
+
private client: AxiosInstance;
|
|
52
|
+
private modelRouterClient: AxiosInstance;
|
|
53
|
+
private config: Config;
|
|
54
|
+
private logger: Logger;
|
|
55
|
+
private ws: WebSocket | null = null;
|
|
56
|
+
|
|
57
|
+
constructor(config: Config, logger: Logger) {
|
|
58
|
+
this.config = config;
|
|
59
|
+
this.logger = logger;
|
|
60
|
+
|
|
61
|
+
// Main Vigthoria Coder API (coder.vigthoria.io)
|
|
62
|
+
this.client = axios.create({
|
|
63
|
+
baseURL: config.get('apiUrl'),
|
|
64
|
+
timeout: 120000,
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
'User-Agent': 'Vigthoria-CLI/1.0.0',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Model Router API for AI operations
|
|
72
|
+
this.modelRouterClient = axios.create({
|
|
73
|
+
baseURL: 'https://coder.vigthoria.io',
|
|
74
|
+
timeout: 120000,
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
'User-Agent': 'Vigthoria-CLI/1.0.0',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Add auth interceptor
|
|
82
|
+
this.client.interceptors.request.use((req) => {
|
|
83
|
+
const token = this.config.get('authToken');
|
|
84
|
+
if (token) {
|
|
85
|
+
req.headers.Authorization = `Bearer ${token}`;
|
|
86
|
+
req.headers.Cookie = `vigthoria-auth-token=${token}`;
|
|
87
|
+
}
|
|
88
|
+
return req;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this.modelRouterClient.interceptors.request.use((req) => {
|
|
92
|
+
const token = this.config.get('authToken');
|
|
93
|
+
if (token) {
|
|
94
|
+
req.headers.Authorization = `Bearer ${token}`;
|
|
95
|
+
req.headers.Cookie = `vigthoria-auth-token=${token}`;
|
|
96
|
+
}
|
|
97
|
+
return req;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Add response interceptor for token refresh
|
|
101
|
+
this.client.interceptors.response.use(
|
|
102
|
+
(res) => res,
|
|
103
|
+
async (error: AxiosError) => {
|
|
104
|
+
if (error.response?.status === 401) {
|
|
105
|
+
const refreshed = await this.refreshToken();
|
|
106
|
+
if (refreshed && error.config) {
|
|
107
|
+
return this.client.request(error.config);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Authentication - Uses Vigthoria Coder /api/login endpoint
|
|
116
|
+
async login(email: string, password: string): Promise<boolean> {
|
|
117
|
+
try {
|
|
118
|
+
const response = await this.client.post('/api/login', { email, password });
|
|
119
|
+
|
|
120
|
+
if (!response.data.success) {
|
|
121
|
+
throw new Error(response.data.error || 'Login failed');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { token, user } = response.data as { token: string; user: VigthoriUser };
|
|
125
|
+
|
|
126
|
+
this.config.setAuth({
|
|
127
|
+
token,
|
|
128
|
+
userId: user.id,
|
|
129
|
+
email: user.email,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Set subscription from user data
|
|
133
|
+
this.config.setSubscription({
|
|
134
|
+
plan: user.subscription?.plan || 'developer',
|
|
135
|
+
status: 'active',
|
|
136
|
+
expiresAt: undefined,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return true;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const axiosError = error as AxiosError;
|
|
142
|
+
const message = (axiosError.response?.data as { error?: string })?.error || (error as Error).message;
|
|
143
|
+
this.logger.error('Login failed:', message);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async loginWithToken(token: string): Promise<boolean> {
|
|
149
|
+
try {
|
|
150
|
+
// Validate token by making a request to user info endpoint
|
|
151
|
+
this.config.set('authToken', token);
|
|
152
|
+
|
|
153
|
+
const response = await this.client.get('/api/user/profile', {
|
|
154
|
+
headers: {
|
|
155
|
+
Authorization: `Bearer ${token}`,
|
|
156
|
+
Cookie: `vigthoria-auth-token=${token}`,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (response.data && response.data.user) {
|
|
161
|
+
const user = response.data.user;
|
|
162
|
+
|
|
163
|
+
this.config.setAuth({
|
|
164
|
+
token,
|
|
165
|
+
userId: user.id,
|
|
166
|
+
email: user.email,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
this.config.setSubscription({
|
|
170
|
+
plan: user.subscription?.plan || user.subscription_plan || 'developer',
|
|
171
|
+
status: 'active',
|
|
172
|
+
expiresAt: undefined,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return false;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
this.config.clearAuth();
|
|
181
|
+
this.logger.error('Token validation failed:', (error as Error).message);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async refreshToken(): Promise<boolean> {
|
|
187
|
+
const refreshToken = this.config.get('refreshToken');
|
|
188
|
+
if (!refreshToken) return false;
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const response = await this.client.post('/api/token/refresh', {
|
|
192
|
+
refresh_token: refreshToken
|
|
193
|
+
});
|
|
194
|
+
const { token, access_token, refresh_token } = response.data;
|
|
195
|
+
|
|
196
|
+
this.config.set('authToken', token || access_token);
|
|
197
|
+
if (refresh_token) {
|
|
198
|
+
this.config.set('refreshToken', refresh_token);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return true;
|
|
202
|
+
} catch {
|
|
203
|
+
this.config.clearAuth();
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async getSubscriptionStatus(): Promise<void> {
|
|
209
|
+
try {
|
|
210
|
+
const response = await this.client.get('/api/user/subscription');
|
|
211
|
+
const data = response.data;
|
|
212
|
+
|
|
213
|
+
this.config.setSubscription({
|
|
214
|
+
plan: data.plan || data.subscription_plan || 'developer',
|
|
215
|
+
status: data.status || 'active',
|
|
216
|
+
expiresAt: data.expiresAt || data.expires_at
|
|
217
|
+
});
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.logger.debug('Failed to get subscription status:', (error as Error).message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Chat API - Supports multiple inference strategies
|
|
225
|
+
*
|
|
226
|
+
* For authenticated users: Uses Vigthoria Cloud API
|
|
227
|
+
* For local users: Uses local Ollama or Model Router
|
|
228
|
+
*/
|
|
229
|
+
async chat(messages: ChatMessage[], model: string, useLocal: boolean = false): Promise<ChatResponse> {
|
|
230
|
+
const resolvedModel = this.resolveModelId(model);
|
|
231
|
+
|
|
232
|
+
// If authenticated and not forcing local, use Vigthoria Cloud API
|
|
233
|
+
if (!useLocal && this.config.isAuthenticated()) {
|
|
234
|
+
try {
|
|
235
|
+
const response = await this.client.post('/api/ai/chat', {
|
|
236
|
+
messages,
|
|
237
|
+
model: resolvedModel,
|
|
238
|
+
maxTokens: this.config.get('preferences').maxTokens,
|
|
239
|
+
temperature: 0.7,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (response.data.success !== false) {
|
|
243
|
+
return {
|
|
244
|
+
id: response.data.id || `vigthoria-${Date.now()}`,
|
|
245
|
+
message: response.data.response || response.data.message || response.data.content,
|
|
246
|
+
model: response.data.model || model,
|
|
247
|
+
usage: response.data.usage,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
this.logger.debug('Vigthoria Cloud API failed, trying fallbacks...');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Strategy 1: Try local Model Router's Vigthoria chat endpoint
|
|
256
|
+
try {
|
|
257
|
+
const response = await axios.post('http://localhost:4009/api/vigthoria/chat', {
|
|
258
|
+
messages,
|
|
259
|
+
model: resolvedModel,
|
|
260
|
+
maxTokens: this.config.get('preferences').maxTokens,
|
|
261
|
+
temperature: 0.7,
|
|
262
|
+
stream: false,
|
|
263
|
+
}, { timeout: 120000 });
|
|
264
|
+
|
|
265
|
+
if (response.data.success !== false) {
|
|
266
|
+
return {
|
|
267
|
+
id: response.data.id || `router-${Date.now()}`,
|
|
268
|
+
message: response.data.response || response.data.message || response.data.content,
|
|
269
|
+
model: response.data.model || model,
|
|
270
|
+
usage: response.data.usage,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
this.logger.debug('Model router failed, trying Ollama directly...');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Strategy 2: Try Ollama directly (for local development/testing)
|
|
278
|
+
try {
|
|
279
|
+
const ollamaModel = this.resolveToOllamaModel(model);
|
|
280
|
+
const prompt = this.formatMessagesForOllama(messages);
|
|
281
|
+
|
|
282
|
+
const response = await axios.post('http://localhost:11434/api/generate', {
|
|
283
|
+
model: ollamaModel,
|
|
284
|
+
prompt,
|
|
285
|
+
stream: false,
|
|
286
|
+
}, { timeout: 120000 });
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
id: `ollama-${Date.now()}`,
|
|
290
|
+
message: response.data.response,
|
|
291
|
+
model: ollamaModel,
|
|
292
|
+
usage: {
|
|
293
|
+
prompt_tokens: response.data.prompt_eval_count || 0,
|
|
294
|
+
completion_tokens: response.data.eval_count || 0,
|
|
295
|
+
total_tokens: (response.data.prompt_eval_count || 0) + (response.data.eval_count || 0),
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
} catch (error) {
|
|
299
|
+
this.logger.debug('Ollama failed...');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Strategy 3: Try ViAgen6 AI endpoint (authenticated fallback)
|
|
303
|
+
if (this.config.isAuthenticated()) {
|
|
304
|
+
try {
|
|
305
|
+
const response = await this.client.post('/viagen6/api/ai/generate', {
|
|
306
|
+
prompt: messages.map(m => `${m.role}: ${m.content}`).join('\n'),
|
|
307
|
+
model: resolvedModel,
|
|
308
|
+
max_tokens: this.config.get('preferences').maxTokens,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
id: `viagen6-${Date.now()}`,
|
|
313
|
+
message: response.data.code || response.data.response || response.data.content,
|
|
314
|
+
model: model,
|
|
315
|
+
};
|
|
316
|
+
} catch (fallbackError) {
|
|
317
|
+
// Continue to error
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
throw new Error('All AI backends unavailable. Please check your connection or authentication.');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Map CLI model names to Ollama model names (for local fallback)
|
|
325
|
+
private resolveToOllamaModel(model: string): string {
|
|
326
|
+
const ollamaMap: Record<string, string> = {
|
|
327
|
+
'fast': 'qwen3:0.6b',
|
|
328
|
+
'mini': 'smollm2:135m',
|
|
329
|
+
'code': 'qwen2.5-coder:7b',
|
|
330
|
+
'balanced': 'phi3:mini',
|
|
331
|
+
'creative': 'gemma3:latest',
|
|
332
|
+
'vigthoria-fast-1.7b': 'qwen3:0.6b',
|
|
333
|
+
'vigthoria-mini-0.6b': 'smollm2:135m',
|
|
334
|
+
'vigthoria-v2-code-8b': 'qwen2.5-coder:7b',
|
|
335
|
+
'vigthoria-balanced-4b': 'phi3:mini',
|
|
336
|
+
'vigthoria-creative-9b-v4': 'gemma3:latest',
|
|
337
|
+
};
|
|
338
|
+
return ollamaMap[model] || model;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Format messages for Ollama's generate API
|
|
342
|
+
private formatMessagesForOllama(messages: ChatMessage[]): string {
|
|
343
|
+
return messages
|
|
344
|
+
.filter(m => m.role !== 'system' || messages.indexOf(m) === 0)
|
|
345
|
+
.map(m => {
|
|
346
|
+
if (m.role === 'system') return `System: ${m.content}\n\n`;
|
|
347
|
+
if (m.role === 'user') return `User: ${m.content}\n`;
|
|
348
|
+
return `Assistant: ${m.content}\n`;
|
|
349
|
+
})
|
|
350
|
+
.join('') + 'Assistant:';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Streaming chat
|
|
354
|
+
async *chatStream(
|
|
355
|
+
messages: ChatMessage[],
|
|
356
|
+
model: string
|
|
357
|
+
): AsyncGenerator<StreamChunk> {
|
|
358
|
+
const wsUrl = this.config.get('wsUrl');
|
|
359
|
+
const token = this.config.get('authToken');
|
|
360
|
+
|
|
361
|
+
return new Promise((resolve, reject) => {
|
|
362
|
+
const ws = new WebSocket(`${wsUrl}/chat`, {
|
|
363
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
ws.on('open', () => {
|
|
367
|
+
ws.send(JSON.stringify({
|
|
368
|
+
type: 'chat',
|
|
369
|
+
messages,
|
|
370
|
+
model: this.resolveModelId(model),
|
|
371
|
+
stream: true,
|
|
372
|
+
}));
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
ws.on('message', (data) => {
|
|
376
|
+
const chunk = JSON.parse(data.toString());
|
|
377
|
+
// Yield chunk - but this pattern won't work directly
|
|
378
|
+
// Need to use event emitter pattern instead
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
ws.on('error', (error) => {
|
|
382
|
+
reject(error);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
ws.on('close', () => {
|
|
386
|
+
resolve(undefined);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Non-streaming alternative with callback
|
|
392
|
+
async chatWithCallback(
|
|
393
|
+
messages: ChatMessage[],
|
|
394
|
+
model: string,
|
|
395
|
+
onChunk: (chunk: string) => void,
|
|
396
|
+
onDone: () => void,
|
|
397
|
+
onError: (error: Error) => void
|
|
398
|
+
): Promise<void> {
|
|
399
|
+
const wsUrl = this.config.get('wsUrl');
|
|
400
|
+
const token = this.config.get('authToken');
|
|
401
|
+
|
|
402
|
+
return new Promise((resolve, reject) => {
|
|
403
|
+
const ws = new WebSocket(`${wsUrl}/chat`, {
|
|
404
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
ws.on('open', () => {
|
|
408
|
+
ws.send(JSON.stringify({
|
|
409
|
+
type: 'chat',
|
|
410
|
+
messages,
|
|
411
|
+
model: this.resolveModelId(model),
|
|
412
|
+
stream: true,
|
|
413
|
+
}));
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
ws.on('message', (data) => {
|
|
417
|
+
try {
|
|
418
|
+
const chunk = JSON.parse(data.toString());
|
|
419
|
+
if (chunk.type === 'content') {
|
|
420
|
+
onChunk(chunk.content);
|
|
421
|
+
} else if (chunk.type === 'done') {
|
|
422
|
+
onDone();
|
|
423
|
+
ws.close();
|
|
424
|
+
resolve();
|
|
425
|
+
} else if (chunk.type === 'error') {
|
|
426
|
+
onError(new Error(chunk.error));
|
|
427
|
+
ws.close();
|
|
428
|
+
reject(new Error(chunk.error));
|
|
429
|
+
}
|
|
430
|
+
} catch (e) {
|
|
431
|
+
// Raw text chunk
|
|
432
|
+
onChunk(data.toString());
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
ws.on('error', (error) => {
|
|
437
|
+
onError(error as Error);
|
|
438
|
+
reject(error);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
ws.on('close', () => {
|
|
442
|
+
resolve();
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Code operations
|
|
448
|
+
async generateCode(prompt: string, language: string, model: string): Promise<string> {
|
|
449
|
+
const response = await this.client.post('/ai/generate', {
|
|
450
|
+
prompt,
|
|
451
|
+
language,
|
|
452
|
+
model: this.resolveModelId(model),
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
return response.data.code;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async explainCode(code: string, language: string): Promise<string> {
|
|
459
|
+
const response = await this.client.post('/ai/explain', {
|
|
460
|
+
code,
|
|
461
|
+
language,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
return response.data.explanation;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async reviewCode(code: string, language: string): Promise<{
|
|
468
|
+
score: number;
|
|
469
|
+
issues: { type: string; line: number; message: string; severity: string }[];
|
|
470
|
+
suggestions: string[];
|
|
471
|
+
}> {
|
|
472
|
+
const response = await this.client.post('/ai/review', {
|
|
473
|
+
code,
|
|
474
|
+
language,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
return response.data;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async fixCode(code: string, language: string, fixType: string): Promise<{
|
|
481
|
+
fixed: string;
|
|
482
|
+
changes: { line: number; before: string; after: string; reason: string }[];
|
|
483
|
+
}> {
|
|
484
|
+
const response = await this.client.post('/ai/fix', {
|
|
485
|
+
code,
|
|
486
|
+
language,
|
|
487
|
+
fixType,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
return response.data;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Model resolution - maps short names to actual Vigthoria model IDs
|
|
494
|
+
private resolveModelId(shortName: string): string {
|
|
495
|
+
const modelMap: Record<string, string> = {
|
|
496
|
+
// Vigthoria Native Models
|
|
497
|
+
'fast': 'vigthoria-fast-1.7b',
|
|
498
|
+
'mini': 'vigthoria-mini-0.6b',
|
|
499
|
+
'code': 'vigthoria-v2-code-8b',
|
|
500
|
+
'balanced': 'vigthoria-balanced-4b',
|
|
501
|
+
'creative': 'vigthoria-creative-9b-v4',
|
|
502
|
+
'music': 'vigthoria-music-master-4b',
|
|
503
|
+
// Aliases
|
|
504
|
+
'pro': 'vigthoria-balanced-4b',
|
|
505
|
+
'ultra': 'vigthoria-creative-9b-v4',
|
|
506
|
+
// Legacy Vigthoria models
|
|
507
|
+
'master': 'vigthoria_master:latest',
|
|
508
|
+
'c1': 'vigthoria_c1_m:latest',
|
|
509
|
+
'm1': 'vigthoria_m1_m:latest',
|
|
510
|
+
// External models
|
|
511
|
+
'qwen-coder': 'qwen2.5-coder:7b',
|
|
512
|
+
'qwen-coder-32b': 'qwen2.5-coder:32b',
|
|
513
|
+
'deepseek': 'deepseek-coder-v2:latest',
|
|
514
|
+
'deepseek-r1': 'deepseek-r1:8b',
|
|
515
|
+
'llama3': 'llama3:8b-instruct-q4_0',
|
|
516
|
+
'mixtral': 'mixtral:8x7b-instruct-v0.1-q4_K_M',
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
return modelMap[shortName] || shortName;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Health check
|
|
523
|
+
async healthCheck(): Promise<boolean> {
|
|
524
|
+
try {
|
|
525
|
+
await this.client.get('/health');
|
|
526
|
+
return true;
|
|
527
|
+
} catch {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|