aboutsummaryrefslogtreecommitdiffstats
AgeCommit message (Collapse)AuthorFilesLines
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>
2026-08-09fix: warn when [[providers.local]] discards its fieldsDanilo M.4-5/+125
A malformed local entry was the one skip that said nothing, on the reasoning that local still ends up working. It does, but the merge rebuilds it from the bare base_url, so an api_key, filter or ctx_size set on that entry is dropped without a word. The app then comes up looking healthy, which is the strongest possible signal that nothing is wrong, making this the case that most needs saying, not least. Local surviving at all depends on DEFAULTS supplying base_url, since that is what the rebuild reads. Noted in config.py so the key is not removed as redundant, and pinned by a test. The not-a-table wording now follows what was written: a list gets the single-bracket fix by name, while a scalar entry does not, since telling someone to fix a [[...]] they never typed points at the wrong line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176tzAW6H1i2Kz8vm2XXVGV
2026-08-09fix: survive malformed provider config at startupDanilo M.4-5/+326
Eight config shapes, all valid TOML, crashed config.load() before any window existed, so the user got a traceback on a terminal a desktop launch does not have. Skip malformed providers instead, keeping the local path working, and say on stderr which one was dropped and why. The guards are isinstance tests rather than a try/except because the shapes raise three different exception types, and the local entry is checked the same way: [[providers.local]] is a truthy list that raised during the bare base_url merge, before the loop could skip it. A config that cannot load at all now reports the file and exits rather than half-starting on defaults the user never configured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176tzAW6H1i2Kz8vm2XXVGV
2026-08-09feat: expose the provider table from configDanilo M.2-0/+55
base_url stays populated so existing callers are untouched; the provider table is built alongside it. models.ini sits beside config.toml and state.ini, following the same pattern as the window layout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09docs: stop crediting from None with secret hygieneDanilo M.3-14/+14
A TimeoutExpired chained without from None already prints no secret, since its __str__ shows only the command and the duration. The suppressed display buys a tidier traceback, not a narrower leak, and the comments claimed otherwise. The from None calls stay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>