Open Finapp
Премиум

AI Chat

Локальный AI ассистент для транзакций, кошельков, категорий и аналитики.

Обзор

В Finapp встроен локальный AI ассистент на Ollama и @tanstack/ai. Чат живёт в slide-over панели (AiChatPanel.vue) и предоставляет ~24 tool'а, оборачивающих мутации и запросы Pinia-сторов. Данные не покидают машину пользователя.

Точки входа

  • FAB-кнопка (AiFab.vue) - плавающая кнопка на всех авторизованных страницах
  • Горячая клавиша Cmd/Ctrl+I (см. shortcuts.ts)
  • Slide-over панель справа, sm:max-w-md

Архитектура

User input → AiChatPanel
  ├── useAiChat.send(text)
  │   ├── pruneContext(messages) - скользящее окно + сжатие старых tool results
  │   ├── chat() через @tanstack/ai + адаптер Ollama
  │   ├── стрим TOOL_CALL_START / TOOL_CALL_ARGS / TOOL_CALL_END / TEXT_MESSAGE_CONTENT
  │   ├── autoRetry - детект неудачных мутаций + незавершённых chain
  │   ├── client-side fallback - клиент напрямую исполняет мутации если модель тупит
  │   ├── toast - визуальный фидбек для мутаций
  │   └── lastAction tracker - для undo
  ├── AiToolCallsList.vue - rich cards для каждого tool
  └── AiMessageContent.vue - markdown-текст ассистента

Каталог tools

Все tools живут в app/components/ai/tools/ и регистрируются через createAllTools().

Transactions

  • create_trn, create_adjustment, create_transfer
  • update_trn, delete_trn, duplicate_trn, bulk_delete_trns
  • list_trns, search_trns, undo_last_action

Wallets

  • create_wallet, update_wallet, delete_wallet, list_wallets
  • archive_wallet, unarchive_wallet, set_wallet_exclude_in_total, reorder_wallets

Categories

  • create_category, update_category, delete_category, list_categories

Analytics

  • get_summary, get_wallet_stats, compare_periods

Settings

  • set_base_currency, set_locale, get_settings, set_theme

Системный промпт

В useSystemPrompt.ts. Ключевые правила:

  • Tool-first: любой фактический вопрос о кошельках/категориях/транзакциях ВСЕГДА через tool, не из памяти
  • Multi-turn: если не хватает полей - задать ОДИН уточняющий вопрос; на следующем turn смержить данные
  • Chain-паттерн: delete_trn/update_trn/duplicate_trn требуют сначала list_trns для получения id
  • FORBIDDEN: выдумывать id, просить у пользователя id, переиспользовать старые tool results
  • DATE RULE: не передавать date если пользователь не сказал явно

Сжатие контекста

pruneContext.ts - скользящее окно + сжатие tool results:

  • maxContextTurns (default 6) - в модель идут только последние N пар user+assistant
  • compressToolsAfterTurns (default 1) - старые tool_calls сжимаются в строку [tool_name: summary]

Это убирает "я уже сделал X" галлюцинации в длинных сессиях.

AutoRetry + client fallback

useAiChat.ts:

  1. autoRetry - если tool вернул error / ok: false, отправить retry с реальным id из list_trns
  2. executeFallback - если модель всё равно тупит на chain'ах, клиент САМ выполняет мутацию и инжектирует результат как синтетический tool_call

Auto-clear при простое

idleMinutesBeforeClear (default 30) - если чат был неактивен N минут, история очищается на следующем send().

Рекомендации моделей

МодельСкоростьToolsЗаметки
gemma4:latestfastexcellentПо умолчанию. Баланс скорость/точность
gemma4:26bmediumgoodДля RAM-богатых, выше точность
qwen3:8bfastexcellentАльтернатива с похожими характеристиками

См. modelMeta.ts.

Настройки

Все настройки в aiStore.settings:

  • model, baseUrl, think, autoRetry, debug
  • maxContextTurns, compressToolsAfterTurns, idleMinutesBeforeClear

Доступны через popover в шапке чата.

Известные ограничения

  • Multi-turn create_trn зависит от модели. На gemma4:latest сборка транзакции через уточнения (юзер: "купил кофе" → ассистент спрашивает сумму/кошелёк → вызывает create_trn) работает примерно в 70% попыток. Модель иногда кладёт ответ не в то поле (например "наличными" в description вместо wallet) или забывает type. Для надёжного multi-turn - qwen3:8b или давать все поля сразу.
  • Chain-tools (duplicate_trn, delete_trn, update_trn) на gemma4:latest в длинных сессиях - модель иногда отвечает текстом и просит id вместо list_trns → мутация. Это компенсировано client-side fallback'ом (executeFallback в useAiChat.ts), который напрямую исполняет мутацию и инжектирует синтетический tool_call.
  • Галлюцинация "я уже это сделал" может вернуться при большом maxContextTurns. Default compressToolsAfterTurns: 1 сжимает старые результаты для предотвращения; выше 8-10 turns риск возвращается.
  • Зависимость от локальной сети. Если Ollama не запущена на baseUrl, чат не работает. Remote fallback отсутствует по дизайну - всё inference локально.
  • Validation errors из @tanstack/ai приходят с state: 'done' (не error) и { error: "..." } в результате. autoRetry распознаёт их через helper isTcFailed по JSON результата.
  • Нет массовой перестановки категорий - useCategoriesStore не предоставляет saveCategoriesOrder, AI не может менять порядок категорий.

Добавление нового tool

  1. Создать app/components/ai/tools/<name>.ts с create*Tools()
  2. Использовать toolDefinition({ description, inputSchema, name }) из @tanstack/ai
  3. Вернуть полную форму результата (с ok: true на успех)
  4. Зарегистрировать в tools/index.ts
  5. Добавить rich card в AiToolCallsList.richKind()
  6. Обновить useSystemPrompt.ts правилом
  7. Добавить bench case в /tmp/ai-bench.mjs