@@ -2,14 +2,15 @@ import { Listr } from 'listr2';
22import pWaitFor from 'p-wait-for' ;
33import { createComposeCommandArgs , type ComposeLayerSelection } from '../lib/compose.js' ;
44import type { BootstrapCommandOptions } from '../types/cli.types.js' ;
5- import type { ProjectContext } from '../types/project.types.js' ;
5+ import type { AiLlmBootstrapEnv , ProjectContext } from '../types/project.types.js' ;
66import { ensureGiteaAdmin } from './gitea-admin.service.js' ;
77import { ensurePenpotAdmin } from './penpot-admin.service.js' ;
88import { ensurePlaneAdmin , waitForPlaneBootstrapPrerequisites } from './plane-admin.service.js' ;
99import { printCommandHeader } from '../ui/banner.js' ;
1010import { formatTaskTitle , printInfo , printSuccess } from '../ui/logger.js' ;
1111import { runCommand } from '../utils/process.js' ;
1212import { parseAiLlmBootstrapEnv , parseBootstrapEnv } from './project.service.js' ;
13+ import { collectConfiguredOllamaModels } from '../utils/model-lists.js' ;
1314
1415const VERBOSE_TASK_RENDERER = 'verbose' as const ;
1516
@@ -81,7 +82,7 @@ export function createBootstrapTasks(
8182 tasks . push ( {
8283 title : formatTaskTitle ( 'bootstrap' , 'Align Ollama runtime models' ) ,
8384 task : async ( ) => {
84- const result = await ensureOllamaModels ( context ) ;
85+ const result = await ensureOllamaModels ( context , aiLlmEnv ) ;
8586 printInfo ( `Ollama runtime models ${ result } .` , 'bootstrap' ) ;
8687 }
8788 } ) ;
@@ -93,11 +94,26 @@ export function createBootstrapTasks(
9394 * Waits for Ollama and ensures the configured runtime models are present locally.
9495 */
9596async function ensureOllamaModels (
96- context : ProjectContext
97+ context : ProjectContext ,
98+ env : AiLlmBootstrapEnv
9799) : Promise < 'present' | 'pulled' > {
98100 await waitForService ( context , 'ollama' , 180 , { includeAiLlm : true } ) ;
99101
100- const syncResult = await runCommand (
102+ const configuredModels = collectConfiguredOllamaModels ( env ) ;
103+ const missingModels = await listMissingOllamaModels ( context , configuredModels ) ;
104+
105+ if ( missingModels . length === 0 ) {
106+ printInfo ( 'All configured Ollama models are already available locally.' , 'bootstrap' ) ;
107+ return 'present' ;
108+ }
109+
110+ printInfo (
111+ `Syncing ${ missingModels . length } missing Ollama model${ missingModels . length === 1 ? '' : 's' } : ${ missingModels . join ( ', ' ) } ` ,
112+ 'bootstrap'
113+ ) ;
114+ printInfo ( 'Large Ollama models can take a long time to download; progress will stream below.' , 'bootstrap' ) ;
115+
116+ await runCommand (
101117 'docker' ,
102118 createComposeCommandArgs ( context , [
103119 'exec' ,
@@ -108,24 +124,42 @@ async function ensureOllamaModels(
108124 ] , { includeAiLlm : true } ) ,
109125 {
110126 cwd : context . projectRoot ,
111- captureOutput : true ,
112127 scope : 'bootstrap'
113128 }
114129 ) ;
115130
116- const lines = syncResult . stdout
117- . split ( / \r ? \n / u)
118- . map ( ( line ) => line . trim ( ) )
119- . filter ( ( line ) => line . length > 0 ) ;
131+ return 'pulled' ;
132+ }
133+
134+ /**
135+ * Lists the configured Ollama models that are still missing locally inside the container.
136+ */
137+ async function listMissingOllamaModels (
138+ context : ProjectContext ,
139+ configuredModels : string [ ]
140+ ) : Promise < string [ ] > {
141+ const missingModels : string [ ] = [ ] ;
142+
143+ for ( const modelName of configuredModels ) {
144+ const inspectResult = await runCommand (
145+ 'docker' ,
146+ createComposeCommandArgs ( context , [ 'exec' , '-T' , 'ollama' , 'ollama' , 'show' , modelName ] , {
147+ includeAiLlm : true
148+ } ) ,
149+ {
150+ allowFailure : true ,
151+ captureOutput : true ,
152+ cwd : context . projectRoot ,
153+ scope : 'bootstrap'
154+ }
155+ ) ;
120156
121- for ( const line of lines ) {
122- if ( ! line . startsWith ( 'ATLAS_OLLAMA_MODEL_SYNC_RESULT=' ) ) {
123- printInfo ( line , 'bootstrap' ) ;
157+ if ( inspectResult . exitCode !== 0 ) {
158+ missingModels . push ( modelName ) ;
124159 }
125160 }
126161
127- const resultLine = lines . find ( ( line ) => line . startsWith ( 'ATLAS_OLLAMA_MODEL_SYNC_RESULT=' ) ) ;
128- return resultLine ?. endsWith ( 'pulled' ) ? 'pulled' : 'present' ;
162+ return missingModels ;
129163}
130164
131165/**
0 commit comments