langtrain 0.1.14 → 0.1.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langtrain",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Unified JavaScript SDK for Langtrain Ecosystem",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -11,7 +11,8 @@
11
11
  "scripts": {
12
12
  "build": "tsup",
13
13
  "dev": "tsup --watch",
14
- "test": "echo \"Error: no test specified\" && exit 1"
14
+ "test": "echo \"Error: no test specified\" && exit 1",
15
+ "postinstall": "node dist/cli.js --first-run || true"
15
16
  },
16
17
  "keywords": [
17
18
  "langtrain",
@@ -29,6 +30,7 @@
29
30
  "author": "Langtrain AI",
30
31
  "license": "MIT",
31
32
  "devDependencies": {
33
+ "@types/gradient-string": "^1.1.6",
32
34
  "@types/node": "^25.2.3",
33
35
  "langtune": "file:../langtune/js",
34
36
  "langvision": "file:../langvision/js",
@@ -39,8 +41,8 @@
39
41
  "@clack/prompts": "^1.0.1",
40
42
  "axios": "^1.13.5",
41
43
  "commander": "^14.0.3",
42
- "conf": "^15.1.0",
43
44
  "gradient-string": "^3.0.0",
44
- "kleur": "^4.1.5"
45
+ "kleur": "^4.1.5",
46
+ "langtrain": "^0.1.15"
45
47
  }
46
48
  }
