aboutsummaryrefslogtreecommitdiffstats
AgeCommit message (Collapse)AuthorFilesLines
2026-09-19feat: icon-only toolbar and audio input follow-upsHEADmasterDanilo M.8-53/+144
Make the chat toolbar icon-only (tooltips retained) and add an icon_size config key, default 28, so the drawn glyphs can be scaled. Qt was drawing the 24px pixmaps at its ~16px default; the explicit icon size fixes that. Fix the deferred audio minors: AudioRecorder.start() now halts a prior source and checks the source error, so a failed capture no longer claims to be recording. Send now validates every pending modality in one gate, so a mixed image+audio clip cannot reach a model without vision. Sub-second clips no longer name voice-note-0s.wav, and non-WAV audio containers classify as unknown, guarded by a test.
2026-09-18feat: mark audio-capable models in the pickerDanilo M.4-7/+45
2026-09-18fix: refresh record button after model list repopulationDanilo M.1-0/+1
2026-09-18fix: keep Stop reachable during capture and refresh it on listing failureDanilo M.1-3/+5
2026-09-18docs: document voice inputDanilo M.2-0/+32
2026-09-18feat: gate audio sends and add the default audio promptDanilo M.1-0/+56
2026-09-18feat: record control and voice-note history markerDanilo M.2-1/+103
2026-09-18test: cover qualified-id modality mappingDanilo M.1-3/+19
2026-09-18feat: detect model input modalities from the routerDanilo M.3-15/+105
2026-09-18feat: audio model capability in metadata and dialogDanilo M.4-14/+103
2026-09-18feat: audio attachment kind and input_audio content partDanilo M.2-8/+98
2026-09-18feat: audio capture and WAV encodingDanilo M.2-0/+142
2026-09-18docs: implementation plan for voice inputDanilo M.1-0/+1309
2026-09-18docs: restrict voice input to WAV attachmentsDanilo M.1-8/+10
2026-09-18docs: spec for voice input to audio-capable modelsDanilo M.1-0/+254
2026-09-12Added comment to remind how to upgrade in case of system qt bumpDanilo M.1-0/+15
2026-08-25fix: keep one-shot skills loaded until NewDanilo M.5-14/+78
2026-08-25fix: honor skills_enabled on restore, and final-review polishDanilo M.6-4/+80
2026-08-25docs: document skills supportDanilo M.2-0/+38
2026-08-25feat: stream skill_loaded events and show loaded-skill chipsDanilo M.2-4/+33
2026-08-25feat: inject loaded skills into the system prompt and load from textDanilo M.2-1/+143
2026-08-25docs: generic skill names in plan test fixturesDanilo M.1-47/+47
2026-08-25test: use generic skill names in fixturesDanilo M.1-41/+41
2026-08-25feat: dispatch the tool loop over web_search and load_skillDanilo M.2-18/+259
2026-08-25feat: persist loaded skills per sessionDanilo M.2-0/+53
2026-08-25feat: skills config keys, on by defaultDanilo M.2-0/+50
2026-08-25feat: skill store parsing for SKILL.md instruction filesDanilo M.2-0/+338
2026-08-25docs: spec and plan for skills supportDanilo M.2-0/+1623
2026-08-25feat: move model and prompt pickers to the bottom bar, add button iconsDanilo M.2-49/+145
2026-08-24docs: document code block wrapping and the copy buttonDanilo M.2-2/+35
Records why the block style carries white-space:pre-wrap and why the padding is margins plus a spacer line: Qt's markdown stylesheet does not cover pre, and Qt ignores padding and vertical margins on it. Also describes the copy button and the constraints behind its shape, since neither a widget nor :hover is available in Qt rich text.
2026-08-24feat: pad code blocks, make the copy link a hover buttonDanilo M.2-16/+131
The copy link was a word-plus-glyph sitting above the block, always at full strength, with no feedback once clicked. It is now an icon-only button under the block, since Qt cannot float it over the tint. Qt draws almost no box properties on <pre>: padding and vertical margins are ignored, so the inset is margin-left/right and the room above and below is a tinted spacer line. The glyph alone renders small whatever font-size says, so the button is a tinted pill instead: grey at rest, the highlight colour under the pointer, and a tick on green once used. Qt rich text has no :hover, so hovering is QTextBrowser.highlighted() driving a re-render, the same way the toggles already re-render. The tick reverts after a moment rather than standing indefinitely, which would claim the clipboard still holds that block long after it stopped being true. Copying a second block first cancels the first one's revert.
2026-08-24feat: wrap code blocks and offer a copy link on eachDanilo M.2-6/+151
Two things made a fenced code block awkward to read. Qt's markdown stylesheet wraps p and li but not pre, and that stylesheet is dropped when only the body fragment is kept, so a long line widened the whole transcript and left it scrolling sideways. The pre style now carries white-space:pre-wrap itself. Copying a block meant selecting it by hand. Each block now gets a copy link above it, on the right, in the same scheme-dispatch the thinking and search toggles already use, since Qt rich text has no widgets to put a real button in. The block text is read back from the source markdown because Qt emits one <pre> per line, which makes the block unrecoverable from the rendered HTML; _close_fences runs first, so a block still streaming is copyable mid-reply. Like the other schemes, a forged copy link in a reply is defused.
2026-08-21feat: make status messages dismissableDanilo M.2-4/+73
Status messages now show a dismiss button; an error persists until dismissed or replaced, and a transient message can be cleared early.
2026-08-21fix: store per-turn token sums so searched turns price every roundDanilo M.3-13/+26
A searched turn makes one API call per search round and all of them are billed, but prompt_tokens/completion_tokens held only the last round's counts, so a searched turn under-reported its cost. The columns now hold the sum across rounds; usage_json keeps each round's raw usage block as a JSON array for external consumers to verify against. A single-round turn stores the same numbers as before.
2026-08-21fix: web search on DeepSeek and other reasoning modelsDanilo M.8-47/+584
A searched turn on a cloud reasoning model ended at the thinking: the model emitted the tool call, but finish_reason: "tool_calls" landed on the same SSE line as the include_usage block, so the single-event parser returned that line as a usage chunk and the loop never saw the tool finish. The parser now emits every event a line carries, so the search fires. Also in this change: - Replay each round's reasoning_content on the assistant tool-call message, which interleaved-thinking models require to keep going. - Add a per-provider replay_reasoning option (DeepSeek, SiliconFlow GLM-4.7+) to carry prior turns' reasoning_content when search is on. - Add an on-demand diagnostic log gated by $LLAMACHAT_DEBUG_LOG. - Record provider, usage_json and reported_cost_usd per reply, so a searched turn keeps every round's billed usage for external consumers.
2026-08-11fix: prevent cloud reasoning models from exhausting default max_tokensDanilo M.5-8/+118
- Send max_tokens = 32768 for non-local providers so reasoning models have room for both chain-of-thought and answer. - Add per-provider thinking_budget option for endpoints (currently SiliconFlow) that cap reasoning tokens separately. - Accept bare JSON arrays from /v1/models; some OpenAI-compatible endpoints omit the {"data": [...]} envelope.
2026-08-10docs: document external providers, keys and costDanilo M.3-1/+121
The generated config ships the provider block commented out so a fresh install never reaches an endpoint nobody configured. The README states plainly that every turn resends the whole conversation, which is free locally and billed per message on a cloud provider.
2026-08-10feat: show conversation cost and the next send's projectionDanilo M.1-0/+57
The projection prices the whole request, not just the draft, because reopening a conversation resends its entire history and that is billed per turn on a cloud provider. Seeing it before sending is the whole point of the readout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10feat: offer model settings on first use of a cloud modelDanilo M.1-1/+39
Fires on selection rather than on send, so the interruption lands while the user is already changing settings instead of mid-thought. Local models never trigger it, and a cancelled dialog is recorded so a model tried once never asks again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10feat: list and route models through every configured providerDanilo M.5-66/+138
Metadata now resolves models.ini over provider defaults over presets.ini, so a local model keeps getting its context and vision from the preset while remaining overridable. A model whose vision support is unknown no longer blocks an attachment: unknown is not the same as no, and the API will reject an image if it really cannot take one. Key resolution moves to the worker thread so a pass: key's pinentry never freezes the GUI. KeyResolver gains a lock since StreamWorker and TitleWorker can now overlap. Skipped providers surface in the status bar rather than only on stderr. model_info uses dataclasses.replace to avoid mutating the ModelInfo that resolve() may return from the store. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10feat: cost label showing spend and the next send's projectionDanilo M.2-1/+82
Three states stay distinct: blank for a free local model, ? for a cloud model whose prices were never entered, and a figure when they were. Collapsing the first two would make an unpriced cloud model read as free. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10feat: dialog for per-model context size, vision and pricesDanilo M.2-0/+199
Field conversion is separated from the widget so the part with the edge cases is testable without a running Qt application. Vision is tri-state through a binary checkbox: an unchecked box with an unknown prefill stays unknown rather than writing False, which would shadow a provider-level vision=True through models.resolve's pick(). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10feat: fan model listing out across providers and route by idDanilo M.2-28/+180
A provider that is unreachable or whose filter matched nothing is reported rather than fatal: local models must stay usable when the network is down. Clients are built on demand so a key is resolved only when that provider is really used. The model listing carries its own timeout, separate from the per-turn one: the picker has to populate fast, and a dead cloud provider must not hang it for minutes. Client.models() takes list_timeout (default 30) and uses httpx.Timeout(list_timeout, connect=5), and MultiClient threads its own list_timeout through to each listing call. The fan-out stays serial on purpose: KeyResolver._cache is unlocked and only safe while every caller resolves on the GUI thread. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09feat: send bearer auth when a provider needs a keyDanilo M.2-4/+109
The local router needs none, so the header is omitted entirely rather than sent empty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09docs: name the ceiling in update_message's keep-on-omit conventionDanilo M.2-2/+14
The convention was documented but not its consequence: because None means keep, no caller can write NULL through this method, and that is invisible from the signature. Unreachable today since every field is filled once when a reply finishes and never cleared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09feat: record token counts and the producing model per messageDanilo M.2-13/+127
Cost has to survive reopening a conversation, which means storing what each reply used. The model goes on the message rather than the session because sessions records only the current one, and a conversation that switched models would otherwise be priced entirely at whichever is selected now. All three columns are nullable so old rows read as unknown. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09fix: reject non-finite provider prices, and read them as unpricedDanilo M.4-11/+273
A nan or inf in [providers.*] survived float() and reached the cost arithmetic, rendering $nan or, for -inf, a negative cost. Worse than a display wart: is_priced answered True, so the readout took the priced branch and showed a poisoned figure in the one state the ? is there to admit it cannot say. Guarded at both ends. providers._number mirrors models._get, which already covered the models.ini writer. is_priced states its own predicate completely rather than trusting its callers, since the dialog is a third writer and the file is documented for hand-editing. Records why float money stays, and that exact-equality assertions in the cost test hold for the chosen prices rather than in general. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09feat: resolve model metadata in layers and compute costDanilo M.2-0/+270
Resolution merges per field, not per source, so an entry setting only ctx_size still inherits the provider's prices. Cost prices each reply at the model that produced it, and rows predating the token columns contribute zero rather than a fabricated number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09fix: reject non-finite prices and escape a model named DEFAULTDanilo M.3-69/+266
float() accepts "nan" and "inf", so a hand-edited models.ini could feed either into the cost arithmetic and render "$nan". Rejected once in _get rather than at each downstream call site. A model whose id is literally DEFAULT wrote a [DEFAULT] section, which a stock configparser reader treats as defaults inherited by every other model. Since the file is documented for hand-editing, that reintroduces the contamination the renamed default_section exists to prevent, so the id is escaped on write and mapped back on read. Also drops the marker branch in save(), leaving mark_skipped() as the sole writer of the "marker present iff no real keys" invariant, and prunes two test blocks that could not fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09feat: store per-model metadata in models.iniDanilo M.2-0/+286
Cloud models have no presets.ini entry, so context size, vision and prices are recorded per model in a file the app writes. A cancelled dialog leaves a marker so a model tried once never asks again, which is distinct from an absent section meaning never asked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>