Files
Hermit/app/CMakeLists.txt
T
WHC 69842edf16 v0.8.10: settings dialog (provider, model, key), window starts on a provider
The window could be started, but it could not be pointed anywhere: a provider,
a model and a key only came in on the command line. The gear in the header now
opens a dialog that changes all three on the engine's live config.

AgentCore (app/src/core/AgentCore.*):
- providers(): the engine's preset table (cst_provider_count/at), with base
  URL, the remembered or preset model, whether a key exists and where it comes
  from (cst_creds_resolve: "saved", or the environment variable), the preset's
  key variable and the source ("built-in" / "user"). A key is read into a local
  buffer only to answer "is there one"; it never leaves the C++ side.
- modelChoices(provider): the provider's own list (cst_provider_models), the
  fetched cache (cst_ps_cached -- which writes through its out-parameters, so
  it takes a real int, not NULL: passing one cost a segfault) and the
  remembered pick. No network. modelInfo() is cst_model_info + its "200K ·
  text only" label; usage() is cst_agent_usage for the session and the turn.
- useProvider(provider, model, key) -> "" or a reason: a different provider
  goes through cst_ps_switch_provider() (the key is verified against /models
  before it is saved; offline it is kept unverified and the header says so),
  a key for the current one through cst_ps_set_key(), and the model is
  remembered in settings.json. syncConfig() takes the engine's config back
  over the app's copy, so provider/model/approval read correctly afterwards.
  The engine's session is never touched by a switch.
- open() no longer starts with an empty provider: it takes the one the engine
  used last (settings.json), else the built-in default, plus that provider's
  base URL and model. Without this the first turn could not be sent at all and
  no entry in the dialog was "in use".

