char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
3057bb66c
char/aliceai-t5-moe
1# Provision UI assets and generate ui.cpp/ui.h. 2# 3# Asset provisioning priority: 4# 1. Pre-built assets in SRC_DIST_DIR (manually built by user) 5# 2. If BUILD_UI=ON: npm build 6# 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) 8 9cmake_minimum_required ( VERSION 3.18 ) 10 11set ( UI_SOURCE_DIR "" CACHE STRING "UI source directory (to run npm build)" ) 12set ( UI_BINARY_DIR "" CACHE STRING "UI binary directory (to store generated files)" ) 13set ( LLAMA_SOURCE_DIR "" CACHE STRING "Project source root (to resolve version from git)" ) 14set ( HF_BUCKET "" CACHE STRING "Hugging Face bucket name" ) 15set ( HF_VERSION "" CACHE STRING "Version to download (empty = resolve from git)" ) 16set ( HF_ENABLED "" CACHE STRING "Whether to allow HF Bucket download (ON/OFF)" ) 17set ( BUILD_UI "" CACHE STRING "Build UI via npm (ON/OFF)" ) 18set ( LLAMA_UI_GZIP "" CACHE STRING "Apply gzip compress to assets to save bandwidth" ) 19 20set ( DIST_DIR " ${ UI_BINARY_DIR } /dist" ) 21set ( SRC_DIST_DIR " ${ UI_SOURCE_DIR } /dist" ) 22set ( WORK_DIR " ${ UI_BINARY_DIR } /ui-src" ) 23set ( STAMP_FILE " ${ UI_BINARY_DIR } /.ui-stamp" ) 24set ( EMBED_STAMP " ${ UI_BINARY_DIR } /.ui-embed.sha256" ) 25set ( UI_CPP " ${ UI_BINARY_DIR } /ui.cpp" ) 26set ( UI_H " ${ UI_BINARY_DIR } /ui.h" ) 27 28function ( mime_from_ext name out_var ) 29string ( FIND " ${ name } " "." ext REVERSE ) 30if ( extGREATER -1) 31string ( SUBSTRING " ${ name } " ${ ext } -1 ext_full ) 32string ( SUBSTRING " ${ ext_full } " 1 -1 ext_str ) 33else () 34set ( ext_str "" ) 35endif () 36if ( ext_strSTREQUAL "html" ) 37set ( m "text/html; charset=utf-8" ) 38elseif ( ext_strSTREQUAL "css" ) 39set ( m "text/css" ) 40elseif ( ext_strSTREQUAL "js" ) 41set ( m "application/javascript" ) 42elseif ( ext_strSTREQUAL "json" ) 43set ( m "application/json" ) 44elseif ( ext_strSTREQUAL "webmanifest" ) 45set ( m "application/manifest+json" ) 46elseif ( ext_strSTREQUAL "svg" ) 47set ( m "image/svg+xml" ) 48elseif ( ext_strSTREQUAL "png" ) 49set ( m "image/png" ) 50elseif ( ext_strSTREQUAL "jpg" OR ext_strSTREQUAL "jpeg" ) 51set ( m "image/jpeg" ) 52elseif ( ext_strSTREQUAL "ico" ) 53set ( m "image/x-icon" ) 54elseif ( ext_strSTREQUAL "woff" ) 55set ( m "font/woff" ) 56elseif ( ext_strSTREQUAL "woff2" ) 57set ( m "font/woff2" ) 58else () 59set ( m "application/octet-stream" ) 60endif () 61set ( $ { out_var } " ${ m } " PARENT_SCOPE ) 62endfunction () 63 64# Fail when a dist tree is present but is missing files the UI needs at 65# runtime; catches truncated/stale asset trees early with a useful message. 66function ( ui_validate_assets files in_dir ) 67list ( LENGTH files n_assets ) 68if ( n_assetsEQUAL 0 ) 69return () 70endif () 71 72set ( found_index FALSE ) 73set ( found_manifest FALSE ) 74set ( found_sw FALSE ) 75set ( found_build_json FALSE ) 76set ( found_version_json FALSE ) 77set ( found_bundle_js FALSE ) 78set ( found_bundle_css FALSE ) 79set ( found_workbox_js FALSE ) 80 81foreach ( f${ files }) 82get_filename_component ( base " ${ f } " NAME ) 83if ( baseSTREQUAL "index.html" ) 84set ( found_index TRUE ) 85elseif ( baseSTREQUAL "manifest.webmanifest" ) 86set ( found_manifest TRUE ) 87elseif ( baseSTREQUAL "sw.js" ) 88set ( found_sw TRUE ) 89elseif ( baseSTREQUAL "build.json" ) 90set ( found_build_json TRUE ) 91elseif ( baseSTREQUAL "version.json" ) 92set ( found_version_json TRUE ) 93elseif ( baseMATCHES "^bundle.*\\.js $ " ) 94set ( found_bundle_js TRUE ) 95elseif ( baseMATCHES "^bundle.*\\.css $ " ) 96set ( found_bundle_css TRUE ) 97elseif ( baseMATCHES "^workbox.*\\.js $ " ) 98set ( found_workbox_js TRUE ) 99endif () 100endforeach () 101 102set ( missing "" ) 103if ( NOT found_index) 104list ( APPEND missing "index.html" ) 105endif () 106if ( NOT found_manifest) 107list ( APPEND missing "manifest.webmanifest" ) 108endif () 109if ( NOT found_sw) 110list ( APPEND missing "sw.js" ) 111endif () 112if ( NOT found_build_json) 113list ( APPEND missing "build.json" ) 114endif () 115if ( NOT found_version_json) 116list ( APPEND missing "version.json" ) 117endif () 118if ( NOT found_bundle_js) 119list ( APPEND missing "bundle[hash].js" ) 120endif () 121if ( NOT found_bundle_css) 122list ( APPEND missing "bundle[hash].css" ) 123endif () 124if ( NOT found_workbox_js) 125list ( APPEND missing "workbox[hash].js" ) 126endif () 127 128if ( missing) 129set ( listing "" ) 130foreach ( f${ files }) 131string ( APPEND listing " ${ f } \n" ) 132endforeach () 133set ( missing_list "" ) 134foreach ( m${ missing }) 135string ( APPEND missing_list " ${ m } \n" ) 136endforeach () 137message ( FATAL_ERROR 138"UI: current asset files:\n ${ listing } " 139"UI: missing required asset(s):\n ${ missing_list } " 140"UI: hint: try cleaning your build directory: ${ in_dir } " ) 141endif () 142endfunction () 143 144# Generate ui.cpp/ui.h embedding every file of ${dist_dir} (empty table when 145# it has no index.html), gzip-compressed when LLAMA_UI_GZIP is enabled. 146function ( emit_files dist_dir ) 147set ( UI_TEMPLATE_DIR " ${ LLAMA_SOURCE_DIR } /tools/ui" ) 148 149# Collect the asset list once and reuse it for the fingerprint, 150# validation, compression and embedding. 151set ( assets "" ) 152if ( EXISTS " ${ dist_dir } /index.html" ) 153file ( GLOB_RECURSE assets 154LIST_DIRECTORIES false 155RELATIVE " ${ dist_dir } " 156" ${ dist_dir } /*" ) 157list ( FILTER assets EXCLUDE REGEX "^_gzip/" ) 158list ( SORT assets ) 159endif () 160 161if ( LLAMA_UI_GZIPAND NOT DEFINED ENV{SOURCE_DATE_EPOCH}) 162# Zero the gzip header timestamp so identical inputs give identical 163# bytes (and therefore stable ETags) on every machine. 164set ( ENV{SOURCE_DATE_EPOCH} 0 ) 165endif () 166 167# Fingerprint of every input that determines ui.cpp/ui.h: compression 168# settings, the asset tree (names + SHA-256) and this script + templates. 169set ( fp " ${ LLAMA_UI_GZIP } | $ ENV { SOURCE_DATE_EPOCH } | ${ CMAKE_VERSION } \n" ) 170foreach ( f${ assets }) 171file ( SHA256 " ${ dist_dir } / ${ f } " h ) 172string ( APPEND fp " ${ f } ${ h } \n" ) 173endforeach () 174foreach ( g 175" ${ CMAKE_CURRENT_FUNCTION_LIST_FILE } " 176" ${ UI_TEMPLATE_DIR } /ui.h.in" 177" ${ UI_TEMPLATE_DIR } /ui.cpp.in" ) 178file ( SHA256 " ${ g } " h ) 179string ( APPEND fp "gen ${ h } \n" ) 180endforeach () 181string ( SHA256 fingerprint " ${ fp } " ) 182 183if ( EXISTS " ${ EMBED_STAMP } " AND EXISTS " ${ UI_CPP } " AND EXISTS " ${ UI_H } " ) 184file ( READ " ${ EMBED_STAMP } " fp_saved ) 185string ( STRIP " ${ fp_saved } " fp_saved ) 186if ( fp_savedSTREQUAL " ${ fingerprint } " ) 187message ( STATUS "UI: assets unchanged, skipping embedding" ) 188return () 189endif () 190endif () 191 192# Drop the old stamp up front so a crash mid-generation cannot leave 193# outputs and stamp out of sync. 194file ( REMOVE " ${ EMBED_STAMP } " ) 195 196ui_validate_assets ( " ${ assets } " " ${ dist_dir } " ) 197 198set ( embed_dir " ${ dist_dir } " ) 199set ( use_gzip FALSE ) 200 201if ( EXISTS " ${ dist_dir } /index.html" ) 202if ( EXISTS " ${ dist_dir } /_gzip" ) 203# a _gzip tree inside dist_dir can only be a leftover from an 204# older version of this script that staged it there 205file ( REMOVE_RECURSE " ${ dist_dir } /_gzip" ) 206message ( STATUS "UI: removed stale gzip tree ${ dist_dir } /_gzip" ) 207endif () 208if ( LLAMA_UI_GZIP) 209# Compress every asset into a parallel _gzip/ tree under the build 210# directory, served with Content-Encoding: gzip. 211set ( gzip_root " ${ UI_BINARY_DIR } /ui-gzip" ) 212set ( gzip_dir " ${ gzip_root } /_gzip" ) 213file ( REMOVE_RECURSE " ${ gzip_root } " ) 214foreach ( f IN LISTS assets) 215get_filename_component ( asset_path " ${ dist_dir } / ${ f } " REALPATH ) 216get_filename_component ( dst_dir " ${ gzip_dir } / ${ f } " DIRECTORY ) 217file ( MAKE_DIRECTORY " ${ dst_dir } " ) 218file ( ARCHIVE_CREATE 219OUTPUT " ${ gzip_dir } / ${ f } " 220PATHS " ${ asset_path } " 221FORMAT raw 222COMPRESSION GZip 223) 224endforeach () 225message ( STATUS "UI: gzip compression applied ( ${ gzip_dir } )" ) 226set ( embed_dir " ${ gzip_dir } " ) 227set ( use_gzip TRUE ) 228endif () 229endif () 230 231list ( LENGTH assets n_assets ) 232 233# Per-asset arrays and table rows go into the ui.h.in / ui.cpp.in templates; 234# configure_file only rewrites on content change, avoiding needless recompiles. 235set ( ASSET_ARRAYS "" ) 236set ( ASSET_TABLE "" ) 237set ( idx 0 ) 238 239foreach ( f IN LISTS assets) 240file ( READ " ${ embed_dir } / ${ f } " hex HEX ) 241if ( hexSTREQUAL "" ) 242message ( FATAL_ERROR "UI: empty file: ${ embed_dir } / ${ f } " ) 243endif () 244 245string ( REGEX REPLACE "(..)" "0x\\1," bytes " ${ hex } " ) 246file ( SHA256 " ${ embed_dir } / ${ f } " etag ) 247mime_from_ext ( " ${ f } " mime ) 248 249string ( APPEND ASSET_ARRAYS 250"static const unsigned char asset_ ${ idx } [] = { ${ bytes } };\n" ) 251 252string ( APPEND ASSET_TABLE 253" { \" ${ f } \", asset_ ${ idx } , sizeof(asset_ ${ idx } ), \"\\\" ${ etag } \\\"\", \" ${ mime } \" },\n" ) 254 255math ( EXPR idx " ${ idx } + 1" ) 256endforeach () 257 258set ( LLAMA_UI_HAS_ASSETS 0 ) 259if ( n_assetsGREATER 0 ) 260set ( LLAMA_UI_HAS_ASSETS 1 ) 261endif () 262set ( N_ASSETS " ${ n_assets } " ) 263set ( USE_GZIP false ) 264if ( use_gzip) 265set ( USE_GZIP true ) 266endif () 267 268configure_file ( " ${ UI_TEMPLATE_DIR } /ui.h.in" " ${ UI_H } " @ONLY ) 269configure_file ( " ${ UI_TEMPLATE_DIR } /ui.cpp.in" " ${ UI_CPP } " @ONLY ) 270 271# Write the embed stamp last, after both generated files succeeded. 272file ( WRITE " ${ EMBED_STAMP } " " ${ fingerprint } " ) 273message ( STATUS "UI: embedded ${ n_assets } assets" ) 274endfunction () 275 276function ( npm_build_should_skip out_var ) 277set ( $ { out_var } FALSE PARENT_SCOPE ) 278 279if ( NOT EXISTS " ${ DIST_DIR } /index.html" ) 280return () 281endif () 282 283if ( EXISTS " ${ STAMP_FILE } " ) 284return () 285endif () 286 287if ( NOT EXISTS " ${ UI_SOURCE_DIR } /sources.cmake" ) 288return () 289endif () 290include ( " ${ UI_SOURCE_DIR } /sources.cmake" ) 291 292set ( globs "" ) 293foreach ( g${ UI_SOURCE_GLOBS }) 294list ( APPEND globs " ${ UI_SOURCE_DIR } / ${ g } " ) 295endforeach () 296file ( GLOB_RECURSE sources ${ globs }) 297foreach ( f${ UI_SOURCE_FILES }) 298list ( APPEND sources " ${ UI_SOURCE_DIR } / ${ f } " ) 299endforeach () 300 301file ( TIMESTAMP " ${ DIST_DIR } /index.html" out_ts ) 302 303foreach ( s${ sources }) 304if ( NOT EXISTS " ${ s } " ) 305continue () 306endif () 307file ( TIMESTAMP " ${ s } " s_ts ) 308if ( s_tsSTRGREATER out_ts) 309return () 310endif () 311endforeach () 312 313set ( $ { out_var } TRUE PARENT_SCOPE ) 314endfunction () 315 316function ( stage_sources ) 317if ( EXISTS " ${ WORK_DIR } " ) 318file ( GLOB staged RELATIVE " ${ WORK_DIR } " " ${ WORK_DIR } /*" ) 319list ( REMOVE_ITEM staged "node_modules" ) 320foreach ( entry${ staged }) 321file ( REMOVE_RECURSE " ${ WORK_DIR } / ${ entry } " ) 322endforeach () 323endif () 324 325file ( COPY " ${ UI_SOURCE_DIR } /" 326DESTINATION " ${ WORK_DIR } " 327NO_SOURCE_PERMISSIONS 328PATTERN "node_modules" EXCLUDE 329) 330endfunction () 331 332function ( npm_build out_var ) 333set ( $ { out_var } FALSE PARENT_SCOPE ) 334 335if ( NOT EXISTS " ${ UI_SOURCE_DIR } /package.json" ) 336message ( STATUS "UI: ${ UI_SOURCE_DIR } /package.json not found, skipping npm" ) 337return () 338endif () 339 340npm_build_should_skip ( skip ) 341if ( skip) 342message ( STATUS "UI: npm output up-to-date, skipping build" ) 343set ( $ { out_var } TRUE PARENT_SCOPE ) 344return () 345endif () 346 347if ( CMAKE_HOST_WIN32) 348find_program ( NPM_EXECUTABLE NAMES npm.cmd npm.bat npm ) 349else () 350find_program ( NPM_EXECUTABLE npm ) 351endif () 352if ( NOT NPM_EXECUTABLE) 353message ( STATUS "UI: npm not found, skipping npm build" ) 354return () 355endif () 356 357stage_sources () 358 359# npm writes node_modules/.package-lock.json on every successful install, 360# so a package-lock.json newer than this marker means node_modules is stale 361set ( NPM_MARKER " ${ WORK_DIR } /node_modules/.package-lock.json" ) 362set ( need_install FALSE ) 363if ( NOT EXISTS " ${ NPM_MARKER } " ) 364set ( need_install TRUE ) 365else () 366file ( TIMESTAMP " ${ WORK_DIR } /package-lock.json" lock_ts ) 367file ( TIMESTAMP " ${ NPM_MARKER } " marker_ts ) 368if ( lock_tsSTRGREATER marker_ts) 369set ( need_install TRUE ) 370endif () 371endif () 372 373if ( need_install) 374message ( STATUS "UI: running npm ci" ) 375execute_process ( 376COMMAND ${ NPM_EXECUTABLE } ci 377WORKING_DIRECTORY " ${ WORK_DIR } " 378RESULT_VARIABLE rc 379ERROR_VARIABLE err 380) 381if ( NOT rcEQUAL 0 ) 382message ( STATUS "UI: npm ci failed ( ${ rc } )" ) 383message ( STATUS " stderr: ${ err } " ) 384return () 385endif () 386endif () 387 388file ( MAKE_DIRECTORY " ${ DIST_DIR } " ) 389 390message ( STATUS "UI: running npm run build, output -> ${ DIST_DIR } " ) 391execute_process ( 392COMMAND ${ CMAKE_COMMAND } -E env "LLAMA_UI_OUT_DIR= ${ DIST_DIR } " "LLAMA_UI_VERSION= ${ HF_VERSION } " "LLAMA_BUILD_NUMBER= ${ LLAMA_BUILD_NUMBER } " 393${ NPM_EXECUTABLE } run build 394WORKING_DIRECTORY " ${ WORK_DIR } " 395RESULT_VARIABLE rc 396ERROR_VARIABLE err 397) 398if ( NOT rcEQUAL 0 ) 399message ( STATUS "UI: npm run build failed ( ${ rc } )" ) 400message ( STATUS " stderr: ${ err } " ) 401return () 402endif () 403 404if ( NOT EXISTS " ${ DIST_DIR } /index.html" ) 405message ( STATUS "UI: npm build finished but assets missing in ${ DIST_DIR } " ) 406return () 407endif () 408 409message ( STATUS "UI: npm build succeeded" ) 410file ( REMOVE " ${ STAMP_FILE } " ) 411set ( $ { out_var } TRUE PARENT_SCOPE ) 412endfunction () 413 414function ( resolve_version out_var ) 415if ( NOT " ${ HF_VERSION } " STREQUAL "" ) 416set ( $ { out_var } " ${ HF_VERSION } " PARENT_SCOPE ) 417return () 418endif () 419 420if ( EXISTS " ${ LLAMA_SOURCE_DIR } /cmake/build-info.cmake" ) 421include ( " ${ LLAMA_SOURCE_DIR } /cmake/build-info.cmake" ) 422if ( NOT " ${ BUILD_NUMBER } " STREQUAL "" AND NOT BUILD_NUMBEREQUAL 0 ) 423set ( $ { out_var } "b ${ BUILD_NUMBER } " PARENT_SCOPE ) 424return () 425endif () 426endif () 427 428set ( $ { out_var } "" PARENT_SCOPE ) 429endfunction () 430 431function ( hf_download version out_var out_resolved ) 432set ( $ { out_var } FALSE PARENT_SCOPE ) 433set ( $ { out_resolved } "" PARENT_SCOPE ) 434 435set ( archive " ${ UI_BINARY_DIR } /dist.tar.gz" ) 436 437# Use HF_TOKEN to benefit from higher rate limits 438set ( auth_headers "" ) 439if ( DEFINED ENV{HF_TOKEN}AND NOT " $ ENV { HF_TOKEN } " STREQUAL "" ) 440list ( APPEND auth_headers "HTTPHEADER" "Authorization: Bearer $ ENV { HF_TOKEN } " ) 441endif () 442 443set ( candidates "" ) 444if ( NOT " ${ version } " STREQUAL "" ) 445list ( APPEND candidates " ${ version } " ) 446endif () 447list ( APPEND candidates "latest" ) 448 449foreach ( resolved${ candidates }) 450set ( base "https://huggingface.co/buckets/ ${ HF_BUCKET } /resolve/ ${ resolved } " ) 451 452message ( STATUS "UI: downloading from ${ resolved } : ${ base } /dist.tar.gz" ) 453 454# Fetch the checksum first: when the archive we already have matches 455# it, the expensive download is skipped and only extraction repeats. 456file ( DOWNLOAD " ${ base } /dist.tar.gz.sha256?download=true" " ${ archive } .sha256" 457STATUS status TIMEOUT 30 ${ auth_headers } 458) 459list ( GET status 0 rc ) 460if ( NOT rcEQUAL 0 ) 461list ( GET status 1 errmsg ) 462message ( STATUS "UI: download dist.tar.gz.sha256 from ${ resolved } failed: ${ errmsg } " ) 463continue () 464endif () 465 466# Validate the sha256 checksum: reject anything that is not a full 467# 64-hex-digit digest before touching the archive. 468file ( READ " ${ archive } .sha256" expected ) 469string ( REGEX MATCH "^[0-9a-fA-F]+" expected " ${ expected } " ) 470string ( TOLOWER " ${ expected } " expected ) 471string ( LENGTH " ${ expected } " expected_len ) 472if ( NOT expected_lenEQUAL 64) 473message ( STATUS "UI: invalid checksum from ${ resolved } " ) 474continue () 475endif () 476 477set ( actual "" ) 478if ( EXISTS " ${ archive } " ) 479file ( SHA256 " ${ archive } " actual ) 480endif () 481 482if ( " ${ actual } " STREQUAL " ${ expected } " ) 483message ( STATUS "UI: local dist.tar.gz matches checksum from ${ resolved } , skipping download" ) 484else () 485file ( DOWNLOAD " ${ base } /dist.tar.gz?download=true" " ${ archive } " 486STATUS status TIMEOUT 300 ${ auth_headers } 487) 488list ( GET status 0 rc ) 489if ( NOT rcEQUAL 0 ) 490list ( GET status 1 errmsg ) 491message ( STATUS "UI: download dist.tar.gz from ${ resolved } failed: ${ errmsg } " ) 492continue () 493endif () 494 495file ( SHA256 " ${ archive } " actual ) 496if ( NOT " ${ actual } " STREQUAL " ${ expected } " ) 497message ( STATUS "UI: checksum mismatch for dist.tar.gz from ${ resolved } " ) 498continue () 499endif () 500endif () 501 502# Remove the stamp with the dist tree it describes, together. 503file ( REMOVE " ${ STAMP_FILE } " ) 504file ( REMOVE_RECURSE " ${ DIST_DIR } " ) 505 506file ( ARCHIVE_EXTRACT INPUT " ${ archive } " DESTINATION " ${ DIST_DIR } " ) 507 508if ( NOT EXISTS " ${ DIST_DIR } /index.html" ) 509message ( STATUS "UI: archive from ${ resolved } is missing required assets" ) 510continue () 511endif () 512 513message ( STATUS "UI: archive verified and extracted" ) 514set ( $ { out_var } TRUE PARENT_SCOPE ) 515set ( $ { out_resolved } " ${ resolved } " PARENT_SCOPE ) 516return () 517endforeach () 518endfunction () 519 520# --------------------------------------------------------------------------- 521# 1. Priority 1: pre-built assets supplied in tools/ui/dist 522# --------------------------------------------------------------------------- 523if ( EXISTS " ${ SRC_DIST_DIR } /index.html" ) 524message ( STATUS "UI: using pre-built assets from ${ SRC_DIST_DIR } " ) 525emit_files ( " ${ SRC_DIST_DIR } " ) 526return () 527endif () 528 529# --------------------------------------------------------------------------- 530# 2. Priority 2: npm build (if BUILD_UI=ON) 531# --------------------------------------------------------------------------- 532set ( provisioned FALSE ) 533 534if ( BUILD_UI) 535# Resolve version from git build-info if not explicitly set 536resolve_version ( HF_VERSION ) 537npm_build ( NPM_OK ) 538if ( NPM_OK) 539set ( provisioned TRUE ) 540endif () 541endif () 542 543# --------------------------------------------------------------------------- 544# 3. Priority 3: HF Bucket download (if npm did not produce assets and HF_ENABLED=ON) 545# --------------------------------------------------------------------------- 546if ( NOT provisionedAND HF_ENABLED) 547resolve_version ( VERSION ) 548 549# Stamp a successful HF download: records bucket + requested version and 550# lets later steps distinguish downloaded assets from locally built ones. 551set ( stamp_key " ${ HF_BUCKET } | ${ VERSION } " ) 552 553set ( stamp_ok FALSE ) 554if ( EXISTS " ${ STAMP_FILE } " AND EXISTS " ${ DIST_DIR } /index.html" AND NOT " ${ VERSION } " STREQUAL "" ) 555file ( READ " ${ STAMP_FILE } " stamped ) 556string ( STRIP " ${ stamped } " stamped ) 557if ( stampedSTREQUAL " ${ stamp_key } " ) 558set ( stamp_ok TRUE ) 559endif () 560endif () 561 562if ( stamp_ok) 563message ( STATUS "UI: HF stamp matches ' ${ stamp_key } ', skipping HF fetch" ) 564set ( provisioned TRUE ) 565else () 566hf_download ( " ${ VERSION } " HF_OK HF_RESOLVED ) 567if ( HF_OK) 568file ( WRITE " ${ STAMP_FILE } " " ${ stamp_key } " ) 569message ( STATUS "UI: HF download succeeded, stamp updated ( ${ stamp_key } , resolved: ${ HF_RESOLVED } )" ) 570set ( provisioned TRUE ) 571else () 572message ( STATUS "UI: HF download failed" ) 573endif () 574endif () 575endif () 576 577# --------------------------------------------------------------------------- 578# 4. Fallback: warn about stale or missing assets, then emit whatever we have 579# --------------------------------------------------------------------------- 580if ( NOT provisioned) 581if ( EXISTS " ${ DIST_DIR } /index.html" ) 582message ( WARNING "UI: provisioning failed; embedding stale assets from ${ DIST_DIR } " ) 583else () 584message ( WARNING "UI: no assets available - building without an embedded UI. " 585"In a disconnected environment, download the pre-built UI " 586"from a llama.cpp release at " 587"https://github.com/ggml-org/llama.cpp/releases and " 588"extract to tools/ui/dist." ) 589endif () 590endif () 591 592emit_files ( " ${ DIST_DIR } " )