Skip to content

Commit 6da4d11

Browse files
andystimeclaude
andcommitted
fix(vscode): improve studio creation and logging
- Use GPU-enabled Jupyter images from quay.io registry - PyTorch: cuda12-python-3.11.8 tag for CUDA 12 support - TensorFlow: cuda-latest tag for latest CUDA support - Update all jupyter/* images to quay.io/jupyter/* - Auto-fill image version tag from template selection - Add real-time progress logging during studio creation - Stream stdout and stderr to output window - Change Docker progress logs from ERROR to INFO level - Add share link validation before creating studio - Show error if share link is empty - Prevent accidental local GPU usage - Add null checks for all list commands - Prevent 'Cannot read properties of null' errors - Safe fallback to empty arrays for studioList, workerList, shareList, agentList Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent c5264ce commit 6da4d11

3 files changed

Lines changed: 56 additions & 27 deletions

File tree

vscode-extension/src/cli/cli.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -334,21 +334,17 @@ export class CLI {
334334
child.stdout.on('data', (data: Buffer) => {
335335
// On Windows, try to decode using the correct encoding
336336
// Node.js Buffer.toString() defaults to UTF-8, but Windows CMD outputs in the system codepage (e.g., GBK for Chinese Windows)
337-
if (process.platform === 'win32') {
338-
// For Windows, we use UTF-8 as modern Windows shells should support it
339-
// If there are encoding issues, the user should ensure their terminal uses UTF-8
340-
stdout += data.toString('utf8');
341-
} else {
342-
stdout += data.toString();
343-
}
337+
const text = process.platform === 'win32' ? data.toString('utf8') : data.toString();
338+
stdout += text;
339+
// Stream output to logger for real-time progress
340+
Logger.log(text.trim());
344341
});
345342

346343
child.stderr.on('data', (data: Buffer) => {
347-
if (process.platform === 'win32') {
348-
stderr += data.toString('utf8');
349-
} else {
350-
stderr += data.toString();
351-
}
344+
const text = process.platform === 'win32' ? data.toString('utf8') : data.toString();
345+
stderr += text;
346+
// Docker outputs progress info to stderr, so log it as info instead of error
347+
Logger.log(text.trim());
352348
});
353349

354350
child.on('close', (code) => {
@@ -453,6 +449,9 @@ export class CLI {
453449
async studioList(): Promise<StudioEnv[]> {
454450
try {
455451
const res = await this.execCommandJSON<ListResponse<StudioEnvJSON>>(['studio', 'list']);
452+
if (!res || !res.items) {
453+
return [];
454+
}
456455
return res.items.map(env => this.convertStudioEnv(env));
457456
} catch (error) {
458457
Logger.error('Failed to list studios', error);
@@ -606,6 +605,9 @@ export class CLI {
606605
async workerList(): Promise<Worker[]> {
607606
try {
608607
const res = await this.execCommandJSON<ListResponse<WorkerJSON>>(['worker', 'list']);
608+
if (!res || !res.items) {
609+
return [];
610+
}
609611
return res.items.map(w => this.convertWorker(w));
610612
} catch (error) {
611613
Logger.error('Failed to list workers', error);
@@ -700,6 +702,9 @@ export class CLI {
700702
async shareList(): Promise<Share[]> {
701703
try {
702704
const res = await this.execCommandJSON<ListResponse<ShareJSON>>(['share', 'list']);
705+
if (!res || !res.items) {
706+
return [];
707+
}
703708
return res.items.map(s => this.convertShare(s));
704709
} catch {
705710
return [];
@@ -750,6 +755,9 @@ export class CLI {
750755
async agentList(): Promise<Agent[]> {
751756
try {
752757
const res = await this.execCommandJSON<ListResponse<AgentJSON>>(['agent', 'list']);
758+
if (!res || !res.items) {
759+
return [];
760+
}
753761
return res.items.map(a => this.convertAgent(a));
754762
} catch (error) {
755763
Logger.error('Failed to list agents', error);

vscode-extension/src/config/studioTemplates.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const STUDIO_TEMPLATES: StudioTemplate[] = [
2626
{
2727
id: 'quickstart-jupyter',
2828
name: '⭐ Jupyter Notebook (Recommended)',
29-
image: 'jupyter/scipy-notebook:latest',
29+
image: 'quay.io/jupyter/scipy-notebook:latest',
3030
description: 'Best for beginners - Start coding in 1 click with Jupyter',
3131
category: 'quickstart',
3232
features: ['Jupyter Lab', 'Python 3', 'NumPy', 'Pandas', 'Matplotlib'],
@@ -40,10 +40,10 @@ export const STUDIO_TEMPLATES: StudioTemplate[] = [
4040
{
4141
id: 'quickstart-pytorch',
4242
name: '⭐ PyTorch + Jupyter (Recommended)',
43-
image: 'jupyter/pytorch-notebook:latest',
44-
description: 'Best for deep learning - PyTorch with Jupyter ready to use',
43+
image: 'quay.io/jupyter/pytorch-notebook:cuda12-python-3.11.8',
44+
description: 'Best for deep learning - PyTorch with Jupyter and CUDA 12',
4545
category: 'quickstart',
46-
features: ['PyTorch', 'Jupyter Lab', 'CUDA Ready', 'TensorBoard'],
46+
features: ['PyTorch', 'Jupyter Lab', 'CUDA 12', 'TensorBoard'],
4747
defaultPorts: ['8888:8888', '6006:6006'],
4848
defaultEnv: { 'JUPYTER_ENABLE_LAB': 'yes', 'JUPYTER_TOKEN': '' },
4949
icon: '⭐',
@@ -57,10 +57,10 @@ export const STUDIO_TEMPLATES: StudioTemplate[] = [
5757
{
5858
id: 'quickstart-tensorflow',
5959
name: '⭐ TensorFlow + Jupyter',
60-
image: 'jupyter/tensorflow-notebook:latest',
61-
description: 'TensorFlow with Jupyter - great for Keras tutorials',
60+
image: 'quay.io/jupyter/tensorflow-notebook:cuda-latest',
61+
description: 'TensorFlow with Jupyter and CUDA - great for Keras tutorials',
6262
category: 'quickstart',
63-
features: ['TensorFlow', 'Keras', 'Jupyter Lab', 'CUDA Ready'],
63+
features: ['TensorFlow', 'Keras', 'Jupyter Lab', 'CUDA'],
6464
defaultPorts: ['8888:8888', '6006:6006'],
6565
defaultEnv: { 'JUPYTER_ENABLE_LAB': 'yes', 'JUPYTER_TOKEN': '' },
6666
icon: '⭐',
@@ -87,10 +87,10 @@ export const STUDIO_TEMPLATES: StudioTemplate[] = [
8787
{
8888
id: 'pytorch-jupyter',
8989
name: 'PyTorch + Jupyter',
90-
image: 'jupyter/pytorch-notebook:latest',
91-
description: 'PyTorch with JupyterLab pre-installed',
90+
image: 'quay.io/jupyter/pytorch-notebook:cuda12-python-3.11.8',
91+
description: 'PyTorch with JupyterLab and CUDA 12 pre-installed',
9292
category: 'pytorch',
93-
features: ['PyTorch', 'Jupyter Lab', 'CUDA', 'Python 3'],
93+
features: ['PyTorch', 'Jupyter Lab', 'CUDA 12', 'Python 3'],
9494
defaultPorts: ['8888:8888', '6006:6006'],
9595
defaultEnv: { 'JUPYTER_ENABLE_LAB': 'yes', 'JUPYTER_TOKEN': '' },
9696
icon: '🔥',
@@ -132,8 +132,8 @@ export const STUDIO_TEMPLATES: StudioTemplate[] = [
132132
{
133133
id: 'tensorflow-jupyter',
134134
name: 'TensorFlow + Jupyter',
135-
image: 'jupyter/tensorflow-notebook:latest',
136-
description: 'TensorFlow with JupyterLab',
135+
image: 'quay.io/jupyter/tensorflow-notebook:cuda-latest',
136+
description: 'TensorFlow with JupyterLab and CUDA',
137137
category: 'tensorflow',
138138
features: ['TensorFlow', 'Jupyter Lab', 'CUDA', 'Python 3'],
139139
defaultPorts: ['8888:8888', '6006:6006'],
@@ -165,7 +165,7 @@ export const STUDIO_TEMPLATES: StudioTemplate[] = [
165165
{
166166
id: 'jupyter-scipy',
167167
name: 'Jupyter SciPy',
168-
image: 'jupyter/scipy-notebook:latest',
168+
image: 'quay.io/jupyter/scipy-notebook:latest',
169169
description: 'Jupyter with scientific Python stack',
170170
category: 'jupyter',
171171
features: ['Jupyter Lab', 'NumPy', 'Pandas', 'Matplotlib', 'SciPy', 'scikit-learn'],
@@ -178,7 +178,7 @@ export const STUDIO_TEMPLATES: StudioTemplate[] = [
178178
{
179179
id: 'jupyter-datascience',
180180
name: 'Jupyter Data Science',
181-
image: 'jupyter/datascience-notebook:latest',
181+
image: 'quay.io/jupyter/datascience-notebook:latest',
182182
description: 'Jupyter with Python, R, and Julia',
183183
category: 'jupyter',
184184
features: ['Jupyter Lab', 'Python', 'R', 'Julia', 'Pandas', 'scikit-learn'],
@@ -191,7 +191,7 @@ export const STUDIO_TEMPLATES: StudioTemplate[] = [
191191
{
192192
id: 'jupyter-all-spark',
193193
name: 'Jupyter All-Spark',
194-
image: 'jupyter/all-spark-notebook:latest',
194+
image: 'quay.io/jupyter/all-spark-notebook:latest',
195195
description: 'Jupyter with Python, R, and Apache Spark',
196196
category: 'jupyter',
197197
features: ['Jupyter Lab', 'Python', 'R', 'Apache Spark', 'PySpark'],

vscode-extension/src/views/createStudioPanel.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ export class CreateStudioPanel {
8787
versionTag: string;
8888
}) {
8989
try {
90+
// Validate required fields
91+
if (!data.name || !data.name.trim()) {
92+
vscode.window.showErrorMessage('Studio name is required');
93+
return;
94+
}
95+
if (!data.gpuUrl || !data.gpuUrl.trim()) {
96+
vscode.window.showErrorMessage('Share link is required. Please enter a GPU share link.');
97+
return;
98+
}
99+
90100
// Resolve image from template or custom
91101
let image = data.customImage;
92102
let ports = data.ports ? data.ports.split(',').map(p => p.trim()).filter(Boolean) : [];
@@ -473,6 +483,17 @@ export class CreateStudioPanel {
473483
portsHelper.textContent = 'Comma-separated port mappings (host:container). Leave empty to use template defaults.';
474484
}
475485
}
486+
487+
// Auto-fill image version tag from template
488+
const versionTagField = document.getElementById('versionTag');
489+
if (versionTagField && template.image) {
490+
const [, defaultTag] = template.image.split(':');
491+
if (defaultTag) {
492+
versionTagField.value = defaultTag;
493+
} else {
494+
versionTagField.value = 'latest';
495+
}
496+
}
476497
} else {
477498
infoBox.style.display = 'none';
478499
}

0 commit comments

Comments
 (0)