QML (app/qml/SettingsDialog.qml, Main.qml):
- a modal dialog with the themed surface the other dialogs use (the Basic
  style's light panel washes out the dark theme), the provider list with its
  key state, a model field with the info label and clickable candidates, a
  password field for a new key, the session's token counters, and a note that
  reports what the last "Use this" answered.

Tests: 8 more checks -- the presets are readable, an unknown provider is
refused, the counters are readable, the gear opens the panel, the rows exist, a
model typed into the field reaches the engine, the note says so, Close puts the
dialog away. 36 checks pass. --uitest also points XDG_CONFIG_HOME at its
scratch directory now, so a run cannot rewrite the user's settings.json or
credentials, and CST_APP_SHOT writes shot.png at the end plus
shot-settings.png while the dialog is open (that is how the dialog above was
reviewed on a machine without a screen).

Verified: 36/36 UI checks, --selftest, ctest cst-app-selftest, make check rc=0,
and a screenshot of the dialog.

Version 0.8.10.
2026-10-11 12:25:33 +08:00

130 lines
5.4 KiB
CMake

cmake_minimum_required(VERSION 3.16)
project(cst-agent-app LANGUAGES C CXX VERSION 0.1.0)
# The Qt front end (app/) embeds the C engine in-process: it links libcstcore
# and never opens a port. Two targets:
#
# cst-app-selftest pure C smoke test of the embedding path (no Qt), so the
# C side is still guarded on machines without Qt Quick
# cst-agent-app the GUI (Qt 6 Quick + QML), built when the Qt modules
# are there
#
# Standalone build (builds the engine from ../ too):
# cmake -S app -B build-app -DCMAKE_BUILD_TYPE=Debug && cmake --build build-app -j
# Inside the repo build (the parent defines the cstcore target, shared with the CLI):
# cmake -B build -DCST_BUILD_APP=ON && cmake --build build --target cst-agent-app
#
# Extra QML import paths, e.g. a Kirigami installed away from the Qt prefix:
# -DCST_APP_QML_IMPORT=... (added to QML2_IMPORT_PATH / the app's runtime)
option(CST_APP_GUI "build the Qt GUI (needs Qt6 Quick, Qml, QuickControls2)" ON)
option(CST_APP_SELFTEST "build the C embedding smoke test" ON)
# --- the engine ----------------------------------------------------------
# Used as a subproject (../CMakeLists.txt already defined cstcore, engine
# compiled once for CLI and app) or built here from the sources next door.
if(NOT TARGET cstcore)
set(CST_APP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..")
include_directories("${CST_APP_ROOT}/include" "${CST_APP_ROOT}/vendor/cjson")
file(GLOB CST_APP_CORE_SRC
"${CST_APP_ROOT}/src/*.c"
"${CST_APP_ROOT}/src/tools/*.c"
"${CST_APP_ROOT}/vendor/cjson/cJSON.c")
list(REMOVE_ITEM CST_APP_CORE_SRC
"${CST_APP_ROOT}/src/main.c"
"${CST_APP_ROOT}/src/tui.c"
"${CST_APP_ROOT}/src/tui_stub.c"
"${CST_APP_ROOT}/src/textsel.c")
# The HTTP front end is not part of an in-process app: no server, no port.
list(REMOVE_ITEM CST_APP_CORE_SRC
"${CST_APP_ROOT}/src/net.c"
"${CST_APP_ROOT}/src/http_srv.c"
"${CST_APP_ROOT}/src/webui.c"
"${CST_APP_ROOT}/src/web_md.c")
if(EXISTS "${CST_APP_ROOT}/.gen/models_builtin.inc")
include_directories("${CST_APP_ROOT}/.gen")
endif()
add_library(cstcore STATIC ${CST_APP_CORE_SRC})
target_compile_definitions(cstcore PRIVATE CST_NO_WEBUI=1)
find_package(CURL REQUIRED)
find_package(Threads REQUIRED)
target_link_libraries(cstcore PUBLIC ${CURL_LIBRARIES} Threads::Threads m)
message(STATUS "app: building the engine from ${CST_APP_ROOT}")
else()
message(STATUS "app: linking the cstcore target of the parent build")
endif()
# --- the C smoke test ----------------------------------------------------
if(CST_APP_SELFTEST)
add_executable(cst-app-selftest tools/selftest.c)
target_link_libraries(cst-app-selftest PRIVATE cstcore)
enable_testing()
add_test(NAME cst-app-selftest COMMAND cst-app-selftest)
endif()
# --- the GUI -------------------------------------------------------------
if(CST_APP_GUI)
find_package(Qt6 6.4 COMPONENTS Core Gui Qml Quick QuickControls2 Test QUIET)
if(NOT Qt6_FOUND)
message(WARNING
"app: Qt 6 Quick/Qml not found, skipping cst-agent-app "
"(the C selftest still builds; install e.g. qt6.qtdeclarative / "
"qt6.qtquickcontrols2 and reconfigure)")
else()
qt_standard_project_setup()
# Qt 6.5+ policies: keep the module resource at :/qt/qml/<URI>/ (what
# main.cpp loads) and let CMake generate the qmldir of qml/ for us.
qt_policy(SET QTP0001 NEW)
qt_policy(SET QTP0004 NEW)
qt_add_executable(cst-agent-app
src/main.cpp
src/core/AgentCore.cpp
src/core/ToolProvider.cpp
src/ui/UiTest.cpp
src/platform/linux/NotifyTool.cpp)
target_include_directories(cst-agent-app PRIVATE
src "${CMAKE_CURRENT_SOURCE_DIR}/../include")
target_compile_definitions(cst-agent-app PRIVATE CST_APP_VERSION="${PROJECT_VERSION}")
if(CST_APP_QML_IMPORT)
target_compile_definitions(cst-agent-app PRIVATE
CST_APP_QML_IMPORT="${CST_APP_QML_IMPORT}")
endif()
target_link_libraries(cst-agent-app PRIVATE
Qt6::Core Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2 Qt6::Test cstcore)
if(WIN32)
set_target_properties(cst-agent-app PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
# Theme is read by every component, so it is a QML singleton (one instance,
# no object needed in the tree).
set_source_files_properties(qml/Theme.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
# Keep the module flat in the resource tree (:qt/qml/CstAgent/Main.qml).
# Sources live in qml/, but a module whose files sit in a subdirectory of
# the URI makes Qt register the types with relative URLs ("qmlRegisterType
# requires absolute URLs" on every start).
foreach(_qml Main ChatView Sidebar SessionRow MessageCard ToolCard ThinkBlock
NoteLine TodoPanel Composer ConsentDialog AskDialog SettingsDialog
Theme)
set_source_files_properties(qml/${_qml}.qml PROPERTIES QT_RESOURCE_ALIAS ${_qml}.qml)
endforeach()
qt_add_qml_module(cst-agent-app
URI CstAgent
VERSION 1.0
QML_FILES
qml/Main.qml
qml/ChatView.qml
qml/Sidebar.qml
qml/SessionRow.qml
qml/MessageCard.qml
qml/ToolCard.qml
qml/ThinkBlock.qml
qml/NoteLine.qml
qml/TodoPanel.qml
qml/Composer.qml
qml/ConsentDialog.qml
qml/AskDialog.qml
qml/SettingsDialog.qml
qml/Theme.qml)
endif()
endif()