44# 1. Pre-built assets in SRC_DIST_DIR (manually built by user)
55# 2. If BUILD_UI=ON: npm build
66# 3. If above did not produce assets and HF_ENABLED=ON: HF Bucket download
7+ # of dist.tar.gz (verified against dist.tar.gz.sha256)
78
8- cmake_minimum_required (VERSION 3.16 )
9+ cmake_minimum_required (VERSION 3.18 )
910
1011set (UI_SOURCE_DIR "" CACHE STRING "UI source directory (to run npm build)" )
1112set (UI_BINARY_DIR "" CACHE STRING "UI binary directory (to store generated files)" )
@@ -16,80 +17,21 @@ set(HF_ENABLED "" CACHE STRING "Whether to allow HF Bucket download (ON/O
1617set (BUILD_UI "" CACHE STRING "Build UI via npm (ON/OFF)" )
1718set (LLAMA_UI_EMBED "" CACHE STRING "Path to llama-ui-embed helper" )
1819
19- # IMPORTANT: When adding PWA assets, sync across all 3 places:
20- # 1. tools/ui/src/lib/constants/pwa.ts (APPLE_DEVICES, PUBLIC_ENDPOINTS)
21- # 2. tools/server/server-http.cpp (public_endpoints)
22- # 3. scripts/ui-assets.cmake (ASSETS list)
23- # - C++ (server-http.cpp) - public endpoints (splash screens generated via helper)
24- # - TypeScript (constants/pwa.ts) - APPLE_DEVICES, PWA_MANIFEST, PUBLIC_ENDPOINTS
20+ # IMPORTANT: When adding PWA assets, sync:
21+ # - tools/ui/src/lib/constants/pwa.ts (APPLE_DEVICES, PWA_MANIFEST)
2522#
26- # When adding/changing PWA assets, update tools/ui/src/lib/constants/pwa.ts first,
27- # then sync any new file names here and in server-http.cpp.
28- set (ASSETS
23+ # The HTTP server registers routes and public endpoints for every embedded asset.
24+ set (REQUIRED_ASSETS
2925 index.html
3026 loading.html
31- # PWA assets
32- favicon.ico
33- favicon-dark.ico
34- favicon.svg
35- favicon-dark.svg
36- pwa-64x64.png
37- pwa-192x192.png
38- pwa-512x512.png
39- maskable-icon-512x512.png
40- apple -touch-icon-180x180.png
41- # iOS splash screens
42- apple -splash-portrait-640x1136.png
43- apple -splash-landscape-1136x640.png
44- apple -splash-portrait-750x1334.png
45- apple -splash-landscape-1334x750.png
46- apple -splash-portrait-1170x2532.png
47- apple -splash-landscape-2532x1170.png
48- apple -splash-portrait-1179x2556.png
49- apple -splash-landscape-2556x1179.png
50- apple -splash-portrait-1206x2622.png
51- apple -splash-landscape-2622x1206.png
52- apple -splash-portrait-1284x2778.png
53- apple -splash-landscape-2778x1284.png
54- apple -splash-portrait-1290x2796.png
55- apple -splash-landscape-2796x1290.png
56- apple -splash-portrait-1320x2868.png
57- apple -splash-landscape-2868x1320.png
58- apple -splash-portrait-1488x2266.png
59- apple -splash-landscape-2266x1488.png
60- apple -splash-portrait-1640x2360.png
61- apple -splash-landscape-2360x1640.png
62- apple -splash-portrait-1668x2388.png
63- apple -splash-landscape-2388x1668.png
64- apple -splash-portrait-2048x2732.png
65- apple -splash-landscape-2732x2048.png
66- # iOS dark splash screens
67- apple -splash-portrait-dark-640x1136.png
68- apple -splash-landscape-dark-1136x640.png
69- apple -splash-portrait-dark-750x1334.png
70- apple -splash-landscape-dark-1334x750.png
71- apple -splash-portrait-dark-1170x2532.png
72- apple -splash-landscape-dark-2532x1170.png
73- apple -splash-portrait-dark-1179x2556.png
74- apple -splash-landscape-dark-2556x1179.png
75- apple -splash-portrait-dark-1206x2622.png
76- apple -splash-landscape-dark-2622x1206.png
77- apple -splash-portrait-dark-1284x2778.png
78- apple -splash-landscape-dark-2778x1284.png
79- apple -splash-portrait-dark-1290x2796.png
80- apple -splash-landscape-dark-2796x1290.png
81- apple -splash-portrait-dark-1320x2868.png
82- apple -splash-landscape-dark-2868x1320.png
83- apple -splash-portrait-dark-1640x2360.png
84- apple -splash-landscape-dark-2360x1640.png
85- apple -splash-portrait-dark-1668x2388.png
86- apple -splash-landscape-dark-2388x1668.png
87- apple -splash-portrait-dark-2048x2732.png
88- apple -splash-landscape-dark-2732x2048.png
8927 manifest.webmanifest
9028 sw.js
91- _app/version.json
9229 build.json
30+ # post-build.js flattens and dehashes these to fixed names in the dist dir
31+ bundle.js
32+ bundle.css
33+ workbox.js
34+ version.json
9335)
9436
9537set (DIST_DIR "${UI_BINARY_DIR } /dist" )
@@ -98,41 +40,21 @@ set(STAMP_FILE "${UI_BINARY_DIR}/.ui-stamp")
9840set (UI_CPP "${UI_BINARY_DIR } /ui.cpp" )
9941set (UI_H "${UI_BINARY_DIR } /ui.h" )
10042
101- function (assets_present out_var )
43+ function (assets_present dir out_var )
10244 set (present TRUE )
103- foreach (asset ${ASSETS } )
104- if (NOT EXISTS "${DIST_DIR } /${asset} " )
45+ foreach (asset ${REQUIRED_ASSETS } )
46+ if (NOT EXISTS "${dir } /${asset} " )
10547 set (present FALSE )
10648 break ()
10749 endif ()
10850 endforeach ()
10951 set (${out_var} ${present} PARENT_SCOPE )
11052endfunction ()
11153
112- function (copy_src_dist out_var )
113- set (${out_var} FALSE PARENT_SCOPE )
114-
115- foreach (asset ${ASSETS} )
116- if (NOT EXISTS "${SRC_DIST_DIR} /${asset} " )
117- return ()
118- endif ()
119- endforeach ()
120-
121- file (MAKE_DIRECTORY "${DIST_DIR} " )
122- message (STATUS "UI: using pre-built assets from ${SRC_DIST_DIR} " )
123- foreach (asset ${ASSETS} )
124- execute_process (
125- COMMAND ${CMAKE_COMMAND } -E copy_if_different
126- "${SRC_DIST_DIR} /${asset} " "${DIST_DIR} /${asset} "
127- )
128- endforeach ()
129- set (${out_var} TRUE PARENT_SCOPE )
130- endfunction ()
131-
13254function (npm_build_should_skip out_var )
13355 set (${out_var} FALSE PARENT_SCOPE )
13456
135- assets_present (present )
57+ assets_present (" ${DIST_DIR} " present )
13658 if (NOT present)
13759 return ()
13860 endif ()
@@ -240,7 +162,7 @@ function(npm_build out_var)
240162 return ()
241163 endif ()
242164
243- assets_present (present )
165+ assets_present (" ${DIST_DIR} " present )
244166 if (NOT present)
245167 message (STATUS "UI: npm build finished but assets missing in ${DIST_DIR} " )
246168 return ()
@@ -272,7 +194,7 @@ function(hf_download version out_var out_resolved)
272194 set (${out_var} FALSE PARENT_SCOPE )
273195 set (${out_resolved} "" PARENT_SCOPE )
274196
275- file ( MAKE_DIRECTORY "${DIST_DIR} " )
197+ set (archive "${UI_BINARY_DIR } /dist.tar.gz " )
276198
277199 set (candidates "" )
278200 if (NOT "${version} " STREQUAL "" )
@@ -281,97 +203,67 @@ function(hf_download version out_var out_resolved)
281203 list (APPEND candidates "latest" )
282204
283205 foreach (resolved ${candidates} )
284- set (base "https://huggingface.co/buckets/ggml-org/${HF_BUCKET} /resolve/${resolved} " )
285-
286- message (STATUS "UI: downloading from ${resolved} : ${base} " )
287-
288- set (ok TRUE )
289- foreach (asset ${ASSETS} )
290- file (DOWNLOAD "${base} /${asset} ?download=true" "${DIST_DIR} /${asset} "
291- STATUS status TIMEOUT 60
292- )
293- list (GET status 0 rc)
294- if (NOT rc EQUAL 0)
295- list (GET status 1 errmsg)
296- message (STATUS "UI: download ${asset} from ${resolved} failed: ${errmsg} " )
297- set (ok FALSE )
298- break ()
299- endif ()
300- message (STATUS "UI: downloaded ${asset} " )
301- endforeach ()
302-
303- if (NOT ok)
206+ set (base "https://huggingface.co/buckets/${HF_BUCKET} /resolve/${resolved} " )
207+
208+ message (STATUS "UI: downloading from ${resolved} : ${base} /dist.tar.gz" )
209+
210+ file (DOWNLOAD "${base} /dist.tar.gz?download=true" "${archive} "
211+ STATUS status TIMEOUT 300
212+ )
213+ list (GET status 0 rc)
214+ if (NOT rc EQUAL 0)
215+ list (GET status 1 errmsg)
216+ message (STATUS "UI: download dist.tar.gz from ${resolved} failed: ${errmsg} " )
304217 continue ()
305218 endif ()
306219
307- # Best-effort checksum verification
308- file (DOWNLOAD "${base} /checksums.txt?download=true" "${DIST_DIR} /checksums.txt"
309- STATUS cs_status TIMEOUT 30
220+ file (DOWNLOAD "${base} /dist.tar.gz.sha256?download=true" "${archive} .sha256"
221+ STATUS status TIMEOUT 30
310222 )
311- list (GET cs_status 0 cs_rc)
312- if (cs_rc EQUAL 0)
313- message (STATUS "UI: verifying checksums" )
314- file (STRINGS "${DIST_DIR} /checksums.txt" cs_lines )
315- foreach (asset ${ASSETS} )
316- file (SHA256 "${DIST_DIR} /${asset} " h )
317- string (TOLOWER "${h} " h)
318- string (REGEX MATCH "${h} [ \t ]+${asset} " m "${cs_lines} " )
319- if (NOT m)
320- message (WARNING "UI: checksum verification failed for ${asset} " )
321- set (ok FALSE )
322- break ()
323- endif ()
324- endforeach ()
325- if (ok)
326- message (STATUS "UI: all checksums verified" )
327- endif ()
223+ list (GET status 0 rc)
224+ if (NOT rc EQUAL 0)
225+ list (GET status 1 errmsg)
226+ message (STATUS "UI: download dist.tar.gz.sha256 from ${resolved} failed: ${errmsg} " )
227+ continue ()
328228 endif ()
329229
330- if (ok)
331- set (${out_var} TRUE PARENT_SCOPE )
332- set (${out_resolved} "${resolved} " PARENT_SCOPE )
333- return ()
230+ # Validate sha256 checkums
231+ file (READ "${archive} .sha256" expected )
232+ string (REGEX MATCH "^[0-9a-fA-F]+" expected "${expected} " )
233+ string (TOLOWER "${expected} " expected)
234+ file (SHA256 "${archive} " actual )
235+ if ("${expected} " STREQUAL "" OR NOT "${actual} " STREQUAL "${expected} " )
236+ message (STATUS "UI: checksum mismatch for dist.tar.gz from ${resolved} " )
237+ continue ()
238+ endif ()
239+
240+ # Clear DIST_DIR to remove stale files first
241+ file (REMOVE_RECURSE "${DIST_DIR} " )
242+
243+ file (ARCHIVE_EXTRACT INPUT "${archive} " DESTINATION "${DIST_DIR} " )
244+
245+ assets_present ("${DIST_DIR} " present )
246+ if (NOT present)
247+ message (STATUS "UI: archive from ${resolved} is missing required assets" )
248+ continue ()
334249 endif ()
250+
251+ message (STATUS "UI: archive verified and extracted" )
252+ set (${out_var} TRUE PARENT_SCOPE )
253+ set (${out_resolved} "${resolved} " PARENT_SCOPE )
254+ return ()
335255 endforeach ()
336256endfunction ()
337257
338- function (emit_files )
339- assets_present (present )
258+ function (emit_files dist_dir )
259+ assets_present (" ${dist_dir} " present )
340260
341261 set (args "${UI_CPP} " "${UI_H} " )
342262 if (present)
343- foreach (asset ${ASSETS} )
344- list (APPEND args "${asset} " "${DIST_DIR} /${asset} " )
345- endforeach ()
346-
347- # Bundle files live in _app/immutable/ — vanilla SvelteKit output, no plugin
348- # rewriting. Embedded names must match the exact _app/ paths that index.html
349- # and sw.js reference.
350- file (GLOB_RECURSE detected_bundle_js "${DIST_DIR} /_app/immutable/bundle.*.js" )
351- file (GLOB_RECURSE detected_bundle_css "${DIST_DIR} /_app/immutable/assets/bundle.*.css" )
352- file (GLOB_RECURSE detected_workbox "${DIST_DIR} /workbox-*.js" )
353- # Compute relative path from DIST_DIR to each found file.
354- # e.g. /path/to/build/tools/ui/dist/_app/immutable/bundle.XXX.js
355- # -> _app/immutable/bundle.XXX.js
356- foreach (f ${detected_bundle_js} )
357- string (REPLACE "${DIST_DIR} /" "" rel "${f} " )
358- list (APPEND args "${rel} " "${f} " )
359- endforeach ()
360- foreach (f ${detected_bundle_css} )
361- string (REPLACE "${DIST_DIR} /" "" rel "${f} " )
362- list (APPEND args "${rel} " "${f} " )
363- endforeach ()
364- foreach (f ${detected_workbox} )
365- string (REPLACE "${DIST_DIR} /" "" rel "${f} " )
366- list (APPEND args "${rel} " "${f} " )
367- endforeach ()
263+ # llama-ui-embed embeds every top-level file in dist_dir
264+ list (APPEND args "${dist_dir} " )
368265 endif ()
369266
370- # Create build.json with the llama.cpp build number for UI version display.
371- # This is separate from SvelteKit's _app/version.json (used for SW cache invalidation).
372- # build.json is generated by the vite plugin (buildInfoPlugin) during npm build.
373- # CMake just embeds it from the dist that npm produced.
374-
375267 execute_process (
376268 COMMAND "${LLAMA_UI_EMBED} " ${args}
377269 RESULT_VARIABLE rc
@@ -384,9 +276,10 @@ endfunction()
384276# ---------------------------------------------------------------------------
385277# 1. Priority 1: pre-built assets supplied in tools/ui/dist
386278# ---------------------------------------------------------------------------
387- copy_src_dist ( SRC_OK )
279+ assets_present ( " ${SRC_DIST_DIR} " SRC_OK )
388280if (SRC_OK)
389- emit_files ()
281+ message (STATUS "UI: using pre-built assets from ${SRC_DIST_DIR} " )
282+ emit_files ("${SRC_DIST_DIR} " )
390283 return ()
391284endif ()
392285
@@ -419,7 +312,7 @@ if(NOT provisioned AND HF_ENABLED)
419312 endif ()
420313 endif ()
421314
422- assets_present (have_assets )
315+ assets_present (" ${DIST_DIR} " have_assets )
423316 if (stamp_ok AND have_assets)
424317 message (STATUS "UI: HF stamp '${stamped} ' matches version, skipping HF fetch" )
425318 set (provisioned TRUE )
@@ -439,7 +332,7 @@ endif()
439332# 4. Fallback: warn about stale or missing assets, then emit whatever we have
440333# ---------------------------------------------------------------------------
441334if (NOT provisioned)
442- assets_present (have_assets )
335+ assets_present (" ${DIST_DIR} " have_assets )
443336 if (have_assets)
444337 message (WARNING "UI: provisioning failed; embedding stale assets from ${DIST_DIR} " )
445338 else ()
@@ -451,4 +344,4 @@ if(NOT provisioned)
451344 endif ()
452345endif ()
453346
454- emit_files ()
347+ emit_files (" ${DIST_DIR} " )
0 commit comments