diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9816e42..1fbcbf9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,11 +21,13 @@ on: "**/*.c", "**/*.cpp", "**/*.cu", + "examples/server/frontend/**", ] pull_request: types: [opened, synchronize, reopened] paths: [ + ".github/workflows/**", "**/CMakeLists.txt", "**/Makefile", "**/*.h", @@ -33,6 +35,7 @@ on: "**/*.c", "**/*.cpp", "**/*.cu", + "examples/server/frontend/**", ] env: @@ -53,6 +56,16 @@ jobs: with: submodules: recursive + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Dependencies id: depends run: | @@ -106,6 +119,16 @@ jobs: with: submodules: recursive + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Dependencies id: depends run: | @@ -174,6 +197,16 @@ jobs: with: submodules: recursive + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Get commit hash id: commit if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }} @@ -223,6 +256,16 @@ jobs: with: submodules: recursive + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Dependencies id: depends run: | @@ -294,6 +337,16 @@ jobs: with: submodules: recursive + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Install cuda-toolkit id: cuda-toolkit if: ${{ matrix.build == 'cuda12' }} @@ -399,6 +452,16 @@ jobs: with: submodules: recursive + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Cache ROCm Installation id: cache-rocm uses: actions/cache@v4 @@ -502,6 +565,16 @@ jobs: with: submodules: recursive + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Free disk space run: | # Remove preinstalled SDKs and caches not needed for this job diff --git a/.gitmodules b/.gitmodules index 5a78519..5d66c87 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "ggml"] path = ggml url = https://github.com/ggml-org/ggml.git +[submodule "examples/server/frontend"] + path = examples/server/frontend + url = https://github.com/leejet/stable-ui.git diff --git a/examples/server/CMakeLists.txt b/examples/server/CMakeLists.txt index d191260..8f5beba 100644 --- a/examples/server/CMakeLists.txt +++ b/examples/server/CMakeLists.txt @@ -1,6 +1,73 @@ set(TARGET sd-server) +option(SD_SERVER_BUILD_FRONTEND "Build server frontend with pnpm" ON) + +set(FRONTEND_DIR "${CMAKE_CURRENT_SOURCE_DIR}/frontend") +set(GENERATED_HTML_HEADER "${FRONTEND_DIR}/dist/gen_index_html.h") + +set(HAVE_FRONTEND_BUILD OFF) + +if(SD_SERVER_BUILD_FRONTEND AND EXISTS "${FRONTEND_DIR}") + if(WIN32) + find_program(PNPM_EXECUTABLE NAMES pnpm.cmd pnpm) + else() + find_program(PNPM_EXECUTABLE NAMES pnpm) + endif() + + if(PNPM_EXECUTABLE) + message(STATUS "Frontend dir found: ${FRONTEND_DIR}") + message(STATUS "pnpm found: ${PNPM_EXECUTABLE}") + + set(HAVE_FRONTEND_BUILD ON) + + add_custom_target(${TARGET}_frontend_install + COMMAND "${PNPM_EXECUTABLE}" -C "${FRONTEND_DIR}" install + WORKING_DIRECTORY "${FRONTEND_DIR}" + COMMENT "Installing frontend dependencies" + VERBATIM + ) + + add_custom_target(${TARGET}_frontend_build + COMMAND "${PNPM_EXECUTABLE}" -C "${FRONTEND_DIR}" run build + WORKING_DIRECTORY "${FRONTEND_DIR}" + COMMENT "Building frontend" + VERBATIM + ) + + add_custom_target(${TARGET}_frontend_header + COMMAND "${PNPM_EXECUTABLE}" -C "${FRONTEND_DIR}" run build:header + WORKING_DIRECTORY "${FRONTEND_DIR}" + COMMENT "Generating gen_index_html.h" + VERBATIM + ) + + add_dependencies(${TARGET}_frontend_build ${TARGET}_frontend_install) + add_dependencies(${TARGET}_frontend_header ${TARGET}_frontend_build) + + add_custom_target(${TARGET}_frontend + DEPENDS ${TARGET}_frontend_header + ) + + set_source_files_properties("${GENERATED_HTML_HEADER}" PROPERTIES GENERATED TRUE) + else() + message(WARNING "pnpm not found, frontend build disabled") + endif() +else() + message(STATUS "Frontend disabled or directory not found: ${FRONTEND_DIR}") +endif() + add_executable(${TARGET} main.cpp) + +if(HAVE_FRONTEND_BUILD) + add_dependencies(${TARGET} ${TARGET}_frontend) + target_sources(${TARGET} PRIVATE "${GENERATED_HTML_HEADER}") + target_include_directories(${TARGET} PRIVATE "${FRONTEND_DIR}/dist") + target_compile_definitions(${TARGET} PRIVATE HAVE_INDEX_HTML) + message(STATUS "HAVE_INDEX_HTML enabled") +else() + message(STATUS "HAVE_INDEX_HTML disabled") +endif() + install(TARGETS ${TARGET} RUNTIME) target_link_libraries(${TARGET} PRIVATE stable-diffusion ${CMAKE_THREAD_LIBS_INIT}) target_compile_features(${TARGET} PUBLIC c_std_11 cxx_std_17) \ No newline at end of file diff --git a/examples/server/README.md b/examples/server/README.md index 38deff6..8aa2158 100644 --- a/examples/server/README.md +++ b/examples/server/README.md @@ -1,3 +1,92 @@ +# Frontend + +## Build with Frontend + +The server can optionally build the web frontend and embed it into the binary as `gen_index_html.h`. + +### Requirements + +Install the following tools: + +* **Node.js** ≥ 22.18 + https://nodejs.org/ + +* **pnpm** ≥ 10 + Install via npm: + +```bash +npm install -g pnpm +``` + +Verify installation: + +```bash +node -v +pnpm -v +``` + +### Install frontend dependencies + +Go to the frontend directory and install dependencies: + +```bash +cd examples/server/frontend +pnpm install +``` + +### Build the server with CMake + +Enable the frontend build option when configuring CMake: + +```bash +cmake -B build -DSD_SERVER_BUILD_FRONTEND=ON +cmake --build build --config Release +``` + +If `pnpm` is available, the build system will automatically run: + +``` +pnpm run build +pnpm run build:header +``` + +and embed the generated frontend into the server binary. + +## Frontend Repository + +The web frontend is maintained in a **separate repository**, https://github.com/leejet/stable-ui. + +If you want to modify the UI or frontend logic, please submit pull requests to the **frontend repository**. + +This repository (`stable-diffusion.cpp`) only vendors the frontend periodically. Changes from the frontend repo are synchronized: + +* approximately **every 1–2 weeks**, or +* when there are **major frontend updates** + +Because of this, frontend changes will **not appear here immediately** after being merged upstream. + +## Using an external frontend + +By default, the server uses the **embedded frontend** generated during the build (`gen_index_html.h`). + +You can also serve a custom frontend file instead of the embedded one by using: + +```bash +--serve-html-path +``` + +For example: + +```bash +sd-server --serve-html-path ./index.html +``` + +In this case, the server will load and serve the specified `index.html` file instead of the embedded frontend. This is useful when: + +* developing or testing frontend changes +* using a custom UI +* avoiding rebuilding the binary after frontend modifications + # Run ``` diff --git a/examples/server/frontend b/examples/server/frontend new file mode 160000 index 0000000..1a34176 --- /dev/null +++ b/examples/server/frontend @@ -0,0 +1 @@ +Subproject commit 1a34176cd6d39ad3a226b2b69047e71f6797f6bc diff --git a/examples/server/main.cpp b/examples/server/main.cpp index cc9e66c..6e4340a 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -13,6 +13,10 @@ #include "common/common.hpp" +#ifdef HAVE_INDEX_HTML +#include "frontend/dist/gen_index_html.h" +#endif + namespace fs = std::filesystem; // ----------------------- helpers ----------------------- @@ -380,7 +384,13 @@ int main(int argc, const char** argv) { return httplib::Server::HandlerResponse::Unhandled; }); - // root + // index html + std::string index_html; +#ifdef HAVE_INDEX_HTML + index_html.assign(reinterpret_cast(index_html_bytes), index_html_size); +#else + index_html = "Stable Diffusion Server is running"; +#endif svr.Get("/", [&](const httplib::Request&, httplib::Response& res) { if (!svr_params.serve_html_path.empty()) { std::ifstream file(svr_params.serve_html_path); @@ -392,7 +402,7 @@ int main(int argc, const char** argv) { res.set_content("Error: Unable to read HTML file", "text/plain"); } } else { - res.set_content("Stable Diffusion Server is running", "text/plain"); + res.set_content(index_html, "text/html"); } });