Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion app/server/server_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "ui_assets.h"
#if defined(AUDIOCPP_HAS_NATIVE_MODEL_MANAGER)
#include "model_installer.h"
#include "engine/framework/runtime/registry.h"
#endif

#include "engine/framework/io/json.h"
Expand Down Expand Up @@ -239,7 +240,41 @@ HttpResponse ui_model_install_status_response(
}

HttpResponse ui_model_package_sizes_response(ModelInstaller &installer) {
return json_response(installer.package_sizes());
namespace json = engine::io::json;
auto inventory = json::parse(installer.package_sizes()).as_object();
const auto registry = engine::runtime::make_default_registry();
auto rows = inventory.at("data").as_array();
for (auto & value : rows) {
auto row = value.as_object();
const auto & metadata = row.at("package");
engine::assets::ModelPackage package;
package.root = json::require_string(metadata, "root");
package.format = json::require_string(metadata, "format");
for (const auto & file : metadata.require("files").as_array()) {
package.files.emplace_back(file.as_string());
}
try {
const auto target = registry.resolve_package_load(json::require_string(metadata, "family"), package);
json::Value::Object load_options, session_options;
for (const auto & option : target.load_options) {
load_options[option.first] = json::Value::make_string(option.second);
}
for (const auto & option : target.session_options) {
session_options[option.first] = json::Value::make_string(option.second);
}
json::Value::Object resolved;
resolved["family"] = json::Value::make_string(target.family);
resolved["model_path"] = json::Value::make_string(target.model_path.generic_string());
resolved["load_options"] = json::Value::make_object(std::move(load_options));
resolved["session_options"] = json::Value::make_object(std::move(session_options));
row["load_target"] = json::Value::make_object(std::move(resolved));
} catch (const std::exception & error) {
row["load_target_error"] = json::Value::make_string(error.what());
}
value = json::Value::make_object(std::move(row));
}
inventory["data"] = json::Value::make_array(std::move(rows));
return json_response(json::stringify(json::Value::make_object(std::move(inventory))));
}

