diff --git a/README.md b/README.md index 60895c1..3d7f5cb 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ The server is configured around an Ollama-compatible API surface: `qwen3:14b` is the default model for chat and roleplay. The model selector loads available models from `/tags` and selects `qwen3:14b` when available (otherwise the first returned model). Existing chats retain their saved model selection. -The composer has independent Web Search and Reasoning toggles, both off by default and reset after a completed reply. It sends `{message, chatId, model, webSearch, reasoning}` using streamed fetch to `POST /chat/stream`. SSE events carry `status` (accepted/analyzing/searching/generating), `thinking` (`{chunk}`), `chunk` (`{chunk}`), `replace` (`{target: "reply"|"thinking", text}`), `done` (reply/sources), and `error` (error/status/code/requestId when available). Text events send only incremental text. A corrective persona pass explicitly replaces provisional output before streaming its replacement. Both sends and regenerations batch Markdown rendering per animation frame. Only the validated complete answer is persisted. Clients consuming SSE must handle `replace` events and accumulate deltas; the browser also accepts the older accumulated event format. +The composer has independent Web Search and Reasoning toggles. Automatic web search starts enabled and keeps the selected on/off state across messages until the page reloads. Reasoning starts off and resets after a completed reply. It sends `{message, chatId, model, webSearch, reasoning}` using streamed fetch to `POST /chat/stream`. SSE events carry `status` (accepted/analyzing/searching/generating), `thinking` (`{chunk}`), `chunk` (`{chunk}`), `replace` (`{target: "reply"|"thinking", text}`), `done` (reply/sources), and `error` (error/status/code/requestId when available). Text events send only incremental text. A corrective persona pass explicitly replaces provisional output before streaming its replacement. Both sends and regenerations batch Markdown rendering per animation frame. Only the validated complete answer is persisted. Clients consuming SSE must handle `replace` events and accumulate deltas; the browser also accepts the older accumulated event format. Every Ollama request explicitly sets `stream: true`, `think`, and `keep_alive`. Reasoning is requested and forwarded only when `reasoning === true` and the model supports the boolean thinking API. The shared conservative capability check supports Qwen3, DeepSeek R1 and DeepSeek v3.1 model IDs; unknown models, Qwen coder variants and models requiring thinking levels have the toggle disabled. Internal calls, roleplay openers and regeneration default to `think: false`. There is no extra model call to obtain reasoning. @@ -318,9 +318,9 @@ Pure chat message, retry, memory, summary, and persona-state formatting lives in ## Optional Web Search -The globe button next to the composer enables search for one message. It defaults to off and resets once the server accepts the message. The browser sends `webSearch: true` (or `false`) through the existing authenticated `POST /chat/stream`; `POST /chat` supports the same optional boolean. Omitting it preserves the normal chat flow. No generic tool/agent system is needed. +Automatic web search is enabled by default. The globe button switches it off or back on; that choice persists across sends and regenerations until the page reloads. The browser sends `webSearch: true` (or `false`) through the authenticated `POST /chat/stream`. `POST /chat`, retries, and edit/resend use the same default: omitting `webSearch` enables automatic search, while `false` bypasses both search planning and retrieval. -Search uses `shouldSearch = webSearchEnabled && (explicitSearchIntent || automaticSearchDecision)`. Deterministic commands such as “search the internet”, “look this up online”, and “find sources” override smart skipping. Arithmetic, rewriting, explanations and questions answered by the server clock can otherwise skip search. Query cleanup removes command wording while preserving dates and years; “average lifespan” is normalized to “average life expectancy”. +When search is enabled, the selected AI model makes a short structured decision about whether external evidence would help and, if so, supplies a search query. It sees the current message, up to six recent messages, and available image observations so it can resolve follow-up references. This internal decision is never streamed or saved as a chat reply. Explicit commands such as `search the internet` bypass planning and search directly; questions answered by the server clock skip retrieval. Malformed decisions, unsupported structured output, or a planning failure fall back to the existing keyword rules. Planning is limited to 160 output tokens and 10 seconds, and honors Stop/disconnection. Query cleanup preserves requested dates and years. The chat service retrieves up to 15 candidates per query and selects 1–5 useful sources dynamically, rejecting empty evidence and wrong-date results. SearXNG `content` maps to `snippet` (an existing non-empty snippet is also accepted). Exact-date event searches use the general index to include archives and try at most one alternate query if the first returns no usable evidence; candidates are merged and deduplicated. Current-news requests retain the news category. The same numbered selected titles, URLs and snippets go immediately before the current question in Ollama's context, into the response, and into storage. Retrieval finishes before either reasoning or answer streaming starts. Instructions require citations, supported partial answers rather than blanket claims of missing details, and honest disclosure when evidence is insufficient. Result content remains untrusted data; no result pages are fetched. @@ -342,13 +342,13 @@ search: - json ``` -The provider calls `GET {SEARXNG_URL}/search?q=...&format=json` using encoded query parameters. SearXNG must allow requests from the application server; disabled JSON output returns HTTP 403. See the [official Search API documentation](https://docs.searxng.org/dev/search_api.html). Only the current query is sent to SearXNG, not conversation history or personas; configured SearXNG engines can forward that query to their search services. +The provider calls `GET {SEARXNG_URL}/search?q=...&format=json` using encoded query parameters. SearXNG must allow requests from the application server; disabled JSON output returns HTTP 403. See the [official Search API documentation](https://docs.searxng.org/dev/search_api.html). Only the resulting search query is sent to SearXNG; it may include relevant details from recent conversation or image observations when needed to resolve the question. Configured SearXNG engines can forward that query to their search services. The server-side `WebSearchProvider` JSDoc contract is `search(query, {signal, limit}?) -> Promise`; `SearXNGSearchProvider` is the initial implementation. Results have `{title, url, snippet}`. This repository uses JavaScript ES modules, so types are documented with JSDoc rather than introducing a TypeScript build. Search emits `{status: "searching"}` only when retrieval actually runs, then `{status: "generating", searchState, sourceCount?}` before model generation. The UI initially says “Preparing response…” so skipped searches never claim to search. `searchState` distinguishes `skipped`, `no_results`, `no_relevant_results`, `succeeded`, and `failed`. Successful `done` events and non-streaming responses carry this state and optional `sources`; search failures carry `searchState: "failed"` in the error response. No usable evidence produces `sources: []` and instructions to say that a search found insufficient relevant evidence, without fake citations. Initial retrieval failures use the existing error event (HTTP 503 on `/chat`) and do not generate an answer; an optional fallback failure preserves the successful initial search. The saved failed user message remains visible, and the composer restores its text for resending without search. -Sources appear below assistant messages as numbered links. They persist in a nullable SQLite `chat_messages.sources` JSON column, added automatically on startup using the existing additive schema mechanism; no manual migration is needed. Old messages remain valid. Sources are preserved in JSON exports/imports and branches. Regenerating an answer reuses its stored results without performing another search; a fresh search requires sending another message with the toggle enabled. +Sources appear below assistant messages as numbered links. They persist in a nullable SQLite `chat_messages.sources` JSON column, added automatically on startup using the existing additive schema mechanism; no manual migration is needed. Old messages remain valid. Sources are preserved in JSON exports/imports and branches. Regenerating or editing/resending a message makes a fresh search decision when web search is enabled, using only the conversation through the corresponding user prompt. Earlier response versions keep their own saved sources. Run `npm test` and `npm run lint` to check the feature and existing flows. `test/webSearch.test.js` uses local HTTP protocol fixtures and an isolated in-memory database; no production search results are mocked. Live SearXNG/Ollama verification still requires configured, reachable services. There is no TypeScript or build command in this framework-free project. diff --git a/public/chat.html b/public/chat.html index 5bfab8f..de3b05a 100644 --- a/public/chat.html +++ b/public/chat.html @@ -292,8 +292,8 @@ -