mattrguerra/cortexmainPrivate on GitHub, published here in full

architecture.md

Language Markdown5.4 KB96 lines

docs/architecture.md

  1. # Architecture
  2. ```
  3. +------------------------------ Cortex.app ---------------------------------+
  4. | |
  5. | WebView (React) Rust (Tauri shell, src-tauri) |
  6. | +-----------------------+ invoke +------------------------------+ |
  7. | | main window | ------------> | commands.rs | |
  8. | | sidebar, chat, | | thin wrappers | |
  9. | | composer, picker, | <-- Channel - | (stream events) | |
  10. | | settings, usage | | windows.rs | |
  11. | +-----------------------+ | vibrancy, quick window, | |
  12. | | quick window | | tray, global shortcut | |
  13. | +-----------------------+ +--------------+---------------+ |
  14. | no network, no keys, no paths | |
  15. | +---------------v---------------+ |
  16. | | cortex-core | |
  17. | | provider: SSE, client, | |
  18. | | errors, cancellation -----> DeepInfra or a
  19. | | chat: one turn end to end | | local server
  20. | | pricing, cost, estimate | |
  21. | | storage: SQLite + FTS5 | |
  22. | | attachments, export, secrets ----> OS keychain
  23. | +-------------------------------+ |
  24. +---------------------------------------------------------------------------+
  25. ```
  26. ## The split
  27. - **`crates/cortex-core`** holds all the logic and has no Tauri dependency, so
  28. it builds and tests in seconds on any machine. The whole of a chat turn
  29. (history into a request, streaming, stopping, usage, cost, saving) is one
  30. function, `chat::run_turn`, tested against the stand-in server.
  31. - **`src-tauri`** is the shell: commands that call into the core, the two
  32. windows, the tray and the shortcut.
  33. - **`src/`** is the interface. It talks to Rust only through the `IpcClient`
  34. interface (`src/ipc/client.ts`), which has two implementations: the real one
  35. (`tauri.ts`) and an in-memory one with sample data (`mock/`), which the
  36. browser tests and the screenshots use.
  37. - **`crates/standin`** is a small OpenAI-compatible server whose behavior is
  38. picked by model id (echo, slow, no usage, reasoning, vision, errors). Every
  39. provider path is tested against it, and so is the real app end to end.
  40. ## Rules the code keeps
  41. - **The webview never sees a key or the network.** Provider calls happen in
  42. Rust. The CSP allows no outside connections. Keys are write-only from the
  43. interface: it can set, clear and ask whether one exists.
  44. - **The webview never supplies a file path.** File pickers are opened from
  45. Rust; pasted and dropped files arrive as bytes.
  46. - **Each window gets only the commands it needs.** `build.rs` declares every
  47. command, so each one needs a permission; the quick window's capability
  48. grants 14 of them (`src-tauri/capabilities/`).
  49. - **No model call behind your back.** Chats are titled from their first line,
  50. not by asking a model. Failed requests are not retried automatically,
  51. because a retry is billed.
  52. - **Types cross the boundary once.** Rust structs generate their TypeScript
  53. twins (`src/ipc/gen`, via ts-rs) when the tests run; CI fails if they are
  54. stale.
  55. ## Streaming
  56. `provider/sse.rs` is a byte-level Server-Sent Events parser: a network chunk
  57. can split a line, a `\r\n` pair or a UTF-8 character anywhere, and a property
  58. test feeds it every way of cutting the same stream. `provider/openai.rs` reads
  59. each chunk tolerantly (DeepInfra, Ollama and llama.cpp each differ a little)
  60. and keeps the last usage block seen, whether it arrives on the final content
  61. chunk or in a chunk of its own. Deltas are batched about every 30 ms on the way
  62. to the interface, which paints them once per frame.
  63. Stopping cancels a token held by the stream: the read loop drops the HTTP
  64. connection at once (the stand-in test checks the server sees it go), and the
  65. partial reply is saved as `cancelled`, with estimated tokens.
  66. ## Storage
  67. One SQLite file in the app's data folder. Tables for providers, conversations,
  68. messages (with tokens, prices and cost on each row), attachments, presets,
  69. settings and the model price list. FTS5 indexes messages and titles, kept in
  70. step by triggers; search input is quoted word by word, so nothing typed can
  71. break the query. A reply left `streaming` by a crash is marked interrupted at
  72. the next start. Images are stored once per content hash, downscaled first to
  73. 1568 px on the long edge, because vision models bill by size.
  74. ## Windows
  75. - **Main:** created hidden and shown when the page has painted (no white
  76. flash). Closing it hides it; Quit quits. On macOS it has a transparent
  77. overlay title bar over `Sidebar` vibrancy.
  78. - **Quick prompt:** a separate small page, created hidden at startup so it
  79. opens at once. A global shortcut (Cmd+Shift+Space by default) shows it on the
  80. screen under the pointer; it hides on Esc or when it loses focus.
  81. - **Tray:** new chat, quick prompt, this month's spend, settings, quit.
  82. On Linux the window is opaque and the page paints its own soft backdrop behind
  83. the glass. Windows is not built or tested.