HttpResponse ui_models_root_response(
Expand Down
16 changes: 16 additions & 0 deletions include/engine/framework/assets/model_package.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <filesystem>
#include <string>
#include <vector>

namespace engine::assets {

struct ModelPackage {
std::filesystem::path root;
// Installed paths relative to root, after applying the download's strip_prefix.
std::vector<std::filesystem::path> files;
std::string format;
};

} // namespace engine::assets
13 changes: 13 additions & 0 deletions include/engine/framework/package_manager/manager.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#pragma once

#include "engine/framework/assets/model_package.h"

#include <atomic>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <string>

namespace engine::package_manager {
Expand All @@ -15,6 +18,12 @@ struct PackageProgress {
std::string message;
};

struct PackageInfo {
std::string family;
assets::ModelPackage package;
bool installed = false;
};

class PackageManager {
public:
PackageManager(std::filesystem::path repository_root, std::filesystem::path models_root);
Expand All @@ -31,6 +40,10 @@ class PackageManager {
std::string clean_partial(const std::string & package_id);
std::string remove(const std::string & package_id);

// Raw installed-package metadata and local presence; no model loading policy
// or remote requests. Unknown package IDs return nullopt.
[[nodiscard]] std::optional<PackageInfo> package_info(const std::string & package_id) const;

// JSON inventory consumed by the native WebUI for local presence, remote
// sizes, and per-file version state.
std::string inventory(bool query_remote, const std::string & package_id = {}) const;
Expand Down
12 changes: 12 additions & 0 deletions include/engine/framework/runtime/model.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "engine/framework/runtime/session.h"
#include "engine/framework/assets/model_package.h"

#include <cstddef>
#include <filesystem>
Expand Down Expand Up @@ -76,6 +77,13 @@ struct ModelLoadRequest {
std::unordered_map<std::string, std::string> options;
};

struct PackageLoadTarget {
std::string family;
std::filesystem::path model_path;
std::unordered_map<std::string, std::string> load_options;
std::unordered_map<std::string, std::string> session_options;
};

std::vector<NamedAsset> discover_named_assets(
const std::filesystem::path & root,
const std::vector<std::string> & relative_candidates);
Expand Down Expand Up @@ -169,6 +177,10 @@ class IVoiceModelLoader {
(void)session_options;
return {};
}

// Translate selected package files into existing loading inputs. No weights
// are loaded here; path-based loading does not call this method.
virtual PackageLoadTarget resolve_package_load(const assets::ModelPackage & package) const;
};

} // namespace engine::runtime
2 changes: 2 additions & 0 deletions include/engine/framework/runtime/registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class ModelRegistry {
ModelInspection inspect(const std::filesystem::path & model_path) const;
std::unique_ptr<ILoadedVoiceModel> load(const ModelLoadRequest & request) const;
std::unique_ptr<ILoadedVoiceModel> load(const std::filesystem::path & model_path) const;
PackageLoadTarget resolve_package_load(
const std::string & family, const assets::ModelPackage & package) const;

/**
* What the named family's session options ask for beyond weight files, from the loader that
Expand Down
5 changes: 5 additions & 0 deletions include/engine/framework/runtime/spec_backed_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ struct SpecBackedVoiceModelConfig {
std::function<ModelResourcePlan(
const std::filesystem::path &,
const std::unordered_map<std::string, std::string> &)> model_resources;
std::function<PackageLoadTarget(const assets::ModelPackage &)> package_load;
};

inline std::shared_ptr<const engine::model_spec::ModelContract> require_model_contract(std::string family) {
Expand Down Expand Up @@ -216,6 +217,10 @@ class SpecBackedVoiceModelLoader final : public IVoiceModelLoader {
}
}

PackageLoadTarget resolve_package_load(const assets::ModelPackage & package) const override {
return config_.package_load ? config_.package_load(package) : IVoiceModelLoader::resolve_package_load(package);
}

ModelInspection inspect(const ModelLoadRequest & request) const override {
const auto assets = config_.load_assets(request.model_path);
const auto contract = require_model_contract(config_.family);
Expand Down
18 changes: 18 additions & 0 deletions src/community_models/auk/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,24 @@ std::shared_ptr<runtime::IVoiceModelLoader> make_auk_loader() {
class Loader final : public runtime::IVoiceModelLoader {
public:
std::string family() const override { return "auk"; }
runtime::PackageLoadTarget resolve_package_load(const assets::ModelPackage & package) const override {
runtime::PackageLoadTarget out{"auk", package.root, {}, {}};
if (package.format == "gguf") {
for (const auto & file : package.files) {
if (file.extension() != ".gguf") continue;
const auto name = file.filename().string();
if (name.rfind("auk-base-", 0) == 0 || name.rfind("auk-flash-", 0) == 0) {
out.session_options.emplace("auk.model_gguf", file.generic_string());
out.session_options.emplace("auk.variant", name.rfind("auk-flash-", 0) == 0 ? "flash" : "base");
} else if (name.rfind("qwen2.5-omni-3b-", 0) == 0) {
out.session_options.emplace("auk.qwen_gguf", file.generic_string());
} else if (name.rfind("auk-vae-", 0) == 0) {
out.session_options.emplace("auk.vae_gguf", file.generic_string());
}
}
}
return out;
}
bool can_load(const runtime::ModelLoadRequest & request) const override {
if (request.family_hint && *request.family_hint != "auk") return false;
try {
Expand Down
16 changes: 16 additions & 0 deletions src/community_models/lfm2_audio/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,22 @@ std::shared_ptr<runtime::IVoiceModelLoader> make_lfm2_audio_loader() {
public:
std::string family() const override { return kFamily; }

runtime::PackageLoadTarget resolve_package_load(const assets::ModelPackage & package) const override {
runtime::PackageLoadTarget out{kFamily, package.root, {}, {}};
if (package.format == "gguf") {
for (const auto & file : package.files) {
if (file.extension() != ".gguf") continue;
const auto name = file.filename().string();
const char * option = "lfm2_audio.model_gguf";
if (name.rfind("mmproj-", 0) == 0) option = "lfm2_audio.mmproj_gguf";
else if (name.rfind("vocoder-", 0) == 0) option = "lfm2_audio.vocoder_gguf";
else if (name.rfind("tokenizer-", 0) == 0) option = "lfm2_audio.detokenizer_gguf";
out.session_options.emplace(option, file.generic_string());
}
}
return out;
}

bool can_load(const runtime::ModelLoadRequest & request) const override {
if (request.family_hint && *request.family_hint != kFamily) {
return false;
Expand Down
18 changes: 18 additions & 0 deletions src/community_models/liveavatar/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,24 @@ std::shared_ptr<engine::runtime::IVoiceModelLoader> make_liveavatar_loader() {
return kFamily;
}

engine::runtime::PackageLoadTarget resolve_package_load(const assets::ModelPackage & package) const override {
engine::runtime::PackageLoadTarget out{kFamily, package.root, {}, {}};
if (package.format == "gguf") {
for (const auto & file : package.files) {
if (file.extension() != ".gguf") continue;
const auto name = file.filename().string();
if (name.rfind("Wan2.2-S2V-Support-", 0) == 0) {
out.load_options.emplace("liveavatar.support_gguf", file.generic_string());
} else if (name.rfind("Wan2.2-S2V-VAE-", 0) == 0) {
out.load_options.emplace("liveavatar.vae_gguf", file.generic_string());
} else if (name.rfind("Wan2.2-S2V-14B-", 0) == 0) {
out.load_options.emplace("liveavatar.denoiser_gguf", file.generic_string());
}
}
}
return out;
}

engine::runtime::CapabilitySet advertised_capabilities() const override {
return make_capabilities();
}
Expand Down
13 changes: 13 additions & 0 deletions src/community_models/minimax_h3/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,19 @@ std::shared_ptr<runtime::IVoiceModelLoader> make_minimax_h3_loader() {
config.family = kFamily;
config.load_assets = load_minimax_h3_assets;
config.create_session = create_minimax_h3_session;
config.package_load = [](const assets::ModelPackage & package) {
runtime::PackageLoadTarget out{kFamily, package.root, {}, {}};
if (package.format == "gguf") {
for (const auto & file : package.files) {
if (file.filename() == "dit.gguf" || file.filename() == "dit_int8.gguf") {
out.model_path = package.root / file;
return out;
}
}
throw std::runtime_error("MiniMax-H3 GGUF package has no dit.gguf or dit_int8.gguf entry");
}
return out;
};
return runtime::make_spec_backed_voice_loader(std::move(config));
}

Expand Down
18 changes: 18 additions & 0 deletions src/community_models/minimax_music3/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,24 @@ std::shared_ptr<runtime::IVoiceModelLoader> make_minimax_music3_loader() {
return kFamily;
}

runtime::PackageLoadTarget resolve_package_load(const assets::ModelPackage & package) const override {
runtime::PackageLoadTarget out{kFamily, package.root, {}, {}};
if (package.format == "gguf") {
for (const auto & file : package.files) {
if (file.extension() != ".gguf") continue;
const auto name = file.filename().string();
if (name.rfind("language_model_", 0) == 0) {
out.session_options.emplace("minimax_music3.language_model_gguf", file.generic_string());
} else if (name.rfind("rvq_depth_decoder_", 0) == 0) {
out.session_options.emplace("minimax_music3.rvq_depth_decoder_gguf", file.generic_string());
} else if (name.rfind("transformer_", 0) == 0) {
out.session_options.emplace("minimax_music3.flow_transformer_gguf", file.generic_string());
}
}
}
return out;
}

bool can_load(const runtime::ModelLoadRequest & request) const override {
if (request.family_hint.has_value() && *request.family_hint != kFamily) {
return false;
Expand Down
28 changes: 28 additions & 0 deletions src/framework/package_manager/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,23 @@ std::string PackageManager::remove(const std::string & package_id) {
return "Removed " + package.id + " -> " + root.string();
}

std::optional<PackageInfo> PackageManager::package_info(const std::string & package_id) const {
const auto found = std::find_if(impl_->packages.begin(), impl_->packages.end(), [&](const Package & package) {
return package.id == package_id;
});
if (found == impl_->packages.end()) return std::nullopt;
PackageInfo info;
info.family = found->family;
info.package.root = (impl_->models_root / found->target_directory).lexically_normal();
info.package.format = found->format;
info.package.files.reserve(found->files.size());
for (const auto & remote : found->files) {
info.package.files.push_back(stripped_path(*found, remote).lexically_normal());
}
info.installed = package_installed(*found, impl_->models_root);
return info;
}

std::string PackageManager::inventory(bool query_remote, const std::string & package_id) const {
std::vector<const Package *> selected;
selected.reserve(impl_->packages.size());
Expand All @@ -890,6 +907,17 @@ std::string PackageManager::inventory(bool query_remote, const std::string & pac
json::Value::Object row;
row["id"] = string_value(package.id);
row["installed"] = bool_value(installed);
json::Value::Array files;
files.reserve(package.files.size());
for (const auto & remote : package.files) {
files.push_back(string_value(stripped_path(package, remote).lexically_normal().generic_string()));
}
json::Value::Object metadata;
metadata["family"] = string_value(package.family);
metadata["root"] = string_value((impl_->models_root / package.target_directory).lexically_normal().generic_string());
metadata["files"] = json::Value::make_array(std::move(files));
metadata["format"] = string_value(package.format);
row["package"] = json::Value::make_object(std::move(metadata));
row["state"] = string_value(query_remote ? "ok" : "pending");
row["message"] = string_value("");
row["size_bytes"] = json::Value::make_null();
Expand Down
18 changes: 18 additions & 0 deletions src/framework/runtime/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,27 @@

#include <unordered_map>
#include <stdexcept>
#include <algorithm>
#include <cctype>

namespace engine::runtime {

PackageLoadTarget IVoiceModelLoader::resolve_package_load(const assets::ModelPackage & package) const {
PackageLoadTarget target{family(), package.root, {}, {}};
if (package.format == "gguf") {
for (const auto & file : package.files) {
auto extension = file.extension().string();
std::transform(extension.begin(), extension.end(), extension.begin(),
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
if (extension == ".gguf") {
target.model_path /= file;
break;
}
}
}
return target;
}

namespace {

std::string normalized_candidate_key(const std::string & relative_candidate) {
Expand Down
12 changes: 12 additions & 0 deletions src/framework/runtime/registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,18 @@ void ModelRegistry::validate_request(const ModelLoadRequest & request) const {
}
}

PackageLoadTarget ModelRegistry::resolve_package_load(
const std::string & family, const assets::ModelPackage & package) const {
ModelLoadRequest request;
request.model_path = package.root;
request.family_hint = family;
const auto * loader = find_loader(request);
if (!loader) {
throw std::runtime_error("unsupported model family hint: " + family);
}
return loader->resolve_package_load(package);
}

const IVoiceModelLoader * ModelRegistry::find_loader(const ModelLoadRequest & request) const {
for (const auto & loader : loaders_) {
if (request.family_hint.has_value()) {
Expand Down
16 changes: 16 additions & 0 deletions src/models/yue2/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,22 @@ std::shared_ptr<runtime::IVoiceModelLoader> make_yue2_loader() {
return kFamily;
}

runtime::PackageLoadTarget resolve_package_load(const assets::ModelPackage & package) const override {
runtime::PackageLoadTarget out{kFamily, package.root, {}, {}};
if (package.format == "gguf") {
for (const auto & file : package.files) {
if (file.extension() != ".gguf") continue;
const auto name = file.filename().string();
if (name.rfind("yue2-3b-", 0) == 0) {
out.session_options.emplace("yue2.model_gguf", file.generic_string());
} else if (name.rfind("yue2-vae-", 0) == 0) {
out.session_options.emplace("yue2.vae_gguf", file.generic_string());
}
}
}
return out;
}

bool can_load(const runtime::ModelLoadRequest & request) const override {
if (request.family_hint.has_value() && *request.family_hint != kFamily) {
return false;
Expand Down
Loading
Loading