@@ -0,0 +1,76 @@
1
+ import { password, isCancel, cancel, intro, green, yellow, red, bgCyan, black, spinner, gray } from './ui';
2
+ import { getConfig, saveConfig } from './config';
3
+ import { SubscriptionClient, SubscriptionInfo } from '../index';
4
+
5
+ export async function ensureAuth(): Promise<string> {
6
+ let config = getConfig();
7
+
8
+ if (!config.apiKey) {
9
+ intro(yellow('Authentication required to verify plan & features.'));
10
+ await handleLogin();
11
+ config = getConfig();
12
+ }
13
+
14
+ return config.apiKey as string;
15
+ }
16
+
17
+ export async function handleLogin() {
18
+ while (true) {
19
+ const apiKey = await password({
20
+ message: 'Enter your Langtrain API Key:',
21
+ validate(value) {
22
+ if (!value || value.length === 0) return 'API Key is required';
23
+ },
24
+ });
25
+
26
+ if (isCancel(apiKey)) {
27
+ cancel('Operation cancelled');
28
+ process.exit(0);
29
+ }
30
+
31
+ const s = spinner();
32
+ s.start('Verifying API Key...');
33
+
34
+ // Verify key immediately
35
+ try {
36
+ const client = new SubscriptionClient({ apiKey: apiKey as string });
37
+ const info = await client.getStatus();
38
+
39
+ s.stop(green(`Authenticated as ${info.plan === 'pro' ? 'PRO' : info.plan.toUpperCase()}`));
40
+
41
+ const config = getConfig();
42
+ saveConfig({ ...config, apiKey: apiKey as string });
43
+ // intro(green('API Key saved successfully!')); // success message above is enough
44
+ return; // Exit loop on success
45
+ } catch (e: any) {
46
+ s.stop(red('Invalid API Key. Please try again.'));
47
+ // Loop continues
48
+ }
49
+ }
50
+ }
51
+
52
+ export async function getSubscription(apiKey: string): Promise<SubscriptionInfo | null> {
53
+ const client = new SubscriptionClient({ apiKey });
54
+ const s = spinner();
55
+ s.start('Verifying subscription plan...');
56
+ try {
57
+ const info = await client.getStatus();
58
+
59
+ // Enhance: Show plan details immediately on auth check
60
+ const planLabel = info.plan === 'pro' ? bgCyan(' PRO ') : info.plan.toUpperCase();
61
+ s.stop(green(`Authenticated as ${planLabel}`));
62
+
63
+ if (info.is_active === false) {
64
+ console.log(yellow('Warning: Your subscription is not active. Some features may be limited.'));
65
+ }
66
+
67
+ return info;
68
+ } catch (e: any) {
69
+ s.stop(red('Failed to verify subscription.'));
70
+ if (e.response && e.response.status === 401) {
71
+ console.log(red('Invalid API Key. Please run login again.'));
72
+ // Optionally clear key?
73
+ }
74
+ return null;
75
+ }
76
+ }
@@ -0,0 +1,28 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.langtrain');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+
8
+ export interface CLIConfig {
9
+ apiKey?: string;
10
+ baseUrl?: string;
11
+ [key: string]: any;
12
+ }
13
+
14
+ export function getConfig(): CLIConfig {
15
+ if (!fs.existsSync(CONFIG_FILE)) return {};
16
+ try {
17
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
18
+ } catch {
19
+ return {};
20
+ }
21
+ }
22
+
23
+ export function saveConfig(config: CLIConfig) {
24
+ if (!fs.existsSync(CONFIG_DIR)) {
25
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
26
+ }
27
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
28
+ }
@@ -0,0 +1,183 @@
1
+ import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, gray, bgCyan, black, gradient } from '../ui';
2
+ import { AgentClient, ModelClient } from '../../index';
3
+
4
+ export async function handleAgentCreate(client: AgentClient, modelClient: ModelClient) {
5
+ const name = await text({
6
+ message: 'Agent Name:',
7
+ placeholder: 'e.g. Support Bot',
8
+ validate(value) {
9
+ if (!value || value.length === 0) return 'API Key is required';
10
+ },
11
+ });
12
+ if (isCancel(name)) {
13
+ cancel('Operation cancelled');
14
+ return;
15
+ }
16
+
17
+ const description = await text({
18
+ message: 'Description:',
19
+ placeholder: 'e.g. A helpful support assistant',
20
+ });
21
+ if (isCancel(description)) return;
22
+
23
+ const systemPrompt = await text({
24
+ message: 'System Prompt:',
25
+ placeholder: 'e.g. You are a helpful assistant.',
26
+ initialValue: 'You are a helpful assistant.'
27
+ });
28
+ if (isCancel(systemPrompt)) return;
29
+
30
+ // Select model
31
+ let model: string | symbol = 'gpt-4o';
32
+ const s = spinner();
33
+ s.start('Fetching agent models...');
34
+ try {
35
+ const models = await modelClient.list('agent');
36
+ s.stop(`Found ${models.length} models`);
37
+ if (models.length > 0) {
38
+ model = await select({
39
+ message: 'Select Agent Model:',
40
+ options: models.map(m => ({ value: m.id, label: m.id }))
41
+ });
42
+ }
43
+ } catch (e) {
44
+ s.stop(yellow('Could not fetch models, using default.'));
45
+ }
46
+
47
+ if (isCancel(model)) return;
48
+
49
+ const s2 = spinner();
50
+ s2.start('Creating agent...');
51
+
52
+ try {
53
+ const agents = await client.list();
54
+ let workspaceId = "";
55
+ if (agents.length > 0) {
56
+ workspaceId = agents[0].workspace_id;
57
+ } else {
58
+ s2.stop(yellow('Workspace ID needed (no existing agents found).'));
59
+ const wid = await text({
60
+ message: 'Enter Workspace ID (UUID):',
61
+ validate(value) {
62
+ if (!value || value.length === 0) return 'Required';
63
+ },
64
+ });
65
+ if (isCancel(wid)) return;
66
+ workspaceId = wid as string;
67
+ s2.start('Creating agent...');
68
+ }
69
+
70
+ const agent = await client.create({
71
+ workspace_id: workspaceId,
72
+ name: name as string,
73
+ description: description as string,
74
+ config: {
75
+ system_prompt: systemPrompt as string,
76
+ model: model as string
77
+ }
78
+ });
79
+ s2.stop(green(`Agent "${agent.name}" created successfully! ID: ${agent.id}`));
80
+ } catch (e: any) {
81
+ s2.stop(red('Failed to create agent.'));
82
+ throw e;
83
+ }
84
+ }
85
+
86
+ export async function handleAgentDelete(client: AgentClient) {
87
+ const s = spinner();
88
+ s.start('Fetching agents...');
89
+ const agents = await client.list();
90
+ s.stop(`Found ${agents.length} agents`);
91
+
92
+ if (agents.length === 0) {
93
+ intro(yellow('No agents to delete.'));
94
+ return;
95
+ }
96
+
97
+ const agentId = await select({
98
+ message: 'Select an agent to DELETE:',
99
+ options: agents.map(a => ({ value: a.id, label: a.name, hint: a.description || 'No description' }))
100
+ });
101
+
102
+ if (isCancel(agentId)) return;
103
+
104
+ const confirmDel = await select({
105
+ message: `Are you sure you want to delete this agent?`,
106
+ options: [
107
+ { value: 'yes', label: 'Yes, delete it', hint: 'Cannot be undone' },
108
+ { value: 'no', label: 'No, keep it' }
109
+ ]
110
+ });
111
+
112
+ if (confirmDel !== 'yes') {
113
+ intro(gray('Deletion cancelled.'));
114
+ return;
115
+ }
116
+
117
+ const d = spinner();
118
+ d.start('Deleting agent...');
119
+ try {
120
+ await client.delete(agentId as string);
121
+ d.stop(green('Agent deleted successfully.'));
122
+ } catch (e: any) {
123
+ d.stop(red('Failed to delete agent.'));
124
+ throw e;
125
+ }
126
+ }
127
+
128
+ export async function handleAgentList(client: AgentClient) {
129
+ const s = spinner();
130
+ s.start('Fetching agents...');
131
+ const agents = await client.list();
132
+ s.stop(`Found ${agents.length} agents`);
133
+
134
+ if (agents.length === 0) {
135
+ intro(yellow('No agents found in your workspace.'));
136
+ return;
137
+ }
138
+
139
+ const agentId = await select({
140
+ message: 'Select an agent to run:',
141
+ options: agents.map(a => ({ value: a.id, label: a.name, hint: a.description || 'No description' }))
142
+ });
143
+
144
+ if (isCancel(agentId)) return;
145
+
146
+ await handleAgentRun(client, agentId as string, agents.find(a => a.id === agentId)?.name || 'Agent');
147
+ }
148
+
149
+ async function handleAgentRun(client: AgentClient, agentId: string, agentName: string) {
150
+ intro(bgCyan(black(` Chatting with ${agentName} `)));
151
+ console.log(gray('Type "exit" to quit conversation.'));
152
+
153
+ let conversationId: string | undefined = undefined;
154
+
155
+ while (true) {
156
+ const input = await text({
157
+ message: 'You:',
158
+ placeholder: 'Type a message...',
159
+ });
160
+
161
+ if (isCancel(input) || input === 'exit') {
162
+ break;
163
+ }
164
+
165
+ const s = spinner();
166
+ s.start('Agent is thinking...');
167
+ try {
168
+ const result = await client.execute(agentId, { prompt: input }, [], conversationId);
169
+ s.stop();
170
+
171
+ if (result.output && result.output.response) {
172
+ console.log(gradient.pastel(`Agent: ${result.output.response}`));
173
+ } else {
174
+ console.log(gradient.pastel(`Agent: ${JSON.stringify(result.output)}`));
175
+ }
176
+
177
+ conversationId = result.conversation_id;
178
+ } catch (e: any) {
179
+ s.stop(red('Error running agent.'));
180
+ console.error(e);
181
+ }
182
+ }
183
+ }
@@ -0,0 +1,30 @@
1
+ import { spinner, intro, red, green, gray, bgCyan } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { SubscriptionClient } from '../../index';
4
+
5
+ export async function handleSubscriptionStatus() {
6
+ const config = getConfig();
7
+ if (!config.apiKey) {
8
+ intro(red('Not logged in. Run "login" first.'));
9
+ return;
10
+ }
11
+ const client = new SubscriptionClient({ apiKey: config.apiKey });
12
+ const s = spinner();
13
+ s.start('Fetching subscription status...');
14
+ try {
15
+ const info = await client.getStatus();
16
+ s.stop(green('Subscription Status:'));
17
+
18
+ console.log(gray('Plan: ') + (info.plan === 'pro' ? bgCyan(' PRO ') : info.plan.toUpperCase()));
19
+ console.log(gray('Active: ') + (info.is_active ? green('Yes') : red('No')));
20
+ if (info.expires_at) console.log(gray('Expires: ') + new Date(info.expires_at).toLocaleDateString());
21
+
22
+ console.log(gray('\nLimits:'));
23
+ console.log(` Models: ${info.limits.max_models === -1 ? 'Unlimited' : info.limits.max_models}`);
24
+ console.log(` Training Jobs: ${info.limits.max_training_jobs}`);
25
+
26
+ } catch (e: any) {
27
+ s.stop(red('Failed to fetch status.'));
28
+ console.error(e.message);
29
+ }
30
+ }
@@ -0,0 +1,155 @@
1
+ import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, bgCyan, black, gradient } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { Langtune, ModelClient, SubscriptionClient, FileClient, TrainingClient } from '../../index';
4
+
5
+ // Handler for Langtune Fine-tuning
6
+ export async function handleTuneFinetune(tune: Langtune, modelClient: ModelClient) {
7
+ let model: string | symbol = '';
8
+
9
+ const s = spinner();
10
+ s.start('Fetching available text models...');
11
+ try {
12
+ const models = await modelClient.list('text');
13
+ s.stop(`Found ${models.length} text models`);
14
+
15
+ if (models.length > 0) {
16
+ model = await select({
17
+ message: 'Select base model:',
18
+ options: models.map(m => ({ value: m.id, label: m.id, hint: m.owned_by }))
19
+ });
20
+ }
21
+ } catch (e) {
22
+ s.stop(yellow('Failed to fetch models. Using manual input.'));
23
+ model = await text({
24
+ message: 'Enter base model (e.g., gpt-3.5-turbo):',
25
+ placeholder: 'gpt-3.5-turbo',
26
+ validate(value) {
27
+ if (!value || value.length === 0) return 'Value is required!';
28
+ },
29
+ });
30
+ }
31
+
32
+ if (isCancel(model)) {
33
+ cancel('Operation cancelled.');
34
+ process.exit(0);
35
+ }
36
+
37
+ const trainFile = await text({
38
+ message: 'Enter path to training file:',
39
+ placeholder: './data.jsonl',
40
+ validate(value) {
41
+ if (!value || value.length === 0) return 'Value is required!';
42
+ },
43
+ });
44
+ if (isCancel(trainFile)) cancel('Operation cancelled.');
45
+
46
+ const epochs = await text({
47
+ message: 'Num Epochs:',
48
+ placeholder: '3',
49
+ initialValue: '3'
50
+ });
51
+ if (isCancel(epochs)) cancel('Operation cancelled.');
52
+
53
+ const track = await select({
54
+ message: 'Track this job on Langtrain Cloud?',
55
+ options: [
56
+ { value: 'yes', label: 'Yes', hint: 'Upload dataset and log job' },
57
+ { value: 'no', label: 'No', hint: 'Local only' }
58
+ ]
59
+ });
60
+ if (isCancel(track)) cancel('Operation cancelled.');
61
+
62
+ if (track === 'yes') {
63
+ const s = spinner();
64
+ s.start('Connecting to Cloud...');
65
+ try {
66
+ const config = getConfig();
67
+ if (!config.apiKey) throw new Error('API Key required. Run "login" first.');
68
+
69
+ // Check Subscription
70
+ const subClient = new SubscriptionClient({ apiKey: config.apiKey });
71
+ const sub = await subClient.getStatus();
72
+ if (!sub.features.includes('cloud_finetuning')) {
73
+ s.stop(red('Feature "cloud_finetuning" is not available on your plan.'));
74
+ const upgrade = await confirm({ message: 'Upgrade to Pro for cloud tracking?' });
75
+ if (upgrade && !isCancel(upgrade)) {
76
+ console.log(bgCyan(black(' Visit https://langtrain.ai/dashboard/billing to upgrade. ')));
77
+ }
78
+ return;
79
+ }
80
+
81
+ const fileClient = new FileClient({ apiKey: config.apiKey });
82
+ const trainingClient = new TrainingClient({ apiKey: config.apiKey });
83
+
84
+ s.message('Uploading dataset...');
85
+ const fileResp = await fileClient.upload(trainFile as string);
86
+
87
+ s.message('Creating Job...');
88
+ const job = await trainingClient.createJob({
89
+ name: `cli-sft-${Date.now()}`,
90
+ base_model: model as string,
91
+ dataset_id: fileResp.id,
92
+ task: 'text',
93
+ hyperparameters: {
94
+ n_epochs: parseInt(epochs as string)
95
+ }
96
+ });
97
+ s.stop(green(`Job tracked: ${job.id}`));
98
+ } catch (e: any) {
99
+ s.stop(red(`Tracking failed: ${e.message}`));
100
+ const cont = await confirm({ message: 'Continue with local training anyway?' });
101
+ if (!cont || isCancel(cont)) return;
102
+ }
103
+ }
104
+
105
+ const s2 = spinner();
106
+ s2.start('Starting local fine-tuning...');
107
+
108
+ try {
109
+ const config: any = {
110
+ model: model as string,
111
+ trainFile: trainFile as string,
112
+ preset: 'default', // simplified
113
+ epochs: parseInt(epochs as string),
114
+ batchSize: 1,
115
+ learningRate: 2e-5,
116
+ loraRank: 16,
117
+ outputDir: './output'
118
+ };
119
+
120
+ await tune.finetune(config);
121
+ s2.stop(green('Fine-tuning job started successfully!'));
122
+ } catch (e: any) {
123
+ s2.stop(red('Failed to start job.'));
124
+ throw e;
125
+ }
126
+ }
127
+
128
+ // Handler for Langtune Generation
129
+ export async function handleTuneGenerate(tune: Langtune) {
130
+ const model = await text({
131
+ message: 'Enter model path:',
132
+ placeholder: './output/model',
133
+ initialValue: './output/model'
134
+ });
135
+ if (isCancel(model)) cancel('Operation cancelled');
136
+
137
+ const prompt = await text({
138
+ message: 'Enter prompt:',
139
+ placeholder: 'Hello world',
140
+ });
141
+ if (isCancel(prompt)) cancel('Operation cancelled');
142
+
143
+ const s = spinner();
144
+ s.start('Connecting to Langtrain Inference API...');
145
+
146
+ try {
147
+ const response = await tune.generate(model as string, { prompt: prompt as string });
148
+ s.stop('Generation complete');
149
+ intro('Response:');
150
+ console.log(gradient.pastel(response));
151
+ } catch (e: any) {
152
+ s.stop(red('Generation failed.'));
153
+ throw e;
154
+ }
155
+ }
@@ -0,0 +1,159 @@
1
+ import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, bgCyan, black, gradient } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { Langvision, ModelClient, SubscriptionClient, FileClient, TrainingClient } from '../../index';
4
+
5
+ // Handler for Langvision Fine-tuning
6
+ export async function handleVisionFinetune(vision: Langvision, modelClient: ModelClient) {
7
+ let model: string | symbol = '';
8
+
9
+ const s = spinner();
10
+ s.start('Fetching available vision models...');
11
+ try {
12
+ const models = await modelClient.list('vision');
13
+ s.stop(`Found ${models.length} vision models`);
14
+
15
+ if (models.length > 0) {
16
+ model = await select({
17
+ message: 'Select base vision model:',
18
+ options: models.map(m => ({ value: m.id, label: m.id, hint: m.owned_by }))
19
+ });
20
+ } else {
21
+ model = await text({
22
+ message: 'Enter base vision model:',
23
+ placeholder: 'llava-v1.5-7b',
24
+ initialValue: 'llava-v1.5-7b'
25
+ });
26
+ }
27
+ } catch (e) {
28
+ s.stop(yellow('Failed to fetch models. Using manual input.'));
29
+ model = await text({
30
+ message: 'Enter base vision model:',
31
+ placeholder: 'llava-v1.5-7b',
32
+ initialValue: 'llava-v1.5-7b'
33
+ });
34
+ }
35
+
36
+ if (isCancel(model)) {
37
+ cancel('Operation cancelled');
38
+ process.exit(0);
39
+ }
40
+
41
+ const dataset = await text({
42
+ message: 'Enter dataset path:',
43
+ placeholder: './dataset',
44
+ });
45
+ if (isCancel(dataset)) cancel('Operation cancelled');
46
+
47
+ const epochs = await text({
48
+ message: 'Num Epochs:',
49
+ placeholder: '3',
50
+ initialValue: '3'
51
+ });
52
+ if (isCancel(epochs)) cancel('Operation cancelled');
53
+
54
+ const track = await select({
55
+ message: 'Track this job on Langtrain Cloud?',
56
+ options: [
57
+ { value: 'yes', label: 'Yes', hint: 'Upload dataset and log job' },
58
+ { value: 'no', label: 'No', hint: 'Local only' }
59
+ ]
60
+ });
61
+ if (isCancel(track)) cancel('Operation cancelled');
62
+
63
+ if (track === 'yes') {
64
+ const s = spinner();
65
+ s.start('Connecting to Cloud...');
66
+ try {
67
+ const config = getConfig();
68
+ if (!config.apiKey) throw new Error('API Key required. Run "login" first.');
69
+
70
+ // Check Subscription
71
+ const subClient = new SubscriptionClient({ apiKey: config.apiKey });
72
+ const sub = await subClient.getStatus();
73
+ if (!sub.features.includes('cloud_finetuning')) {
74
+ s.stop(red('Feature "cloud_finetuning" is not available on your plan.'));
75
+ const upgrade = await confirm({ message: 'Upgrade to Pro for cloud tracking?' });
76
+ if (upgrade && !isCancel(upgrade)) {
77
+ console.log(bgCyan(black(' Visit https://langtrain.ai/dashboard/billing to upgrade. ')));
78
+ }
79
+ return;
80
+ }
81
+
82
+ const fileClient = new FileClient({ apiKey: config.apiKey });
83
+ const trainingClient = new TrainingClient({ apiKey: config.apiKey });
84
+
85
+ s.message('Uploading dataset...');
86
+ const fileResp = await fileClient.upload(dataset as string, undefined, 'fine-tune-vision');
87
+
88
+ s.message('Creating Job...');
89
+ const job = await trainingClient.createJob({
90
+ name: `cli-vision-${Date.now()}`,
91
+ base_model: model as string,
92
+ dataset_id: fileResp.id,
93
+ task: 'vision',
94
+ training_method: 'lora',
95
+ hyperparameters: {
96
+ n_epochs: parseInt(epochs as string)
97
+ }
98
+ });
99
+ s.stop(green(`Job tracked: ${job.id}`));
100
+ } catch (e: any) {
101
+ s.stop(red(`Tracking failed: ${e.message}`));
102
+ const cont = await confirm({ message: 'Continue with local training anyway?' });
103
+ if (!cont || isCancel(cont)) return;
104
+ }
105
+ }
106
+
107
+ const s2 = spinner();
108
+ s2.start('Analyzing dataset structure...');
109
+ await new Promise(r => setTimeout(r, 800));
110
+ s2.message('Starting vision fine-tuning on Langtrain Cloud...');
111
+
112
+ try {
113
+ const config: any = {
114
+ model: model as string,
115
+ dataset: dataset as string,
116
+ epochs: parseInt(epochs as string),
117
+ batchSize: 1,
118
+ learningRate: 2e-5,
119
+ loraRank: 16,
120
+ outputDir: './vision-output'
121
+ };
122
+ await vision.finetune(config);
123
+ s2.stop(green('Vision fine-tuning started successfully!'));
124
+ } catch (e: any) {
125
+ s2.stop(red('Failed to start vision job.'));
126
+ throw e;
127
+ }
128
+ }
129
+
130
+ // Handler for Langvision Generation
131
+ export async function handleVisionGenerate(vision: Langvision) {
132
+ const model = await text({
133
+ message: 'Enter model path:',
134
+ placeholder: './vision-output/model',
135
+ initialValue: './vision-output/model'
136
+ });
137
+ if (isCancel(model)) cancel('Operation cancelled');
138
+
139
+ const prompt = await text({
140
+ message: 'Enter prompt/image path:', // Simplified for CLI
141
+ placeholder: 'Describe this image...',
142
+ });
143
+ if (isCancel(prompt)) cancel('Operation cancelled');
144
+
145
+ const s = spinner();
146
+ s.start('Uploading image and context...');
147
+ await new Promise(r => setTimeout(r, 600));
148
+ s.message('Generating vision response...');
149
+
150
+ try {
151
+ const response = await vision.generate(model as string, { prompt: prompt as string });
152
+ s.stop('Generation complete');
153
+ intro('Response:');
154
+ console.log(gradient.pastel(response));
155
+ } catch (e: any) {
156
+ s.stop(red('Generation failed.'));
157
+ throw e;
158
+ }
159
+ }