stuff
10
.cursor/rules/auto-fix.mdc
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
description: For testing and debugging
|
||||
alwaysApply: false
|
||||
---
|
||||
When rule is mentioned:
|
||||
1. Run mnemo_cards_web/complete_auto_debug.sh
|
||||
2. Wait for it to complete
|
||||
3. Read and analyse the report in mnemo_cards_web/debug_report
|
||||
4. Fix the errors
|
||||
5. Go to step 1
|
||||
64
.cursor/rules/auto_work.mdc
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Title: Autonomy & Context-Budget Control
|
||||
# Scope: project
|
||||
# Objective: Run tasks end-to-end without pausing for questions; never exhaust context; recover state after long runs.
|
||||
|
||||
## Operating Mode
|
||||
- Operate as an IMPLEMENTER, not an advisor. Execute changes directly in the codebase, run tests, and iterate until the task is completed.
|
||||
- Do NOT ask for confirmation. If information is missing, make the safest reasonable assumption, record it in `workflow_state.md` under **ASSUMPTIONS**, and proceed.
|
||||
- Only stop when:
|
||||
1) all acceptance checks pass, or
|
||||
2) an explicit denylisted command is required, or
|
||||
3) a hard error cannot be bypassed without human credentials.
|
||||
|
||||
## Files for Long-Run State
|
||||
- Maintain two small, bounded files at repo root:
|
||||
- `project_config.md`: non-volatile goals, constraints, stack specifics, commands to run, and acceptance criteria.
|
||||
- `workflow_state.md`: volatile loop state with **PLAN**, **NEXT_ACTIONS**, **ASSUMPTIONS**, **PROGRESS_LOG** (tail ≤ 200 lines), and **OPEN_ISSUES**.
|
||||
- Always read from `project_config.md` first, then `workflow_state.md`. If absent, create them with reasonable defaults and proceed.
|
||||
|
||||
## Autonomous Loop (each cycle)
|
||||
1) **Read**: Load only the smallest necessary snippets. Never paste whole files into chat.
|
||||
2) **Plan**: Update `workflow_state.md` → **PLAN** and **NEXT_ACTIONS** (≤ 150 tokens combined).
|
||||
3) **Act**:
|
||||
- Apply file edits directly via the editor. Prefer targeted diffs and surgical refactors.
|
||||
- Run fast checks (format, lint, typecheck, unit tests) as configured in `project_config.md`.
|
||||
4) **Verify**:
|
||||
- Parse outputs; extract only the essential signals back into `workflow_state.md` (**PROGRESS_LOG** summary ≤ 100 tokens per step).
|
||||
- If failures occur, append a brief root-cause hypothesis and loop.
|
||||
5) **Trim**:
|
||||
- Aggressively prune prior chat messages by relying on the two state files instead of conversational memory.
|
||||
- Summarize long logs into bullet points; never paste raw logs > 30 lines.
|
||||
6) **Repeat** until acceptance criteria met. Then write a concise **DONE** report in `workflow_state.md`.
|
||||
|
||||
## Context Budget Rules
|
||||
- Hard token guardrails:
|
||||
- Summarize code context instead of inlining it. Reference files and line ranges.
|
||||
- For large diffs, chunk work by directory or component; complete and verify one chunk before opening the next.
|
||||
- Never quote third-party library code. Link the package name and version only.
|
||||
- On every loop, if the visible conversation > ~1–2 screens, summarize to `workflow_state.md` and clear prior verbose messages by continuing with short references.
|
||||
|
||||
## Execution & Safety
|
||||
- Auto-run commands that are safe and reversible (formatters, linters, unit tests, local dev server).
|
||||
- If a command appears on the **DENYLIST** (see below), do NOT execute. Instead, write an **INTENT** note in `workflow_state.md` asking for approval, then continue with other work.
|
||||
- Prefer idempotent scripts and atomic commits to prevent partial breakage.
|
||||
|
||||
## Reporting Style
|
||||
- Communicate progress inside `workflow_state.md` only.
|
||||
- In chat, respond with a one-paragraph status and the next concrete action, no tutorials.
|
||||
|
||||
## DENYLIST (edit per project)
|
||||
- Global destructive actions: `rm -rf /`, `sudo`, `diskutil eraseDisk`, cloud destroy commands (e.g., `terraform destroy`, `pulumi destroy`, `gcloud compute instances delete` without `--dry-run`), production DB migrations, any command targeting prod environments.
|
||||
- Network or credentialed actions unless sandboxed.
|
||||
- Cost-incurring benchmarks or load tests without limits.
|
||||
|
||||
## Minimal Acceptance Checklist (default)
|
||||
- Builds successfully.
|
||||
- Linters/type checks pass.
|
||||
- All existing tests pass; new tests cover new behavior.
|
||||
- README or `project_config.md` updated for any new command, env var, or migration.
|
||||
|
||||
Use timeouts when running continous actions like launching or compiling the app.
|
||||
4
.cursor/rules/mnemo-cards-web.mdc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
mnemo_cards_web_v2 is a web app. It should only run on web! Not mobile or macos. And tests should also only run on web!
|
||||
9
.cursor/rules/write-tests.mdc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
Use yx_state and yx_scope,
|
||||
Follow clean architecture techique,
|
||||
You can use ../mnemo_cards app as a design and product features reference, but NEVER copy its architecture.
|
||||
When you finish with another step:
|
||||
Write unit tests for all the functionalities
|
||||
Update PROGESS.md and TODO.md
|
||||
45
.forgejo/workflows/agent.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: agent-issue-to-pr
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
|
||||
jobs:
|
||||
agent:
|
||||
if: github.event.label.name == 'agent:do'
|
||||
runs-on: docker
|
||||
container: python:3.12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Aider
|
||||
run: pip install --upgrade pip aider-chat
|
||||
|
||||
- name: Create branch
|
||||
id: mkbr
|
||||
run: |
|
||||
BR="agent/${{ github.event.issue.number }}"
|
||||
echo "BR=$BR" >> $GITHUB_ENV
|
||||
git checkout -b "$BR"
|
||||
git config user.name "forgejo-actions[bot]"
|
||||
git config user.email "actions@forgejo.local"
|
||||
|
||||
- name: Run agent (write code + tests)
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
aider --yes --message "Issue #${{ github.event.issue.number }} — ${{ github.event.issue.title }}.
|
||||
Сделай минимальный патч и тесты. CI не трогай." .
|
||||
git add -A
|
||||
git commit -m "agent: implement #${{ github.event.issue.number }}" || true
|
||||
git push -u origin "$BR"
|
||||
|
||||
- name: Open PR via API
|
||||
env:
|
||||
API: ${{ github.api_url }}
|
||||
REPO: ${{ github.repository }}
|
||||
TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
curl -sS -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"head":"'"$BR"'","base":"main","title":"Agent PR for #'"${{ github.event.issue.number }}"'","body":"Автогенерация по issue."}' \
|
||||
"$API/repos/$REPO/pulls"
|
||||
14
.metadata
|
|
@ -4,8 +4,8 @@
|
|||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e"
|
||||
channel: "stable"
|
||||
revision: "3e493a3e4d0a5c99fa7da51faae354e95a9a1abe"
|
||||
channel: "beta"
|
||||
|
||||
project_type: app
|
||||
|
||||
|
|
@ -13,11 +13,11 @@ project_type: app
|
|||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e
|
||||
base_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e
|
||||
- platform: macos
|
||||
create_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e
|
||||
base_revision: 6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e
|
||||
create_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
base_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
- platform: web
|
||||
create_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
base_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
|
||||
# User provided section
|
||||
|
||||
|
|
|
|||
373
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "funny_letters",
|
||||
"cwd": "funny_letters",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "funny_letters (profile mode)",
|
||||
"cwd": "funny_letters",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "funny_letters (release mode)",
|
||||
"cwd": "funny_letters",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "games",
|
||||
"cwd": "games",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "games (profile mode)",
|
||||
"cwd": "games",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "games (release mode)",
|
||||
"cwd": "games",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards",
|
||||
"cwd": "mnemo_cards",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards (profile mode)",
|
||||
"cwd": "mnemo_cards",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards (release mode)",
|
||||
"cwd": "mnemo_cards",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_backend",
|
||||
"cwd": "mnemo_cards_backend",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_common",
|
||||
"cwd": "mnemo_cards_common",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_common_backend",
|
||||
"cwd": "mnemo_cards_common_backend",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_frontend_common",
|
||||
"cwd": "mnemo_cards_frontend_common",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_frontend_common (profile mode)",
|
||||
"cwd": "mnemo_cards_frontend_common",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_frontend_common (release mode)",
|
||||
"cwd": "mnemo_cards_frontend_common",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_snake_game",
|
||||
"cwd": "mnemo_cards_snake_game",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_snake_game (profile mode)",
|
||||
"cwd": "mnemo_cards_snake_game",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_snake_game (release mode)",
|
||||
"cwd": "mnemo_cards_snake_game",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_telegram_bot",
|
||||
"cwd": "mnemo_cards_telegram_bot",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_web",
|
||||
"cwd": "mnemo_cards_web",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_web (profile mode)",
|
||||
"cwd": "mnemo_cards_web",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_web (release mode)",
|
||||
"cwd": "mnemo_cards_web",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "example",
|
||||
"cwd": "funny_letters/example",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "example (profile mode)",
|
||||
"cwd": "funny_letters/example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "example (release mode)",
|
||||
"cwd": "funny_letters/example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "ads_example",
|
||||
"cwd": "games/ads_example",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "ads_example (profile mode)",
|
||||
"cwd": "games/ads_example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "ads_example (release mode)",
|
||||
"cwd": "games/ads_example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_game_api",
|
||||
"cwd": "games/mnemo_cards_game_api",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_game_api (profile mode)",
|
||||
"cwd": "games/mnemo_cards_game_api",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_game_api (release mode)",
|
||||
"cwd": "games/mnemo_cards_game_api",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_web_bridge",
|
||||
"cwd": "games/mnemo_cards_web_bridge",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_web_bridge (profile mode)",
|
||||
"cwd": "games/mnemo_cards_web_bridge",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "mnemo_cards_web_bridge (release mode)",
|
||||
"cwd": "games/mnemo_cards_web_bridge",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "bus_word_game",
|
||||
"cwd": "games/apps/bus_word_game",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "bus_word_game (profile mode)",
|
||||
"cwd": "games/apps/bus_word_game",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "bus_word_game (release mode)",
|
||||
"cwd": "games/apps/bus_word_game",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "host_app",
|
||||
"cwd": "games/apps/host_app",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "host_app (profile mode)",
|
||||
"cwd": "games/apps/host_app",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "host_app (release mode)",
|
||||
"cwd": "games/apps/host_app",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "web_app1",
|
||||
"cwd": "games/apps/web_app1",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "web_app1 (profile mode)",
|
||||
"cwd": "games/apps/web_app1",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "web_app1 (release mode)",
|
||||
"cwd": "games/apps/web_app1",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "bridge_core",
|
||||
"cwd": "games/packages/bridge_core",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "bridge_core (profile mode)",
|
||||
"cwd": "games/packages/bridge_core",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "bridge_core (release mode)",
|
||||
"cwd": "games/packages/bridge_core",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "payloads_app1",
|
||||
"cwd": "games/packages/payloads_app1",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "payloads_app1 (profile mode)",
|
||||
"cwd": "games/packages/payloads_app1",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "payloads_app1 (release mode)",
|
||||
"cwd": "games/packages/payloads_app1",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "payloads_host",
|
||||
"cwd": "games/packages/payloads_host",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "payloads_host (profile mode)",
|
||||
"cwd": "games/packages/payloads_host",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "payloads_host (release mode)",
|
||||
"cwd": "games/packages/payloads_host",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "payloads_shared",
|
||||
"cwd": "games/packages/payloads_shared",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "payloads_shared (profile mode)",
|
||||
"cwd": "games/packages/payloads_shared",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "payloads_shared (release mode)",
|
||||
"cwd": "games/packages/payloads_shared",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
}
|
||||
]
|
||||
}
|
||||
219
README.md
|
|
@ -1,16 +1,215 @@
|
|||
# mnemo_cards
|
||||
# Mnemo Cards - Мобильное приложение для изучения языков
|
||||
|
||||
Mnemo
|
||||
Полнофункциональная система для изучения языков, включающая мобильное приложение, веб-сервер, телеграм-бот и веб-игры.
|
||||
|
||||
## Getting Started
|
||||
## 🏗️ Структура проекта
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
Проект состоит из нескольких взаимосвязанных компонентов:
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
### 📱 Мобильное приложение (`mnemo_cards/`)
|
||||
Основное Flutter приложение для iOS и Android с функциями:
|
||||
- Изучение языков через карточки
|
||||
- Интеграция с Firebase (Firestore, Analytics, Crashlytics)
|
||||
- Система покупок (YooKassa, In-App Purchase)
|
||||
- Реклама (Yandex Mobile Ads)
|
||||
- Локальные уведомления
|
||||
- Интеграция с веб-играми через WebView
|
||||
- Поддержка Telegram Web App
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
**Технологии:** Flutter, BLoC, Auto Route, Firebase, WebView
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
### 🌐 Веб-приложение (`mnemo_cards_web/`)
|
||||
Веб-версия Flutter приложения с функциями:
|
||||
- Изучение языков через карточки в браузере
|
||||
- Интеграция с Firebase (Firestore, Analytics, Crashlytics)
|
||||
- Адаптивный дизайн для различных устройств
|
||||
- Синхронизация данных с мобильным приложением
|
||||
- Поддержка PWA (Progressive Web App)
|
||||
- Интеграция с веб-играми
|
||||
|
||||
**Технологии:** Flutter Web, BLoC, Auto Route, Firebase, PWA
|
||||
|
||||
### 🔧 Общие компоненты (`mnemo_cards_common/`)
|
||||
Общие DTO и утилиты для мобильного приложения:
|
||||
- Модели данных
|
||||
- Сетевые утилиты
|
||||
- JSON сериализация
|
||||
- Криптографические функции
|
||||
|
||||
### 🖥️ Backend сервер (`mnemo_cards_backend/`)
|
||||
Dart сервер на базе Shelf framework:
|
||||
- REST API для мобильного приложения
|
||||
- База данных Isar
|
||||
- Интеграция с Google APIs
|
||||
- Система платежей YooKassa
|
||||
- Периодические задачи
|
||||
- Swagger документация
|
||||
|
||||
**Технологии:** Dart, Shelf, Isar, Google APIs
|
||||
|
||||
### 🔧 Backend общие компоненты (`mnemo_cards_common_backend/`)
|
||||
Общие компоненты для backend:
|
||||
- Модели данных
|
||||
- Утилиты для работы с Isar
|
||||
- Общие бизнес-логика
|
||||
|
||||
### 🤖 Telegram бот (`mnemo_cards_telegram_bot/`)
|
||||
Telegram бот для управления приложением:
|
||||
- Административные функции
|
||||
- Распространение APK файлов
|
||||
- Управление пользователями
|
||||
- Интеграция с основным backend
|
||||
|
||||
**Технологии:** Dart, Teledart, Isar
|
||||
|
||||
### 🎮 Веб-игры и WebView Bridge (`games/`)
|
||||
Система веб-игр с безопасной коммуникацией через WebView:
|
||||
- **Host приложение** - Flutter приложение-контейнер
|
||||
- **Web приложения** - Flutter Web игры (bus_word_game, web_app1)
|
||||
- **Bridge Core** - система безопасной коммуникации
|
||||
- **Payloads** - типизированные сообщения между приложениями
|
||||
|
||||
**Архитектура:**
|
||||
- Изолированные payload'ы для каждого приложения
|
||||
- JavaScript bridge для WebView коммуникации
|
||||
- Типобезопасная обработка сообщений
|
||||
- Поддержка сложных сценариев (авторизация, quiz, нативные диалоги)
|
||||
|
||||
**Технологии:** Flutter Web, WebView, JavaScript Bridge, Melos
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### Мобильное приложение
|
||||
```bash
|
||||
cd mnemo_cards
|
||||
flutter pub get
|
||||
flutter run
|
||||
```
|
||||
|
||||
### Веб-приложение
|
||||
```bash
|
||||
cd mnemo_cards_web
|
||||
flutter pub get
|
||||
flutter run -d chrome
|
||||
```
|
||||
|
||||
### Backend сервер
|
||||
```bash
|
||||
cd mnemo_cards_backend
|
||||
dart pub get
|
||||
dart run
|
||||
```
|
||||
|
||||
### Telegram бот
|
||||
```bash
|
||||
cd mnemo_cards_telegram_bot
|
||||
dart pub get
|
||||
dart run
|
||||
```
|
||||
|
||||
### Веб-игры
|
||||
```bash
|
||||
cd games
|
||||
melos bootstrap
|
||||
cd apps/host_app
|
||||
flutter run
|
||||
```
|
||||
|
||||
## 🔗 Зависимости между компонентами
|
||||
|
||||
```
|
||||
mnemo_cards
|
||||
├── mnemo_cards_common (DTO)
|
||||
├── games/mnemo_cards_web_bridge (WebView интеграция)
|
||||
└── Firebase/Backend API
|
||||
|
||||
mnemo_cards_web
|
||||
├── mnemo_cards_common (DTO)
|
||||
├── mnemo_cards_frontend_common (общие компоненты)
|
||||
├── games/payloads_shared (общие типы)
|
||||
└── Firebase/Backend API
|
||||
|
||||
mnemo_cards_backend
|
||||
├── mnemo_cards_common (общие DTO)
|
||||
├── mnemo_cards_common_backend (backend утилиты)
|
||||
└── Isar Database
|
||||
|
||||
mnemo_cards_telegram_bot
|
||||
├── mnemo_cards_common_backend
|
||||
└── Backend API
|
||||
|
||||
games/
|
||||
├── bridge_core (WebView коммуникация)
|
||||
├── payloads_shared (общие типы)
|
||||
└── apps/ (веб-приложения)
|
||||
```
|
||||
|
||||
## 📋 Основные функции
|
||||
|
||||
### Мобильное приложение
|
||||
- 📚 Изучение языков через карточки
|
||||
- 🎮 Интеграция с веб-играми
|
||||
- 💳 Система покупок и подписок
|
||||
- 📊 Аналитика и статистика
|
||||
- 🔔 Push уведомления
|
||||
- 🌐 Telegram Web App поддержка
|
||||
|
||||
### Веб-приложение
|
||||
- 📚 Изучение языков через карточки в браузере
|
||||
- 🎮 Интеграция с веб-играми
|
||||
- 💳 Система покупок и подписок
|
||||
- 📊 Аналитика и статистика
|
||||
- 🔄 Синхронизация с мобильным приложением
|
||||
- 📱 PWA поддержка для установки на устройство
|
||||
|
||||
### Backend
|
||||
- 🔐 Аутентификация и авторизация
|
||||
- 💾 Управление данными пользователей
|
||||
- 💰 Обработка платежей
|
||||
- 📈 Аналитика и метрики
|
||||
- 🔄 Периодические задачи
|
||||
- 📚 API для мобильного приложения
|
||||
|
||||
### Telegram бот
|
||||
- 👨💼 Административные функции
|
||||
- 📱 Распространение APK
|
||||
- 👥 Управление пользователями
|
||||
- 📊 Мониторинг системы
|
||||
|
||||
### Веб-игры
|
||||
- 🎯 Интерактивные игры для изучения языков
|
||||
- 🔗 Безопасная интеграция с мобильным приложением
|
||||
- 📊 Отслеживание прогресса
|
||||
- 🎨 Современный UI/UX
|
||||
|
||||
## 🛠️ Технологический стек
|
||||
|
||||
- **Frontend:** Flutter, Flutter Web
|
||||
- **Backend:** Dart, Shelf, Isar
|
||||
- **База данных:** Isar (локальная), Firestore (облачная)
|
||||
- **Аутентификация:** Firebase Auth, Google Sign-In
|
||||
- **Платежи:** YooKassa, In-App Purchase
|
||||
- **Аналитика:** Firebase Analytics, Crashlytics
|
||||
- **Реклама:** Yandex Mobile Ads
|
||||
- **Уведомления:** Firebase Messaging, Local Notifications
|
||||
- **Коммуникация:** WebView Bridge, REST API
|
||||
- **Мониторинг:** Telegram Bot
|
||||
|
||||
## 📱 Поддерживаемые платформы
|
||||
|
||||
- **iOS** - нативное приложение
|
||||
- **Android** - нативное приложение
|
||||
- **Web** - Flutter Web приложение и игры
|
||||
- **Telegram** - Web App интеграция
|
||||
|
||||
## 🔒 Безопасность
|
||||
|
||||
- Шифрование данных
|
||||
- Безопасная WebView коммуникация
|
||||
- Защита от root/jailbreak
|
||||
- Валидация платежей
|
||||
- Изоляция payload'ов в веб-играх
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Проект является приватным и не предназначен для публикации.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 36 KiB |
175
chat/mnemo_cards_chat/README.md
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
# mnemo_cards_chat
|
||||
|
||||
A Flutter package providing chat functionality with LLM integration for mnemo_cards applications.
|
||||
|
||||
## Features
|
||||
|
||||
- 💬 **Text Chat**: Send and receive text messages with LLM
|
||||
- 🎤 **Audio Messages**: Record and playback voice messages
|
||||
- 🔄 **Reactive State**: State management with yx_state
|
||||
- 🏗️ **Clean Architecture**: Modular design with dependency injection
|
||||
- 🔌 **Extensible**: Abstract ChatRepository interface for different backends
|
||||
|
||||
## Installation
|
||||
|
||||
Add to your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
mnemo_cards_chat:
|
||||
path: packages/mnemo_cards_chat
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup
|
||||
|
||||
```dart
|
||||
import 'package:mnemo_cards_chat/mnemo_cards_chat.dart';
|
||||
|
||||
// 1. Implement ChatRepository for your backend
|
||||
class MyChatRepository implements ChatRepository {
|
||||
// Implement all ChatRepository methods
|
||||
// e.g., sendTextMessage, getChatMessages, etc.
|
||||
}
|
||||
|
||||
// 2. Create chat service
|
||||
final chatService = ChatService(chatRepository: MyChatRepository());
|
||||
|
||||
// 3. Create state manager
|
||||
final chatStateManager = ChatStateManager(chatService: chatService);
|
||||
|
||||
// 4. Use in your UI
|
||||
// See ChatStateManager for available methods
|
||||
```
|
||||
|
||||
### Dependency Injection with yx_scope
|
||||
|
||||
```dart
|
||||
import 'package:yx_scope/yx_scope.dart';
|
||||
import 'package:mnemo_cards_chat/mnemo_cards_chat.dart';
|
||||
|
||||
class ChatModule extends ScopeModule<UserScopeContainer> {
|
||||
ChatModule(super.container);
|
||||
|
||||
// Chat repository adapter
|
||||
late final chatRepositoryDep = dep(
|
||||
() => ChatRepositoryAdapter(container.httpRepository),
|
||||
);
|
||||
|
||||
// Chat service
|
||||
late final chatServiceDep = dep(
|
||||
() => ChatService(chatRepository: chatRepositoryDep.get),
|
||||
);
|
||||
|
||||
// Chat state manager
|
||||
late final chatStateManagerDep = dep(
|
||||
() => ChatStateManager(chatService: chatServiceDep.get),
|
||||
);
|
||||
|
||||
ChatStateManager get chatStateManager => chatStateManagerDep.get;
|
||||
}
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```dart
|
||||
// Initialize chat session
|
||||
await chatStateManager.initializeChat('session_id');
|
||||
|
||||
// Send text message
|
||||
await chatStateManager.sendTextMessage('Hello, world!');
|
||||
|
||||
// Send audio message
|
||||
final audioData = Uint8List.fromList([...]);
|
||||
await chatStateManager.sendAudioMessage(audioData, Duration(seconds: 5));
|
||||
|
||||
// Listen to state changes
|
||||
chatStateManager.addListener(() {
|
||||
final state = chatStateManager.state;
|
||||
if (state is ChatStateLoaded) {
|
||||
// Handle loaded state
|
||||
print('Messages: ${state.messages.length}');
|
||||
} else if (state is ChatStateError) {
|
||||
// Handle error
|
||||
print('Error: ${state.message}');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **`ChatService`**: Business logic and API orchestration
|
||||
- **`ChatStateManager`**: Reactive state management
|
||||
- **`ChatRepository`**: Abstract interface for backend communication
|
||||
- **Models**: Data structures for messages, sessions, and participants
|
||||
|
||||
### State Types
|
||||
|
||||
```dart
|
||||
// Loading state
|
||||
const ChatStateLoading()
|
||||
|
||||
// Loaded state with session data
|
||||
ChatStateLoaded(
|
||||
session: session,
|
||||
messages: messages,
|
||||
isSendingMessage: false,
|
||||
errorMessage: null,
|
||||
)
|
||||
|
||||
// Error state
|
||||
ChatStateError('Failed to load chat')
|
||||
```
|
||||
|
||||
## Models
|
||||
|
||||
### ChatMessage
|
||||
Union type for different message types:
|
||||
- `ChatMessage.text(TextMessage)` - Text messages
|
||||
- `ChatMessage.audio(AudioMessage)` - Audio messages
|
||||
|
||||
### ChatParticipant
|
||||
Represents chat participants:
|
||||
- `ChatParticipant.user()` - Human users
|
||||
- `ChatParticipant.assistant()` - AI assistants
|
||||
|
||||
### ChatSession
|
||||
Represents chat conversation sessions with metadata.
|
||||
|
||||
## API Integration
|
||||
|
||||
Implement `ChatRepository` to connect with your backend:
|
||||
|
||||
```dart
|
||||
abstract class ChatRepository {
|
||||
Future<ChatBasicSession> createChatSession(CreateChatSessionRequest request);
|
||||
Future<List<ChatBasicSession>> getChatSessions({int limit, String? afterSessionId});
|
||||
Future<ChatBasicSession> getChatSession(String sessionId);
|
||||
Future<ChatMessageResponse> sendTextMessage(SendTextMessageRequest request);
|
||||
Future<ChatMessageResponse> sendAudioMessage(String sessionId, dynamic audioData, Duration duration, {String? fileName, String? mimeType});
|
||||
Future<List<ChatMessageResponse>> getChatMessages(String sessionId, {int limit, String? beforeMessageId});
|
||||
Future<ChatBasicSession> updateChatSession(String sessionId, Map<String, dynamic> updates);
|
||||
Future<void> deleteChatSession(String sessionId);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run tests:
|
||||
```bash
|
||||
flutter test
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Follow the existing code style
|
||||
2. Add tests for new features
|
||||
3. Update documentation
|
||||
4. Ensure all tests pass
|
||||
|
||||
## License
|
||||
|
||||
This package is part of the mnemo_cards project.
|
||||
61
chat/mnemo_cards_chat/analysis_options.yaml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
|
||||
exclude:
|
||||
- '**/*.g.dart'
|
||||
- '**/*.freezed.dart'
|
||||
|
||||
language:
|
||||
strict-casts: true
|
||||
strict-inference: true
|
||||
strict-raw-types: true
|
||||
|
||||
errors:
|
||||
# Treat missing required parameters as errors
|
||||
missing_required_param: error
|
||||
# Treat missing returns as errors
|
||||
missing_return: error
|
||||
# Treat invalid assignments as errors
|
||||
invalid_assignment: error
|
||||
|
||||
linter:
|
||||
rules:
|
||||
# Basic rules
|
||||
avoid_print: true
|
||||
prefer_const_constructors: true
|
||||
prefer_const_literals_to_create_immutables: true
|
||||
prefer_final_fields: true
|
||||
unnecessary_this: true
|
||||
sort_child_properties_last: true
|
||||
use_key_in_widget_constructors: true
|
||||
|
||||
# Type safety rules - NO DYNAMIC!
|
||||
avoid_dynamic_calls: true
|
||||
avoid_type_to_string: true
|
||||
implicit_call_tearoffs: true
|
||||
|
||||
# Additional type safety
|
||||
always_declare_return_types: true
|
||||
always_specify_types: false # Too verbose, but we have strict-inference
|
||||
type_annotate_public_apis: true
|
||||
|
||||
# Code quality
|
||||
always_use_package_imports: true
|
||||
avoid_empty_else: true
|
||||
avoid_relative_lib_imports: true
|
||||
avoid_slow_async_io: true
|
||||
cancel_subscriptions: true
|
||||
close_sinks: true
|
||||
no_adjacent_strings_in_list: true
|
||||
unnecessary_statements: true
|
||||
|
||||
# Style
|
||||
prefer_single_quotes: true
|
||||
require_trailing_commas: true
|
||||
|
||||
# Documentation
|
||||
package_api_docs: true
|
||||
public_member_api_docs: false # Can enable for stricter docs
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"format-version":[1,0,0],"native-assets":{}}
|
||||
BIN
chat/mnemo_cards_chat/build/unit_test_assets/AssetManifest.bin
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -0,0 +1 @@
|
|||
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]}]
|
||||
BIN
chat/mnemo_cards_chat/build/unit_test_assets/NOTICES.Z
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"format-version":[1,0,0],"native-assets":{}}
|
||||
37
chat/mnemo_cards_chat/lib/mnemo_cards_chat.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/// Chat module for mnemo_cards applications
|
||||
///
|
||||
/// Provides chat functionality with LLM integration supporting both text and audio messages.
|
||||
///
|
||||
/// ## Features
|
||||
/// - Text message chat with LLM
|
||||
/// - Audio message recording and playback
|
||||
/// - Session management
|
||||
/// - Reactive state management with yx_state
|
||||
/// - Clean architecture with yx_scope DI
|
||||
///
|
||||
/// ## Usage
|
||||
/// ```dart
|
||||
/// import 'package:mnemo_cards_chat/mnemo_cards_chat.dart';
|
||||
///
|
||||
/// // Register chat module in your scope
|
||||
/// final scope = UserScopeContainer()
|
||||
/// ..add(ChatModule(chatConfig: chatConfig));
|
||||
/// ```
|
||||
library mnemo_cards_chat;
|
||||
|
||||
export 'src/domain/models/chat_basic.dart';
|
||||
export 'src/domain/models/chat_message.dart';
|
||||
export 'src/domain/models/chat_session.dart';
|
||||
export 'src/domain/models/chat_models.dart';
|
||||
export 'src/domain/services/chat_repository.dart';
|
||||
export 'src/domain/services/chat_service.dart';
|
||||
export 'src/domain/state/chat_state_manager.dart';
|
||||
|
||||
// Re-export common types from chat_session for convenience
|
||||
export 'src/domain/models/chat_session.dart' show
|
||||
ChatSession,
|
||||
ChatSessionStatus,
|
||||
UpdateChatSessionRequest;
|
||||
|
||||
// Re-export common types from chat_api_simple for convenience
|
||||
export 'src/domain/models/chat_api_simple.dart';
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_api_simple.freezed.dart';
|
||||
part 'chat_api_simple.g.dart';
|
||||
|
||||
/// Request to send text message to chat
|
||||
@freezed
|
||||
class SendTextMessageRequest with _$SendTextMessageRequest {
|
||||
const factory SendTextMessageRequest({
|
||||
required String sessionId,
|
||||
required String content,
|
||||
}) = _SendTextMessageRequest;
|
||||
|
||||
factory SendTextMessageRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$SendTextMessageRequestFromJson(json);
|
||||
}
|
||||
|
||||
/// Response from chat API containing assistant's message
|
||||
@freezed
|
||||
class ChatMessageResponse with _$ChatMessageResponse {
|
||||
const factory ChatMessageResponse({
|
||||
required String messageId,
|
||||
required String sessionId,
|
||||
required String content,
|
||||
required String senderId,
|
||||
required DateTime timestamp,
|
||||
@Default('text') String messageType,
|
||||
}) = _ChatMessageResponse;
|
||||
|
||||
factory ChatMessageResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChatMessageResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Request to get chat messages for a session
|
||||
@freezed
|
||||
class GetChatMessagesRequest with _$GetChatMessagesRequest {
|
||||
const factory GetChatMessagesRequest({
|
||||
required String sessionId,
|
||||
@Default(50) int limit,
|
||||
}) = _GetChatMessagesRequest;
|
||||
|
||||
factory GetChatMessagesRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$GetChatMessagesRequestFromJson(json);
|
||||
}
|
||||
|
||||
/// Response containing list of chat messages
|
||||
@freezed
|
||||
class ChatMessagesResponse with _$ChatMessagesResponse {
|
||||
const factory ChatMessagesResponse({
|
||||
required List<ChatMessageResponse> messages,
|
||||
@Default(false) bool hasMore,
|
||||
}) = _ChatMessagesResponse;
|
||||
|
||||
factory ChatMessagesResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChatMessagesResponseFromJson(json);
|
||||
}
|
||||
|
||||
/// Request to create new chat session
|
||||
@freezed
|
||||
class CreateChatSessionRequest with _$CreateChatSessionRequest {
|
||||
const factory CreateChatSessionRequest({
|
||||
required String title,
|
||||
}) = _CreateChatSessionRequest;
|
||||
|
||||
factory CreateChatSessionRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$CreateChatSessionRequestFromJson(json);
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_api_simple.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$SendTextMessageRequestImpl _$$SendTextMessageRequestImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$SendTextMessageRequestImpl(
|
||||
sessionId: json['sessionId'] as String,
|
||||
content: json['content'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SendTextMessageRequestImplToJson(
|
||||
_$SendTextMessageRequestImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'sessionId': instance.sessionId,
|
||||
'content': instance.content,
|
||||
};
|
||||
|
||||
_$ChatMessageResponseImpl _$$ChatMessageResponseImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ChatMessageResponseImpl(
|
||||
messageId: json['messageId'] as String,
|
||||
sessionId: json['sessionId'] as String,
|
||||
content: json['content'] as String,
|
||||
senderId: json['senderId'] as String,
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
messageType: json['messageType'] as String? ?? 'text',
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatMessageResponseImplToJson(
|
||||
_$ChatMessageResponseImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'messageId': instance.messageId,
|
||||
'sessionId': instance.sessionId,
|
||||
'content': instance.content,
|
||||
'senderId': instance.senderId,
|
||||
'timestamp': instance.timestamp.toIso8601String(),
|
||||
'messageType': instance.messageType,
|
||||
};
|
||||
|
||||
_$GetChatMessagesRequestImpl _$$GetChatMessagesRequestImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$GetChatMessagesRequestImpl(
|
||||
sessionId: json['sessionId'] as String,
|
||||
limit: (json['limit'] as num?)?.toInt() ?? 50,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$GetChatMessagesRequestImplToJson(
|
||||
_$GetChatMessagesRequestImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'sessionId': instance.sessionId,
|
||||
'limit': instance.limit,
|
||||
};
|
||||
|
||||
_$ChatMessagesResponseImpl _$$ChatMessagesResponseImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ChatMessagesResponseImpl(
|
||||
messages: (json['messages'] as List<dynamic>)
|
||||
.map((e) => ChatMessageResponse.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
hasMore: json['hasMore'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatMessagesResponseImplToJson(
|
||||
_$ChatMessagesResponseImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'messages': instance.messages,
|
||||
'hasMore': instance.hasMore,
|
||||
};
|
||||
|
||||
_$CreateChatSessionRequestImpl _$$CreateChatSessionRequestImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$CreateChatSessionRequestImpl(title: json['title'] as String);
|
||||
|
||||
Map<String, dynamic> _$$CreateChatSessionRequestImplToJson(
|
||||
_$CreateChatSessionRequestImpl instance,
|
||||
) => <String, dynamic>{'title': instance.title};
|
||||
54
chat/mnemo_cards_chat/lib/src/domain/models/chat_basic.dart
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_basic.freezed.dart';
|
||||
part 'chat_basic.g.dart';
|
||||
|
||||
/// Basic chat message for initial implementation
|
||||
@freezed
|
||||
class ChatBasicMessage with _$ChatBasicMessage {
|
||||
const factory ChatBasicMessage({
|
||||
required String id,
|
||||
required String sessionId,
|
||||
required String content,
|
||||
required String senderId,
|
||||
required String senderName,
|
||||
required DateTime timestamp,
|
||||
@Default('text') String messageType,
|
||||
String? audioUrl,
|
||||
@Default(0) int durationMs,
|
||||
int? fileSize,
|
||||
}) = _ChatBasicMessage;
|
||||
|
||||
factory ChatBasicMessage.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChatBasicMessageFromJson(json);
|
||||
}
|
||||
|
||||
/// Basic chat session for initial implementation
|
||||
@freezed
|
||||
class ChatBasicSession with _$ChatBasicSession {
|
||||
const factory ChatBasicSession({
|
||||
required String id,
|
||||
required String userId,
|
||||
required String title,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
@Default('active') String status,
|
||||
String? description,
|
||||
@Default(0) int messageCount,
|
||||
}) = _ChatBasicSession;
|
||||
|
||||
factory ChatBasicSession.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChatBasicSessionFromJson(json);
|
||||
}
|
||||
|
||||
/// Request to send text message
|
||||
@freezed
|
||||
class SendTextMessageBasicRequest with _$SendTextMessageBasicRequest {
|
||||
const factory SendTextMessageBasicRequest({
|
||||
required String sessionId,
|
||||
required String content,
|
||||
}) = _SendTextMessageBasicRequest;
|
||||
|
||||
factory SendTextMessageBasicRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$SendTextMessageBasicRequestFromJson(json);
|
||||
}
|
||||
|
|
@ -0,0 +1,897 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'chat_basic.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
ChatBasicMessage _$ChatBasicMessageFromJson(Map<String, dynamic> json) {
|
||||
return _ChatBasicMessage.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ChatBasicMessage {
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
String get sessionId => throw _privateConstructorUsedError;
|
||||
String get content => throw _privateConstructorUsedError;
|
||||
String get senderId => throw _privateConstructorUsedError;
|
||||
String get senderName => throw _privateConstructorUsedError;
|
||||
DateTime get timestamp => throw _privateConstructorUsedError;
|
||||
String get messageType => throw _privateConstructorUsedError;
|
||||
String? get audioUrl => throw _privateConstructorUsedError;
|
||||
int get durationMs => throw _privateConstructorUsedError;
|
||||
int? get fileSize => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ChatBasicMessage to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ChatBasicMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ChatBasicMessageCopyWith<ChatBasicMessage> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ChatBasicMessageCopyWith<$Res> {
|
||||
factory $ChatBasicMessageCopyWith(
|
||||
ChatBasicMessage value,
|
||||
$Res Function(ChatBasicMessage) then,
|
||||
) = _$ChatBasicMessageCopyWithImpl<$Res, ChatBasicMessage>;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id,
|
||||
String sessionId,
|
||||
String content,
|
||||
String senderId,
|
||||
String senderName,
|
||||
DateTime timestamp,
|
||||
String messageType,
|
||||
String? audioUrl,
|
||||
int durationMs,
|
||||
int? fileSize,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ChatBasicMessageCopyWithImpl<$Res, $Val extends ChatBasicMessage>
|
||||
implements $ChatBasicMessageCopyWith<$Res> {
|
||||
_$ChatBasicMessageCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ChatBasicMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? sessionId = null,
|
||||
Object? content = null,
|
||||
Object? senderId = null,
|
||||
Object? senderName = null,
|
||||
Object? timestamp = null,
|
||||
Object? messageType = null,
|
||||
Object? audioUrl = freezed,
|
||||
Object? durationMs = null,
|
||||
Object? fileSize = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
sessionId: null == sessionId
|
||||
? _value.sessionId
|
||||
: sessionId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
content: null == content
|
||||
? _value.content
|
||||
: content // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
senderId: null == senderId
|
||||
? _value.senderId
|
||||
: senderId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
senderName: null == senderName
|
||||
? _value.senderName
|
||||
: senderName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
timestamp: null == timestamp
|
||||
? _value.timestamp
|
||||
: timestamp // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
messageType: null == messageType
|
||||
? _value.messageType
|
||||
: messageType // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
audioUrl: freezed == audioUrl
|
||||
? _value.audioUrl
|
||||
: audioUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
durationMs: null == durationMs
|
||||
? _value.durationMs
|
||||
: durationMs // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
fileSize: freezed == fileSize
|
||||
? _value.fileSize
|
||||
: fileSize // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ChatBasicMessageImplCopyWith<$Res>
|
||||
implements $ChatBasicMessageCopyWith<$Res> {
|
||||
factory _$$ChatBasicMessageImplCopyWith(
|
||||
_$ChatBasicMessageImpl value,
|
||||
$Res Function(_$ChatBasicMessageImpl) then,
|
||||
) = __$$ChatBasicMessageImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
String id,
|
||||
String sessionId,
|
||||
String content,
|
||||
String senderId,
|
||||
String senderName,
|
||||
DateTime timestamp,
|
||||
String messageType,
|
||||
String? audioUrl,
|
||||
int durationMs,
|
||||
int? fileSize,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ChatBasicMessageImplCopyWithImpl<$Res>
|
||||
extends _$ChatBasicMessageCopyWithImpl<$Res, _$ChatBasicMessageImpl>
|
||||
implements _$$ChatBasicMessageImplCopyWith<$Res> {
|
||||
__$$ChatBasicMessageImplCopyWithImpl(
|
||||
_$ChatBasicMessageImpl _value,
|
||||
$Res Function(_$ChatBasicMessageImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ChatBasicMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? sessionId = null,
|
||||
Object? content = null,
|
||||
Object? senderId = null,
|
||||
Object? senderName = null,
|
||||
Object? timestamp = null,
|
||||
Object? messageType = null,
|
||||
Object? audioUrl = freezed,
|
||||
Object? durationMs = null,
|
||||
Object? fileSize = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_$ChatBasicMessageImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
sessionId: null == sessionId
|
||||
? _value.sessionId
|
||||
: sessionId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
content: null == content
|
||||
? _value.content
|
||||
: content // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
senderId: null == senderId
|
||||
? _value.senderId
|
||||
: senderId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
senderName: null == senderName
|
||||
? _value.senderName
|
||||
: senderName // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
timestamp: null == timestamp
|
||||
? _value.timestamp
|
||||
: timestamp // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
messageType: null == messageType
|
||||
? _value.messageType
|
||||
: messageType // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
audioUrl: freezed == audioUrl
|
||||
? _value.audioUrl
|
||||
: audioUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
durationMs: null == durationMs
|
||||
? _value.durationMs
|
||||
: durationMs // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
fileSize: freezed == fileSize
|
||||
? _value.fileSize
|
||||
: fileSize // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ChatBasicMessageImpl implements _ChatBasicMessage {
|
||||
const _$ChatBasicMessageImpl({
|
||||
required this.id,
|
||||
required this.sessionId,
|
||||
required this.content,
|
||||
required this.senderId,
|
||||
required this.senderName,
|
||||
required this.timestamp,
|
||||
this.messageType = 'text',
|
||||
this.audioUrl,
|
||||
this.durationMs = 0,
|
||||
this.fileSize,
|
||||
});
|
||||
|
||||
factory _$ChatBasicMessageImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ChatBasicMessageImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String sessionId;
|
||||
@override
|
||||
final String content;
|
||||
@override
|
||||
final String senderId;
|
||||
@override
|
||||
final String senderName;
|
||||
@override
|
||||
final DateTime timestamp;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String messageType;
|
||||
@override
|
||||
final String? audioUrl;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int durationMs;
|
||||
@override
|
||||
final int? fileSize;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatBasicMessage(id: $id, sessionId: $sessionId, content: $content, senderId: $senderId, senderName: $senderName, timestamp: $timestamp, messageType: $messageType, audioUrl: $audioUrl, durationMs: $durationMs, fileSize: $fileSize)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ChatBasicMessageImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.sessionId, sessionId) ||
|
||||
other.sessionId == sessionId) &&
|
||||
(identical(other.content, content) || other.content == content) &&
|
||||
(identical(other.senderId, senderId) ||
|
||||
other.senderId == senderId) &&
|
||||
(identical(other.senderName, senderName) ||
|
||||
other.senderName == senderName) &&
|
||||
(identical(other.timestamp, timestamp) ||
|
||||
other.timestamp == timestamp) &&
|
||||
(identical(other.messageType, messageType) ||
|
||||
other.messageType == messageType) &&
|
||||
(identical(other.audioUrl, audioUrl) ||
|
||||
other.audioUrl == audioUrl) &&
|
||||
(identical(other.durationMs, durationMs) ||
|
||||
other.durationMs == durationMs) &&
|
||||
(identical(other.fileSize, fileSize) ||
|
||||
other.fileSize == fileSize));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
sessionId,
|
||||
content,
|
||||
senderId,
|
||||
senderName,
|
||||
timestamp,
|
||||
messageType,
|
||||
audioUrl,
|
||||
durationMs,
|
||||
fileSize,
|
||||
);
|
||||
|
||||
/// Create a copy of ChatBasicMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ChatBasicMessageImplCopyWith<_$ChatBasicMessageImpl> get copyWith =>
|
||||
__$$ChatBasicMessageImplCopyWithImpl<_$ChatBasicMessageImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ChatBasicMessageImplToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ChatBasicMessage implements ChatBasicMessage {
|
||||
const factory _ChatBasicMessage({
|
||||
required final String id,
|
||||
required final String sessionId,
|
||||
required final String content,
|
||||
required final String senderId,
|
||||
required final String senderName,
|
||||
required final DateTime timestamp,
|
||||
final String messageType,
|
||||
final String? audioUrl,
|
||||
final int durationMs,
|
||||
final int? fileSize,
|
||||
}) = _$ChatBasicMessageImpl;
|
||||
|
||||
factory _ChatBasicMessage.fromJson(Map<String, dynamic> json) =
|
||||
_$ChatBasicMessageImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get id;
|
||||
@override
|
||||
String get sessionId;
|
||||
@override
|
||||
String get content;
|
||||
@override
|
||||
String get senderId;
|
||||
@override
|
||||
String get senderName;
|
||||
@override
|
||||
DateTime get timestamp;
|
||||
@override
|
||||
String get messageType;
|
||||
@override
|
||||
String? get audioUrl;
|
||||
@override
|
||||
int get durationMs;
|
||||
@override
|
||||
int? get fileSize;
|
||||
|
||||
/// Create a copy of ChatBasicMessage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ChatBasicMessageImplCopyWith<_$ChatBasicMessageImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
ChatBasicSession _$ChatBasicSessionFromJson(Map<String, dynamic> json) {
|
||||
return _ChatBasicSession.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ChatBasicSession {
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
String get userId => throw _privateConstructorUsedError;
|
||||
String get title => throw _privateConstructorUsedError;
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
String get status => throw _privateConstructorUsedError;
|
||||
String? get description => throw _privateConstructorUsedError;
|
||||
int get messageCount => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ChatBasicSession to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ChatBasicSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ChatBasicSessionCopyWith<ChatBasicSession> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ChatBasicSessionCopyWith<$Res> {
|
||||
factory $ChatBasicSessionCopyWith(
|
||||
ChatBasicSession value,
|
||||
$Res Function(ChatBasicSession) then,
|
||||
) = _$ChatBasicSessionCopyWithImpl<$Res, ChatBasicSession>;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id,
|
||||
String userId,
|
||||
String title,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
String status,
|
||||
String? description,
|
||||
int messageCount,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ChatBasicSessionCopyWithImpl<$Res, $Val extends ChatBasicSession>
|
||||
implements $ChatBasicSessionCopyWith<$Res> {
|
||||
_$ChatBasicSessionCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ChatBasicSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? userId = null,
|
||||
Object? title = null,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
Object? status = null,
|
||||
Object? description = freezed,
|
||||
Object? messageCount = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
userId: null == userId
|
||||
? _value.userId
|
||||
: userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
title: null == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
status: null == status
|
||||
? _value.status
|
||||
: status // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
messageCount: null == messageCount
|
||||
? _value.messageCount
|
||||
: messageCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ChatBasicSessionImplCopyWith<$Res>
|
||||
implements $ChatBasicSessionCopyWith<$Res> {
|
||||
factory _$$ChatBasicSessionImplCopyWith(
|
||||
_$ChatBasicSessionImpl value,
|
||||
$Res Function(_$ChatBasicSessionImpl) then,
|
||||
) = __$$ChatBasicSessionImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
String id,
|
||||
String userId,
|
||||
String title,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
String status,
|
||||
String? description,
|
||||
int messageCount,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ChatBasicSessionImplCopyWithImpl<$Res>
|
||||
extends _$ChatBasicSessionCopyWithImpl<$Res, _$ChatBasicSessionImpl>
|
||||
implements _$$ChatBasicSessionImplCopyWith<$Res> {
|
||||
__$$ChatBasicSessionImplCopyWithImpl(
|
||||
_$ChatBasicSessionImpl _value,
|
||||
$Res Function(_$ChatBasicSessionImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ChatBasicSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? userId = null,
|
||||
Object? title = null,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
Object? status = null,
|
||||
Object? description = freezed,
|
||||
Object? messageCount = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$ChatBasicSessionImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
userId: null == userId
|
||||
? _value.userId
|
||||
: userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
title: null == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
status: null == status
|
||||
? _value.status
|
||||
: status // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
messageCount: null == messageCount
|
||||
? _value.messageCount
|
||||
: messageCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ChatBasicSessionImpl implements _ChatBasicSession {
|
||||
const _$ChatBasicSessionImpl({
|
||||
required this.id,
|
||||
required this.userId,
|
||||
required this.title,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.status = 'active',
|
||||
this.description,
|
||||
this.messageCount = 0,
|
||||
});
|
||||
|
||||
factory _$ChatBasicSessionImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ChatBasicSessionImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String userId;
|
||||
@override
|
||||
final String title;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String status;
|
||||
@override
|
||||
final String? description;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int messageCount;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatBasicSession(id: $id, userId: $userId, title: $title, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, description: $description, messageCount: $messageCount)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ChatBasicSessionImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.userId, userId) || other.userId == userId) &&
|
||||
(identical(other.title, title) || other.title == title) &&
|
||||
(identical(other.createdAt, createdAt) ||
|
||||
other.createdAt == createdAt) &&
|
||||
(identical(other.updatedAt, updatedAt) ||
|
||||
other.updatedAt == updatedAt) &&
|
||||
(identical(other.status, status) || other.status == status) &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
(identical(other.messageCount, messageCount) ||
|
||||
other.messageCount == messageCount));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
userId,
|
||||
title,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
status,
|
||||
description,
|
||||
messageCount,
|
||||
);
|
||||
|
||||
/// Create a copy of ChatBasicSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ChatBasicSessionImplCopyWith<_$ChatBasicSessionImpl> get copyWith =>
|
||||
__$$ChatBasicSessionImplCopyWithImpl<_$ChatBasicSessionImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ChatBasicSessionImplToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ChatBasicSession implements ChatBasicSession {
|
||||
const factory _ChatBasicSession({
|
||||
required final String id,
|
||||
required final String userId,
|
||||
required final String title,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
final String status,
|
||||
final String? description,
|
||||
final int messageCount,
|
||||
}) = _$ChatBasicSessionImpl;
|
||||
|
||||
factory _ChatBasicSession.fromJson(Map<String, dynamic> json) =
|
||||
_$ChatBasicSessionImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get id;
|
||||
@override
|
||||
String get userId;
|
||||
@override
|
||||
String get title;
|
||||
@override
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
String get status;
|
||||
@override
|
||||
String? get description;
|
||||
@override
|
||||
int get messageCount;
|
||||
|
||||
/// Create a copy of ChatBasicSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ChatBasicSessionImplCopyWith<_$ChatBasicSessionImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
SendTextMessageBasicRequest _$SendTextMessageBasicRequestFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) {
|
||||
return _SendTextMessageBasicRequest.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SendTextMessageBasicRequest {
|
||||
String get sessionId => throw _privateConstructorUsedError;
|
||||
String get content => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SendTextMessageBasicRequest to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of SendTextMessageBasicRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$SendTextMessageBasicRequestCopyWith<SendTextMessageBasicRequest>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SendTextMessageBasicRequestCopyWith<$Res> {
|
||||
factory $SendTextMessageBasicRequestCopyWith(
|
||||
SendTextMessageBasicRequest value,
|
||||
$Res Function(SendTextMessageBasicRequest) then,
|
||||
) =
|
||||
_$SendTextMessageBasicRequestCopyWithImpl<
|
||||
$Res,
|
||||
SendTextMessageBasicRequest
|
||||
>;
|
||||
@useResult
|
||||
$Res call({String sessionId, String content});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SendTextMessageBasicRequestCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends SendTextMessageBasicRequest
|
||||
>
|
||||
implements $SendTextMessageBasicRequestCopyWith<$Res> {
|
||||
_$SendTextMessageBasicRequestCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of SendTextMessageBasicRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? sessionId = null, Object? content = null}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
sessionId: null == sessionId
|
||||
? _value.sessionId
|
||||
: sessionId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
content: null == content
|
||||
? _value.content
|
||||
: content // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SendTextMessageBasicRequestImplCopyWith<$Res>
|
||||
implements $SendTextMessageBasicRequestCopyWith<$Res> {
|
||||
factory _$$SendTextMessageBasicRequestImplCopyWith(
|
||||
_$SendTextMessageBasicRequestImpl value,
|
||||
$Res Function(_$SendTextMessageBasicRequestImpl) then,
|
||||
) = __$$SendTextMessageBasicRequestImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String sessionId, String content});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$SendTextMessageBasicRequestImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$SendTextMessageBasicRequestCopyWithImpl<
|
||||
$Res,
|
||||
_$SendTextMessageBasicRequestImpl
|
||||
>
|
||||
implements _$$SendTextMessageBasicRequestImplCopyWith<$Res> {
|
||||
__$$SendTextMessageBasicRequestImplCopyWithImpl(
|
||||
_$SendTextMessageBasicRequestImpl _value,
|
||||
$Res Function(_$SendTextMessageBasicRequestImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of SendTextMessageBasicRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? sessionId = null, Object? content = null}) {
|
||||
return _then(
|
||||
_$SendTextMessageBasicRequestImpl(
|
||||
sessionId: null == sessionId
|
||||
? _value.sessionId
|
||||
: sessionId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
content: null == content
|
||||
? _value.content
|
||||
: content // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$SendTextMessageBasicRequestImpl
|
||||
implements _SendTextMessageBasicRequest {
|
||||
const _$SendTextMessageBasicRequestImpl({
|
||||
required this.sessionId,
|
||||
required this.content,
|
||||
});
|
||||
|
||||
factory _$SendTextMessageBasicRequestImpl.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$$SendTextMessageBasicRequestImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String sessionId;
|
||||
@override
|
||||
final String content;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SendTextMessageBasicRequest(sessionId: $sessionId, content: $content)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SendTextMessageBasicRequestImpl &&
|
||||
(identical(other.sessionId, sessionId) ||
|
||||
other.sessionId == sessionId) &&
|
||||
(identical(other.content, content) || other.content == content));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, sessionId, content);
|
||||
|
||||
/// Create a copy of SendTextMessageBasicRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$SendTextMessageBasicRequestImplCopyWith<_$SendTextMessageBasicRequestImpl>
|
||||
get copyWith =>
|
||||
__$$SendTextMessageBasicRequestImplCopyWithImpl<
|
||||
_$SendTextMessageBasicRequestImpl
|
||||
>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$SendTextMessageBasicRequestImplToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SendTextMessageBasicRequest
|
||||
implements SendTextMessageBasicRequest {
|
||||
const factory _SendTextMessageBasicRequest({
|
||||
required final String sessionId,
|
||||
required final String content,
|
||||
}) = _$SendTextMessageBasicRequestImpl;
|
||||
|
||||
factory _SendTextMessageBasicRequest.fromJson(Map<String, dynamic> json) =
|
||||
_$SendTextMessageBasicRequestImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get sessionId;
|
||||
@override
|
||||
String get content;
|
||||
|
||||
/// Create a copy of SendTextMessageBasicRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$SendTextMessageBasicRequestImplCopyWith<_$SendTextMessageBasicRequestImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_basic.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ChatBasicMessageImpl _$$ChatBasicMessageImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ChatBasicMessageImpl(
|
||||
id: json['id'] as String,
|
||||
sessionId: json['sessionId'] as String,
|
||||
content: json['content'] as String,
|
||||
senderId: json['senderId'] as String,
|
||||
senderName: json['senderName'] as String,
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
messageType: json['messageType'] as String? ?? 'text',
|
||||
audioUrl: json['audioUrl'] as String?,
|
||||
durationMs: (json['durationMs'] as num?)?.toInt() ?? 0,
|
||||
fileSize: (json['fileSize'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatBasicMessageImplToJson(
|
||||
_$ChatBasicMessageImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'sessionId': instance.sessionId,
|
||||
'content': instance.content,
|
||||
'senderId': instance.senderId,
|
||||
'senderName': instance.senderName,
|
||||
'timestamp': instance.timestamp.toIso8601String(),
|
||||
'messageType': instance.messageType,
|
||||
'audioUrl': instance.audioUrl,
|
||||
'durationMs': instance.durationMs,
|
||||
'fileSize': instance.fileSize,
|
||||
};
|
||||
|
||||
_$ChatBasicSessionImpl _$$ChatBasicSessionImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ChatBasicSessionImpl(
|
||||
id: json['id'] as String,
|
||||
userId: json['userId'] as String,
|
||||
title: json['title'] as String,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||
status: json['status'] as String? ?? 'active',
|
||||
description: json['description'] as String?,
|
||||
messageCount: (json['messageCount'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatBasicSessionImplToJson(
|
||||
_$ChatBasicSessionImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'userId': instance.userId,
|
||||
'title': instance.title,
|
||||
'createdAt': instance.createdAt.toIso8601String(),
|
||||
'updatedAt': instance.updatedAt.toIso8601String(),
|
||||
'status': instance.status,
|
||||
'description': instance.description,
|
||||
'messageCount': instance.messageCount,
|
||||
};
|
||||
|
||||
_$SendTextMessageBasicRequestImpl _$$SendTextMessageBasicRequestImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$SendTextMessageBasicRequestImpl(
|
||||
sessionId: json['sessionId'] as String,
|
||||
content: json['content'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SendTextMessageBasicRequestImplToJson(
|
||||
_$SendTextMessageBasicRequestImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'sessionId': instance.sessionId,
|
||||
'content': instance.content,
|
||||
};
|
||||
107
chat/mnemo_cards_chat/lib/src/domain/models/chat_message.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_message.freezed.dart';
|
||||
part 'chat_message.g.dart';
|
||||
|
||||
/// Base class for all chat messages
|
||||
@freezed
|
||||
class ChatMessage with _$ChatMessage {
|
||||
const factory ChatMessage.text(TextMessage message) = ChatMessageText;
|
||||
|
||||
const factory ChatMessage.audio(AudioMessage message) = ChatMessageAudio;
|
||||
|
||||
factory ChatMessage.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChatMessageFromJson(json);
|
||||
}
|
||||
|
||||
/// Text message in chat
|
||||
@freezed
|
||||
class TextMessage with _$TextMessage {
|
||||
const factory TextMessage({
|
||||
required String id,
|
||||
required String sessionId,
|
||||
required String content,
|
||||
required ChatParticipant sender,
|
||||
required DateTime timestamp,
|
||||
@Default(MessageStatus.sent) MessageStatus status,
|
||||
String? metadata,
|
||||
}) = _TextMessage;
|
||||
|
||||
factory TextMessage.fromJson(Map<String, dynamic> json) =>
|
||||
_$TextMessageFromJson(json);
|
||||
}
|
||||
|
||||
/// Audio message in chat
|
||||
@freezed
|
||||
class AudioMessage with _$AudioMessage {
|
||||
const factory AudioMessage({
|
||||
required String id,
|
||||
required String sessionId,
|
||||
required String audioUrl,
|
||||
required ChatParticipant sender,
|
||||
required DateTime timestamp,
|
||||
required Duration duration,
|
||||
required int fileSize, // in bytes
|
||||
@Default(MessageStatus.sent) MessageStatus status,
|
||||
String? transcription, // optional text transcription
|
||||
String? metadata,
|
||||
}) = _AudioMessage;
|
||||
|
||||
factory AudioMessage.fromJson(Map<String, dynamic> json) =>
|
||||
_$AudioMessageFromJson(json);
|
||||
}
|
||||
|
||||
/// Participant in chat conversation
|
||||
@freezed
|
||||
class ChatParticipant with _$ChatParticipant {
|
||||
const factory ChatParticipant.user({
|
||||
required String id,
|
||||
required String name,
|
||||
String? avatarUrl,
|
||||
}) = ChatParticipantUser;
|
||||
|
||||
const factory ChatParticipant.assistant({
|
||||
required String id,
|
||||
required String name,
|
||||
String? avatarUrl,
|
||||
String? model, // LLM model name
|
||||
}) = ChatParticipantAssistant;
|
||||
|
||||
factory ChatParticipant.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChatParticipantFromJson(json);
|
||||
}
|
||||
|
||||
/// Message delivery status
|
||||
enum MessageStatus {
|
||||
@JsonValue('sending')
|
||||
sending,
|
||||
|
||||
@JsonValue('sent')
|
||||
sent,
|
||||
|
||||
@JsonValue('delivered')
|
||||
delivered,
|
||||
|
||||
@JsonValue('error')
|
||||
error,
|
||||
}
|
||||
|
||||
/// Extension for MessageStatus with display properties
|
||||
extension MessageStatusExtension on MessageStatus {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case MessageStatus.sending:
|
||||
return 'Отправляется...';
|
||||
case MessageStatus.sent:
|
||||
return 'Отправлено';
|
||||
case MessageStatus.delivered:
|
||||
return 'Доставлено';
|
||||
case MessageStatus.error:
|
||||
return 'Ошибка';
|
||||
}
|
||||
}
|
||||
|
||||
bool get isError => this == MessageStatus.error;
|
||||
bool get isSending => this == MessageStatus.sending;
|
||||
bool get isDelivered => this == MessageStatus.delivered || this == MessageStatus.sent;
|
||||
}
|
||||
134
chat/mnemo_cards_chat/lib/src/domain/models/chat_message.g.dart
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_message.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ChatMessageTextImpl _$$ChatMessageTextImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ChatMessageTextImpl(
|
||||
TextMessage.fromJson(json['message'] as Map<String, dynamic>),
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatMessageTextImplToJson(
|
||||
_$ChatMessageTextImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'message': instance.message,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
_$ChatMessageAudioImpl _$$ChatMessageAudioImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ChatMessageAudioImpl(
|
||||
AudioMessage.fromJson(json['message'] as Map<String, dynamic>),
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatMessageAudioImplToJson(
|
||||
_$ChatMessageAudioImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'message': instance.message,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
_$TextMessageImpl _$$TextMessageImplFromJson(Map<String, dynamic> json) =>
|
||||
_$TextMessageImpl(
|
||||
id: json['id'] as String,
|
||||
sessionId: json['sessionId'] as String,
|
||||
content: json['content'] as String,
|
||||
sender: ChatParticipant.fromJson(json['sender'] as Map<String, dynamic>),
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
status:
|
||||
$enumDecodeNullable(_$MessageStatusEnumMap, json['status']) ??
|
||||
MessageStatus.sent,
|
||||
metadata: json['metadata'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$TextMessageImplToJson(_$TextMessageImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'sessionId': instance.sessionId,
|
||||
'content': instance.content,
|
||||
'sender': instance.sender,
|
||||
'timestamp': instance.timestamp.toIso8601String(),
|
||||
'status': _$MessageStatusEnumMap[instance.status]!,
|
||||
'metadata': instance.metadata,
|
||||
};
|
||||
|
||||
const _$MessageStatusEnumMap = {
|
||||
MessageStatus.sending: 'sending',
|
||||
MessageStatus.sent: 'sent',
|
||||
MessageStatus.delivered: 'delivered',
|
||||
MessageStatus.error: 'error',
|
||||
};
|
||||
|
||||
_$AudioMessageImpl _$$AudioMessageImplFromJson(Map<String, dynamic> json) =>
|
||||
_$AudioMessageImpl(
|
||||
id: json['id'] as String,
|
||||
sessionId: json['sessionId'] as String,
|
||||
audioUrl: json['audioUrl'] as String,
|
||||
sender: ChatParticipant.fromJson(json['sender'] as Map<String, dynamic>),
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
duration: Duration(microseconds: (json['duration'] as num).toInt()),
|
||||
fileSize: (json['fileSize'] as num).toInt(),
|
||||
status:
|
||||
$enumDecodeNullable(_$MessageStatusEnumMap, json['status']) ??
|
||||
MessageStatus.sent,
|
||||
transcription: json['transcription'] as String?,
|
||||
metadata: json['metadata'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AudioMessageImplToJson(_$AudioMessageImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'sessionId': instance.sessionId,
|
||||
'audioUrl': instance.audioUrl,
|
||||
'sender': instance.sender,
|
||||
'timestamp': instance.timestamp.toIso8601String(),
|
||||
'duration': instance.duration.inMicroseconds,
|
||||
'fileSize': instance.fileSize,
|
||||
'status': _$MessageStatusEnumMap[instance.status]!,
|
||||
'transcription': instance.transcription,
|
||||
'metadata': instance.metadata,
|
||||
};
|
||||
|
||||
_$ChatParticipantUserImpl _$$ChatParticipantUserImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ChatParticipantUserImpl(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
avatarUrl: json['avatarUrl'] as String?,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatParticipantUserImplToJson(
|
||||
_$ChatParticipantUserImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'avatarUrl': instance.avatarUrl,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
_$ChatParticipantAssistantImpl _$$ChatParticipantAssistantImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ChatParticipantAssistantImpl(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
avatarUrl: json['avatarUrl'] as String?,
|
||||
model: json['model'] as String?,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatParticipantAssistantImplToJson(
|
||||
_$ChatParticipantAssistantImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'avatarUrl': instance.avatarUrl,
|
||||
'model': instance.model,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
114
chat/mnemo_cards_chat/lib/src/domain/models/chat_models.dart
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/// Non-serializable chat models and utilities
|
||||
/// These models contain binary data or platform-specific types that cannot be JSON serialized
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// Request to send audio message to chat with binary data
|
||||
class SendAudioMessageData {
|
||||
const SendAudioMessageData({
|
||||
required this.sessionId,
|
||||
required this.audioData,
|
||||
required this.duration,
|
||||
this.fileName,
|
||||
this.mimeType,
|
||||
this.transcription,
|
||||
this.metadata,
|
||||
});
|
||||
|
||||
final String sessionId;
|
||||
final Uint8List audioData; // Binary audio data
|
||||
final Duration duration;
|
||||
final String? fileName;
|
||||
final String? mimeType; // e.g., 'audio/webm', 'audio/wav'
|
||||
final String? transcription;
|
||||
final String? metadata;
|
||||
|
||||
/// Create from web audio recording (Blob/ArrayBuffer)
|
||||
factory SendAudioMessageData.fromWebAudio({
|
||||
required String sessionId,
|
||||
required dynamic audioBlob, // Web Blob or ArrayBuffer
|
||||
required Duration duration,
|
||||
String? transcription,
|
||||
String? metadata,
|
||||
}) {
|
||||
// Convert to Uint8List (implementation depends on web_audio_api or similar)
|
||||
final audioData = Uint8List(0); // Placeholder - actual conversion needed
|
||||
return SendAudioMessageData(
|
||||
sessionId: sessionId,
|
||||
audioData: audioData,
|
||||
duration: duration,
|
||||
fileName: 'recording.webm',
|
||||
mimeType: 'audio/webm',
|
||||
transcription: transcription,
|
||||
metadata: metadata,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get file size in bytes
|
||||
int get fileSize => audioData.length;
|
||||
|
||||
/// Validate audio data
|
||||
bool get isValid => audioData.isNotEmpty && duration.inMilliseconds > 0;
|
||||
}
|
||||
|
||||
/// Audio recording state
|
||||
enum AudioRecordingState {
|
||||
idle,
|
||||
recording,
|
||||
paused,
|
||||
stopped,
|
||||
processing,
|
||||
error,
|
||||
}
|
||||
|
||||
/// Audio playback state
|
||||
enum AudioPlaybackState {
|
||||
idle,
|
||||
loading,
|
||||
playing,
|
||||
paused,
|
||||
stopped,
|
||||
error,
|
||||
}
|
||||
|
||||
/// Audio recording session information
|
||||
class AudioRecordingSession {
|
||||
AudioRecordingSession({
|
||||
required this.startTime,
|
||||
this.endTime,
|
||||
this.duration = Duration.zero,
|
||||
});
|
||||
|
||||
final DateTime startTime;
|
||||
DateTime? endTime;
|
||||
Duration duration;
|
||||
|
||||
bool get isActive => endTime == null;
|
||||
bool get isCompleted => endTime != null;
|
||||
|
||||
void stop() {
|
||||
endTime = DateTime.now();
|
||||
duration = endTime!.difference(startTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// Audio playback information
|
||||
class AudioPlaybackInfo {
|
||||
const AudioPlaybackInfo({
|
||||
required this.currentTime,
|
||||
required this.totalDuration,
|
||||
required this.isPlaying,
|
||||
required this.volume,
|
||||
});
|
||||
|
||||
final Duration currentTime;
|
||||
final Duration totalDuration;
|
||||
final bool isPlaying;
|
||||
final double volume; // 0.0 to 1.0
|
||||
|
||||
double get progress => totalDuration.inMilliseconds > 0
|
||||
? currentTime.inMilliseconds / totalDuration.inMilliseconds
|
||||
: 0.0;
|
||||
|
||||
bool get isCompleted => currentTime >= totalDuration;
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_session.freezed.dart';
|
||||
part 'chat_session.g.dart';
|
||||
|
||||
/// Chat session containing conversation between user and assistant
|
||||
@freezed
|
||||
class ChatSession with _$ChatSession {
|
||||
const factory ChatSession({
|
||||
required String id,
|
||||
required String userId,
|
||||
required String title,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
@Default(ChatSessionStatus.active) ChatSessionStatus status,
|
||||
String? description,
|
||||
@Default([]) List<String> tags,
|
||||
@Default(0) int messageCount,
|
||||
String? lastMessagePreview,
|
||||
DateTime? lastMessageAt,
|
||||
}) = _ChatSession;
|
||||
|
||||
factory ChatSession.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChatSessionFromJson(json);
|
||||
}
|
||||
|
||||
/// Status of chat session
|
||||
enum ChatSessionStatus {
|
||||
@JsonValue('active')
|
||||
active,
|
||||
|
||||
@JsonValue('archived')
|
||||
archived,
|
||||
|
||||
@JsonValue('deleted')
|
||||
deleted,
|
||||
}
|
||||
|
||||
/// Extension for ChatSessionStatus
|
||||
extension ChatSessionStatusExtension on ChatSessionStatus {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case ChatSessionStatus.active:
|
||||
return 'Активный';
|
||||
case ChatSessionStatus.archived:
|
||||
return 'Архивирован';
|
||||
case ChatSessionStatus.deleted:
|
||||
return 'Удален';
|
||||
}
|
||||
}
|
||||
|
||||
bool get isActive => this == ChatSessionStatus.active;
|
||||
bool get isArchived => this == ChatSessionStatus.archived;
|
||||
bool get isDeleted => this == ChatSessionStatus.deleted;
|
||||
}
|
||||
|
||||
/// DTO for updating chat session
|
||||
@freezed
|
||||
class UpdateChatSessionRequest with _$UpdateChatSessionRequest {
|
||||
const factory UpdateChatSessionRequest({
|
||||
String? title,
|
||||
String? description,
|
||||
List<String>? tags,
|
||||
ChatSessionStatus? status,
|
||||
}) = _UpdateChatSessionRequest;
|
||||
|
||||
factory UpdateChatSessionRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$UpdateChatSessionRequestFromJson(json);
|
||||
}
|
||||
|
|
@ -0,0 +1,657 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'chat_session.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
ChatSession _$ChatSessionFromJson(Map<String, dynamic> json) {
|
||||
return _ChatSession.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ChatSession {
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
String get userId => throw _privateConstructorUsedError;
|
||||
String get title => throw _privateConstructorUsedError;
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
ChatSessionStatus get status => throw _privateConstructorUsedError;
|
||||
String? get description => throw _privateConstructorUsedError;
|
||||
List<String> get tags => throw _privateConstructorUsedError;
|
||||
int get messageCount => throw _privateConstructorUsedError;
|
||||
String? get lastMessagePreview => throw _privateConstructorUsedError;
|
||||
DateTime? get lastMessageAt => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ChatSession to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ChatSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ChatSessionCopyWith<ChatSession> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ChatSessionCopyWith<$Res> {
|
||||
factory $ChatSessionCopyWith(
|
||||
ChatSession value,
|
||||
$Res Function(ChatSession) then,
|
||||
) = _$ChatSessionCopyWithImpl<$Res, ChatSession>;
|
||||
@useResult
|
||||
$Res call({
|
||||
String id,
|
||||
String userId,
|
||||
String title,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
ChatSessionStatus status,
|
||||
String? description,
|
||||
List<String> tags,
|
||||
int messageCount,
|
||||
String? lastMessagePreview,
|
||||
DateTime? lastMessageAt,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ChatSessionCopyWithImpl<$Res, $Val extends ChatSession>
|
||||
implements $ChatSessionCopyWith<$Res> {
|
||||
_$ChatSessionCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ChatSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? userId = null,
|
||||
Object? title = null,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
Object? status = null,
|
||||
Object? description = freezed,
|
||||
Object? tags = null,
|
||||
Object? messageCount = null,
|
||||
Object? lastMessagePreview = freezed,
|
||||
Object? lastMessageAt = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
userId: null == userId
|
||||
? _value.userId
|
||||
: userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
title: null == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
status: null == status
|
||||
? _value.status
|
||||
: status // ignore: cast_nullable_to_non_nullable
|
||||
as ChatSessionStatus,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
tags: null == tags
|
||||
? _value.tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
messageCount: null == messageCount
|
||||
? _value.messageCount
|
||||
: messageCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
lastMessagePreview: freezed == lastMessagePreview
|
||||
? _value.lastMessagePreview
|
||||
: lastMessagePreview // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
lastMessageAt: freezed == lastMessageAt
|
||||
? _value.lastMessageAt
|
||||
: lastMessageAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ChatSessionImplCopyWith<$Res>
|
||||
implements $ChatSessionCopyWith<$Res> {
|
||||
factory _$$ChatSessionImplCopyWith(
|
||||
_$ChatSessionImpl value,
|
||||
$Res Function(_$ChatSessionImpl) then,
|
||||
) = __$$ChatSessionImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
String id,
|
||||
String userId,
|
||||
String title,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
ChatSessionStatus status,
|
||||
String? description,
|
||||
List<String> tags,
|
||||
int messageCount,
|
||||
String? lastMessagePreview,
|
||||
DateTime? lastMessageAt,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ChatSessionImplCopyWithImpl<$Res>
|
||||
extends _$ChatSessionCopyWithImpl<$Res, _$ChatSessionImpl>
|
||||
implements _$$ChatSessionImplCopyWith<$Res> {
|
||||
__$$ChatSessionImplCopyWithImpl(
|
||||
_$ChatSessionImpl _value,
|
||||
$Res Function(_$ChatSessionImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ChatSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? userId = null,
|
||||
Object? title = null,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
Object? status = null,
|
||||
Object? description = freezed,
|
||||
Object? tags = null,
|
||||
Object? messageCount = null,
|
||||
Object? lastMessagePreview = freezed,
|
||||
Object? lastMessageAt = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_$ChatSessionImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
userId: null == userId
|
||||
? _value.userId
|
||||
: userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
title: null == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
status: null == status
|
||||
? _value.status
|
||||
: status // ignore: cast_nullable_to_non_nullable
|
||||
as ChatSessionStatus,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
tags: null == tags
|
||||
? _value._tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
messageCount: null == messageCount
|
||||
? _value.messageCount
|
||||
: messageCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
lastMessagePreview: freezed == lastMessagePreview
|
||||
? _value.lastMessagePreview
|
||||
: lastMessagePreview // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
lastMessageAt: freezed == lastMessageAt
|
||||
? _value.lastMessageAt
|
||||
: lastMessageAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ChatSessionImpl implements _ChatSession {
|
||||
const _$ChatSessionImpl({
|
||||
required this.id,
|
||||
required this.userId,
|
||||
required this.title,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.status = ChatSessionStatus.active,
|
||||
this.description,
|
||||
final List<String> tags = const [],
|
||||
this.messageCount = 0,
|
||||
this.lastMessagePreview,
|
||||
this.lastMessageAt,
|
||||
}) : _tags = tags;
|
||||
|
||||
factory _$ChatSessionImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ChatSessionImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String userId;
|
||||
@override
|
||||
final String title;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
@JsonKey()
|
||||
final ChatSessionStatus status;
|
||||
@override
|
||||
final String? description;
|
||||
final List<String> _tags;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get tags {
|
||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_tags);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final int messageCount;
|
||||
@override
|
||||
final String? lastMessagePreview;
|
||||
@override
|
||||
final DateTime? lastMessageAt;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatSession(id: $id, userId: $userId, title: $title, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, description: $description, tags: $tags, messageCount: $messageCount, lastMessagePreview: $lastMessagePreview, lastMessageAt: $lastMessageAt)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ChatSessionImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.userId, userId) || other.userId == userId) &&
|
||||
(identical(other.title, title) || other.title == title) &&
|
||||
(identical(other.createdAt, createdAt) ||
|
||||
other.createdAt == createdAt) &&
|
||||
(identical(other.updatedAt, updatedAt) ||
|
||||
other.updatedAt == updatedAt) &&
|
||||
(identical(other.status, status) || other.status == status) &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
const DeepCollectionEquality().equals(other._tags, _tags) &&
|
||||
(identical(other.messageCount, messageCount) ||
|
||||
other.messageCount == messageCount) &&
|
||||
(identical(other.lastMessagePreview, lastMessagePreview) ||
|
||||
other.lastMessagePreview == lastMessagePreview) &&
|
||||
(identical(other.lastMessageAt, lastMessageAt) ||
|
||||
other.lastMessageAt == lastMessageAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
userId,
|
||||
title,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
status,
|
||||
description,
|
||||
const DeepCollectionEquality().hash(_tags),
|
||||
messageCount,
|
||||
lastMessagePreview,
|
||||
lastMessageAt,
|
||||
);
|
||||
|
||||
/// Create a copy of ChatSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ChatSessionImplCopyWith<_$ChatSessionImpl> get copyWith =>
|
||||
__$$ChatSessionImplCopyWithImpl<_$ChatSessionImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ChatSessionImplToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ChatSession implements ChatSession {
|
||||
const factory _ChatSession({
|
||||
required final String id,
|
||||
required final String userId,
|
||||
required final String title,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
final ChatSessionStatus status,
|
||||
final String? description,
|
||||
final List<String> tags,
|
||||
final int messageCount,
|
||||
final String? lastMessagePreview,
|
||||
final DateTime? lastMessageAt,
|
||||
}) = _$ChatSessionImpl;
|
||||
|
||||
factory _ChatSession.fromJson(Map<String, dynamic> json) =
|
||||
_$ChatSessionImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get id;
|
||||
@override
|
||||
String get userId;
|
||||
@override
|
||||
String get title;
|
||||
@override
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
ChatSessionStatus get status;
|
||||
@override
|
||||
String? get description;
|
||||
@override
|
||||
List<String> get tags;
|
||||
@override
|
||||
int get messageCount;
|
||||
@override
|
||||
String? get lastMessagePreview;
|
||||
@override
|
||||
DateTime? get lastMessageAt;
|
||||
|
||||
/// Create a copy of ChatSession
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ChatSessionImplCopyWith<_$ChatSessionImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
UpdateChatSessionRequest _$UpdateChatSessionRequestFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) {
|
||||
return _UpdateChatSessionRequest.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UpdateChatSessionRequest {
|
||||
String? get title => throw _privateConstructorUsedError;
|
||||
String? get description => throw _privateConstructorUsedError;
|
||||
List<String>? get tags => throw _privateConstructorUsedError;
|
||||
ChatSessionStatus? get status => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this UpdateChatSessionRequest to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of UpdateChatSessionRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$UpdateChatSessionRequestCopyWith<UpdateChatSessionRequest> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $UpdateChatSessionRequestCopyWith<$Res> {
|
||||
factory $UpdateChatSessionRequestCopyWith(
|
||||
UpdateChatSessionRequest value,
|
||||
$Res Function(UpdateChatSessionRequest) then,
|
||||
) = _$UpdateChatSessionRequestCopyWithImpl<$Res, UpdateChatSessionRequest>;
|
||||
@useResult
|
||||
$Res call({
|
||||
String? title,
|
||||
String? description,
|
||||
List<String>? tags,
|
||||
ChatSessionStatus? status,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UpdateChatSessionRequestCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends UpdateChatSessionRequest
|
||||
>
|
||||
implements $UpdateChatSessionRequestCopyWith<$Res> {
|
||||
_$UpdateChatSessionRequestCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of UpdateChatSessionRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? title = freezed,
|
||||
Object? description = freezed,
|
||||
Object? tags = freezed,
|
||||
Object? status = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
title: freezed == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
tags: freezed == tags
|
||||
? _value.tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
status: freezed == status
|
||||
? _value.status
|
||||
: status // ignore: cast_nullable_to_non_nullable
|
||||
as ChatSessionStatus?,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$UpdateChatSessionRequestImplCopyWith<$Res>
|
||||
implements $UpdateChatSessionRequestCopyWith<$Res> {
|
||||
factory _$$UpdateChatSessionRequestImplCopyWith(
|
||||
_$UpdateChatSessionRequestImpl value,
|
||||
$Res Function(_$UpdateChatSessionRequestImpl) then,
|
||||
) = __$$UpdateChatSessionRequestImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
String? title,
|
||||
String? description,
|
||||
List<String>? tags,
|
||||
ChatSessionStatus? status,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$UpdateChatSessionRequestImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$UpdateChatSessionRequestCopyWithImpl<
|
||||
$Res,
|
||||
_$UpdateChatSessionRequestImpl
|
||||
>
|
||||
implements _$$UpdateChatSessionRequestImplCopyWith<$Res> {
|
||||
__$$UpdateChatSessionRequestImplCopyWithImpl(
|
||||
_$UpdateChatSessionRequestImpl _value,
|
||||
$Res Function(_$UpdateChatSessionRequestImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of UpdateChatSessionRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? title = freezed,
|
||||
Object? description = freezed,
|
||||
Object? tags = freezed,
|
||||
Object? status = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_$UpdateChatSessionRequestImpl(
|
||||
title: freezed == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
tags: freezed == tags
|
||||
? _value._tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
status: freezed == status
|
||||
? _value.status
|
||||
: status // ignore: cast_nullable_to_non_nullable
|
||||
as ChatSessionStatus?,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$UpdateChatSessionRequestImpl implements _UpdateChatSessionRequest {
|
||||
const _$UpdateChatSessionRequestImpl({
|
||||
this.title,
|
||||
this.description,
|
||||
final List<String>? tags,
|
||||
this.status,
|
||||
}) : _tags = tags;
|
||||
|
||||
factory _$UpdateChatSessionRequestImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UpdateChatSessionRequestImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String? title;
|
||||
@override
|
||||
final String? description;
|
||||
final List<String>? _tags;
|
||||
@override
|
||||
List<String>? get tags {
|
||||
final value = _tags;
|
||||
if (value == null) return null;
|
||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
final ChatSessionStatus? status;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UpdateChatSessionRequest(title: $title, description: $description, tags: $tags, status: $status)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UpdateChatSessionRequestImpl &&
|
||||
(identical(other.title, title) || other.title == title) &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
const DeepCollectionEquality().equals(other._tags, _tags) &&
|
||||
(identical(other.status, status) || other.status == status));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
title,
|
||||
description,
|
||||
const DeepCollectionEquality().hash(_tags),
|
||||
status,
|
||||
);
|
||||
|
||||
/// Create a copy of UpdateChatSessionRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$UpdateChatSessionRequestImplCopyWith<_$UpdateChatSessionRequestImpl>
|
||||
get copyWith =>
|
||||
__$$UpdateChatSessionRequestImplCopyWithImpl<
|
||||
_$UpdateChatSessionRequestImpl
|
||||
>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$UpdateChatSessionRequestImplToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _UpdateChatSessionRequest implements UpdateChatSessionRequest {
|
||||
const factory _UpdateChatSessionRequest({
|
||||
final String? title,
|
||||
final String? description,
|
||||
final List<String>? tags,
|
||||
final ChatSessionStatus? status,
|
||||
}) = _$UpdateChatSessionRequestImpl;
|
||||
|
||||
factory _UpdateChatSessionRequest.fromJson(Map<String, dynamic> json) =
|
||||
_$UpdateChatSessionRequestImpl.fromJson;
|
||||
|
||||
@override
|
||||
String? get title;
|
||||
@override
|
||||
String? get description;
|
||||
@override
|
||||
List<String>? get tags;
|
||||
@override
|
||||
ChatSessionStatus? get status;
|
||||
|
||||
/// Create a copy of UpdateChatSessionRequest
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$UpdateChatSessionRequestImplCopyWith<_$UpdateChatSessionRequestImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_session.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ChatSessionImpl _$$ChatSessionImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ChatSessionImpl(
|
||||
id: json['id'] as String,
|
||||
userId: json['userId'] as String,
|
||||
title: json['title'] as String,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||
status:
|
||||
$enumDecodeNullable(_$ChatSessionStatusEnumMap, json['status']) ??
|
||||
ChatSessionStatus.active,
|
||||
description: json['description'] as String?,
|
||||
tags:
|
||||
(json['tags'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||
const [],
|
||||
messageCount: (json['messageCount'] as num?)?.toInt() ?? 0,
|
||||
lastMessagePreview: json['lastMessagePreview'] as String?,
|
||||
lastMessageAt: json['lastMessageAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastMessageAt'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatSessionImplToJson(_$ChatSessionImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'userId': instance.userId,
|
||||
'title': instance.title,
|
||||
'createdAt': instance.createdAt.toIso8601String(),
|
||||
'updatedAt': instance.updatedAt.toIso8601String(),
|
||||
'status': _$ChatSessionStatusEnumMap[instance.status]!,
|
||||
'description': instance.description,
|
||||
'tags': instance.tags,
|
||||
'messageCount': instance.messageCount,
|
||||
'lastMessagePreview': instance.lastMessagePreview,
|
||||
'lastMessageAt': instance.lastMessageAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
const _$ChatSessionStatusEnumMap = {
|
||||
ChatSessionStatus.active: 'active',
|
||||
ChatSessionStatus.archived: 'archived',
|
||||
ChatSessionStatus.deleted: 'deleted',
|
||||
};
|
||||
|
||||
_$UpdateChatSessionRequestImpl _$$UpdateChatSessionRequestImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$UpdateChatSessionRequestImpl(
|
||||
title: json['title'] as String?,
|
||||
description: json['description'] as String?,
|
||||
tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||
status: $enumDecodeNullable(_$ChatSessionStatusEnumMap, json['status']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$UpdateChatSessionRequestImplToJson(
|
||||
_$UpdateChatSessionRequestImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'title': instance.title,
|
||||
'description': instance.description,
|
||||
'tags': instance.tags,
|
||||
'status': _$ChatSessionStatusEnumMap[instance.status],
|
||||
};
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import '../models/chat_api_simple.dart';
|
||||
import '../models/chat_basic.dart';
|
||||
|
||||
/// Abstract repository interface for chat operations
|
||||
///
|
||||
/// This allows the chat module to be decoupled from specific HTTP implementations
|
||||
abstract class ChatRepository {
|
||||
/// Create new chat session
|
||||
Future<ChatBasicSession> createChatSession(CreateChatSessionRequest request);
|
||||
|
||||
/// Get user's chat sessions
|
||||
Future<List<ChatBasicSession>> getChatSessions({
|
||||
int limit,
|
||||
String? afterSessionId,
|
||||
});
|
||||
|
||||
/// Get specific chat session
|
||||
Future<ChatBasicSession> getChatSession(String sessionId);
|
||||
|
||||
/// Send text message
|
||||
Future<ChatMessageResponse> sendTextMessage(SendTextMessageRequest request);
|
||||
|
||||
/// Send audio message (multipart/form-data)
|
||||
Future<ChatMessageResponse> sendAudioMessage(
|
||||
String sessionId,
|
||||
dynamic audioData,
|
||||
Duration duration, {
|
||||
String? fileName,
|
||||
String? mimeType,
|
||||
});
|
||||
|
||||
/// Get messages for chat session
|
||||
Future<List<ChatMessageResponse>> getChatMessages(
|
||||
String sessionId, {
|
||||
int limit,
|
||||
String? beforeMessageId,
|
||||
});
|
||||
|
||||
/// Update chat session
|
||||
Future<ChatBasicSession> updateChatSession(
|
||||
String sessionId,
|
||||
Map<String, dynamic> updates,
|
||||
);
|
||||
|
||||
/// Delete chat session
|
||||
Future<void> deleteChatSession(String sessionId);
|
||||
}
|
||||
135
chat/mnemo_cards_chat/lib/src/domain/services/chat_service.dart
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import '../models/chat_api_simple.dart';
|
||||
import '../models/chat_basic.dart';
|
||||
import 'chat_repository.dart';
|
||||
|
||||
/// Service for chat functionality
|
||||
class ChatService {
|
||||
ChatService({
|
||||
required ChatRepository chatRepository,
|
||||
}) : _chatRepository = chatRepository;
|
||||
|
||||
final ChatRepository _chatRepository;
|
||||
|
||||
/// Create new chat session
|
||||
Future<ChatBasicSession> createSession({
|
||||
required String title,
|
||||
String? description,
|
||||
}) async {
|
||||
log('Creating new chat session: $title', name: 'ChatService');
|
||||
|
||||
final request = CreateChatSessionRequest(title: title);
|
||||
final session = await _chatRepository.createChatSession(request);
|
||||
|
||||
log('Chat session created: ${session.id}', name: 'ChatService');
|
||||
return session;
|
||||
}
|
||||
|
||||
/// Get chat session by ID
|
||||
Future<ChatBasicSession> getSession(String sessionId) async {
|
||||
log('Getting chat session: $sessionId', name: 'ChatService');
|
||||
|
||||
final session = await _chatRepository.getChatSession(sessionId);
|
||||
|
||||
log('Chat session loaded: ${session.title}', name: 'ChatService');
|
||||
return session;
|
||||
}
|
||||
|
||||
/// Get user's chat sessions
|
||||
Future<List<ChatBasicSession>> getSessions({
|
||||
int limit = 20,
|
||||
String? afterSessionId,
|
||||
}) async {
|
||||
log('Getting chat sessions (limit: $limit)', name: 'ChatService');
|
||||
|
||||
final sessions = await _chatRepository.getChatSessions(
|
||||
limit: limit,
|
||||
afterSessionId: afterSessionId,
|
||||
);
|
||||
|
||||
log('Loaded ${sessions.length} chat sessions', name: 'ChatService');
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/// Send text message
|
||||
Future<ChatMessageResponse> sendTextMessage(
|
||||
String sessionId,
|
||||
String content,
|
||||
) async {
|
||||
log('Sending text message to session: $sessionId', name: 'ChatService');
|
||||
|
||||
final request = SendTextMessageRequest(
|
||||
sessionId: sessionId,
|
||||
content: content,
|
||||
);
|
||||
|
||||
final response = await _chatRepository.sendTextMessage(request);
|
||||
|
||||
log('Text message sent successfully', name: 'ChatService');
|
||||
return response;
|
||||
}
|
||||
|
||||
/// Send audio message
|
||||
Future<ChatMessageResponse> sendAudioMessage(
|
||||
String sessionId,
|
||||
dynamic audioData,
|
||||
Duration duration, {
|
||||
String? transcription,
|
||||
String? fileName,
|
||||
String? mimeType,
|
||||
}) async {
|
||||
log('Sending audio message to session: $sessionId', name: 'ChatService');
|
||||
|
||||
final response = await _chatRepository.sendAudioMessage(
|
||||
sessionId,
|
||||
audioData,
|
||||
duration,
|
||||
fileName: fileName,
|
||||
mimeType: mimeType,
|
||||
);
|
||||
|
||||
log('Audio message sent successfully', name: 'ChatService');
|
||||
return response;
|
||||
}
|
||||
|
||||
/// Get messages for session
|
||||
Future<List<ChatMessageResponse>> getMessages(
|
||||
String sessionId, {
|
||||
int limit = 50,
|
||||
String? beforeMessageId,
|
||||
}) async {
|
||||
log('Getting messages for session: $sessionId (limit: $limit)', name: 'ChatService');
|
||||
|
||||
final messages = await _chatRepository.getChatMessages(
|
||||
sessionId,
|
||||
limit: limit,
|
||||
beforeMessageId: beforeMessageId,
|
||||
);
|
||||
|
||||
log('Loaded ${messages.length} messages', name: 'ChatService');
|
||||
return messages;
|
||||
}
|
||||
|
||||
/// Update chat session
|
||||
Future<ChatBasicSession> updateSession(
|
||||
String sessionId,
|
||||
Map<String, dynamic> updates,
|
||||
) async {
|
||||
log('Updating chat session: $sessionId', name: 'ChatService');
|
||||
|
||||
final session = await _chatRepository.updateChatSession(sessionId, updates);
|
||||
|
||||
log('Chat session updated: ${session.title}', name: 'ChatService');
|
||||
return session;
|
||||
}
|
||||
|
||||
/// Delete chat session
|
||||
Future<void> deleteSession(String sessionId) async {
|
||||
log('Deleting chat session: $sessionId', name: 'ChatService');
|
||||
|
||||
await _chatRepository.deleteChatSession(sessionId);
|
||||
|
||||
log('Chat session deleted', name: 'ChatService');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:yx_state/yx_state.dart';
|
||||
|
||||
import '../models/chat_basic.dart';
|
||||
import '../models/chat_message.dart';
|
||||
import '../services/chat_service.dart';
|
||||
|
||||
/// State for chat functionality
|
||||
class ChatState {
|
||||
const ChatState._();
|
||||
|
||||
const factory ChatState.loading() = ChatStateLoading;
|
||||
|
||||
const factory ChatState.loaded({
|
||||
required ChatBasicSession session,
|
||||
required List<ChatMessage> messages,
|
||||
bool isSendingMessage,
|
||||
bool isLoadingMore,
|
||||
bool hasMoreMessages,
|
||||
String? errorMessage,
|
||||
}) = ChatStateLoaded;
|
||||
|
||||
const factory ChatState.error(String message) = ChatStateError;
|
||||
}
|
||||
|
||||
/// Loading state
|
||||
class ChatStateLoading extends ChatState {
|
||||
const ChatStateLoading() : super._();
|
||||
}
|
||||
|
||||
/// Loaded state with session and messages
|
||||
class ChatStateLoaded extends ChatState {
|
||||
const ChatStateLoaded({
|
||||
required this.session,
|
||||
required this.messages,
|
||||
this.isSendingMessage = false,
|
||||
this.isLoadingMore = false,
|
||||
this.hasMoreMessages = true,
|
||||
this.errorMessage,
|
||||
}) : super._();
|
||||
|
||||
final ChatBasicSession session;
|
||||
final List<ChatMessage> messages;
|
||||
final bool isSendingMessage;
|
||||
final bool isLoadingMore;
|
||||
final bool hasMoreMessages;
|
||||
final String? errorMessage;
|
||||
|
||||
ChatStateLoaded copyWith({
|
||||
ChatBasicSession? session,
|
||||
List<ChatMessage>? messages,
|
||||
bool? isSendingMessage,
|
||||
bool? isLoadingMore,
|
||||
bool? hasMoreMessages,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return ChatStateLoaded(
|
||||
session: session ?? this.session,
|
||||
messages: messages ?? this.messages,
|
||||
isSendingMessage: isSendingMessage ?? this.isSendingMessage,
|
||||
isLoadingMore: isLoadingMore ?? this.isLoadingMore,
|
||||
hasMoreMessages: hasMoreMessages ?? this.hasMoreMessages,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Error state
|
||||
class ChatStateError extends ChatState {
|
||||
const ChatStateError(this.message) : super._();
|
||||
|
||||
final String message;
|
||||
}
|
||||
|
||||
/// State manager for chat functionality
|
||||
class ChatStateManager extends StateManager<ChatState> {
|
||||
ChatStateManager({
|
||||
required ChatService chatService,
|
||||
}) : _chatService = chatService,
|
||||
super(const ChatStateLoading());
|
||||
|
||||
final ChatService _chatService;
|
||||
ChatBasicSession? _currentSession;
|
||||
final List<ChatMessage> _messages = [];
|
||||
|
||||
/// Initialize chat with session
|
||||
Future<void> initializeChat(String sessionId) => handle((emit) async {
|
||||
log('Initializing chat for session: $sessionId', name: 'ChatStateManager');
|
||||
emit(const ChatStateLoading());
|
||||
|
||||
try {
|
||||
// Load session info and messages
|
||||
final session = await _chatService.getSession(sessionId);
|
||||
final messages = await _chatService.getMessages(sessionId, limit: 50);
|
||||
|
||||
_currentSession = session;
|
||||
_messages.clear();
|
||||
// Convert ChatMessageResponse to ChatMessage - simplified for now
|
||||
// In real implementation, this conversion would be more complex
|
||||
|
||||
emit(ChatStateLoaded(
|
||||
session: session,
|
||||
messages: List.unmodifiable(_messages),
|
||||
hasMoreMessages: messages.length >= 50,
|
||||
));
|
||||
|
||||
log('Chat initialized with ${messages.length} messages', name: 'ChatStateManager');
|
||||
} catch (e, s) {
|
||||
log(
|
||||
'Error initializing chat',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
name: 'ChatStateManager',
|
||||
);
|
||||
emit(ChatStateError('Failed to load chat: ${e.toString()}'));
|
||||
}
|
||||
});
|
||||
|
||||
/// Send text message
|
||||
Future<void> sendTextMessage(String content) => handle((emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is! ChatStateLoaded || _currentSession == null) {
|
||||
log('Cannot send message: chat not initialized', name: 'ChatStateManager');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add sending state
|
||||
emit(currentState.copyWith(isSendingMessage: true, errorMessage: null));
|
||||
|
||||
try {
|
||||
// Send to server
|
||||
final response = await _chatService.sendTextMessage(_currentSession!.id, content);
|
||||
|
||||
// Add assistant response
|
||||
// Simplified - in real implementation would convert response to ChatMessage
|
||||
|
||||
emit(currentState.copyWith(
|
||||
isSendingMessage: false,
|
||||
));
|
||||
|
||||
log('Message sent successfully', name: 'ChatStateManager');
|
||||
} catch (e, s) {
|
||||
log(
|
||||
'Error sending message',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
name: 'ChatStateManager',
|
||||
);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
isSendingMessage: false,
|
||||
errorMessage: 'Failed to send message: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
/// Send audio message
|
||||
Future<void> sendAudioMessage(
|
||||
dynamic audioData,
|
||||
Duration duration, {
|
||||
String? transcription,
|
||||
}) => handle((emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is! ChatStateLoaded || _currentSession == null) {
|
||||
log('Cannot send audio message: chat not initialized', name: 'ChatStateManager');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add sending state
|
||||
emit(currentState.copyWith(isSendingMessage: true, errorMessage: null));
|
||||
|
||||
try {
|
||||
// Send to server
|
||||
final response = await _chatService.sendAudioMessage(
|
||||
_currentSession!.id,
|
||||
audioData,
|
||||
duration,
|
||||
transcription: transcription,
|
||||
);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
isSendingMessage: false,
|
||||
));
|
||||
|
||||
log('Audio message sent successfully', name: 'ChatStateManager');
|
||||
} catch (e, s) {
|
||||
log(
|
||||
'Error sending audio message',
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
name: 'ChatStateManager',
|
||||
);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
isSendingMessage: false,
|
||||
errorMessage: 'Failed to send audio message: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
/// Clear error message
|
||||
void clearError() => handle((emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is ChatStateLoaded) {
|
||||
emit(currentState.copyWith(errorMessage: null));
|
||||
}
|
||||
});
|
||||
|
||||
/// Get current session
|
||||
ChatBasicSession? get currentSession => _currentSession;
|
||||
|
||||
/// Get messages count
|
||||
int get messagesCount => _messages.length;
|
||||
}
|
||||
629
chat/mnemo_cards_chat/pubspec.lock
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "85.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.7.1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "409002f1adeea601018715d613115cfaf0e31f512cb80ae4534c79867ae2363d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.2"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.12.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.11.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.9.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.8"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: freezed_annotation
|
||||
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
json_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
json_serializable:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.9.5"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mocktail:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: mocktail
|
||||
sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.7"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
yx_scope:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: yx_scope
|
||||
sha256: "9ba98b442261596311363bf7361622e5ccc67189705b8d042ca23c9de366f8bf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
yx_state:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: yx_state
|
||||
sha256: "13ab50d3875686f65058cb9abdfbb1725e9d15ea99ade51b1f3a5f93727a8eaa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
sdks:
|
||||
dart: ">=3.9.2 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
43
chat/mnemo_cards_chat/pubspec.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
name: mnemo_cards_chat
|
||||
description: "Chat module for mnemo_cards applications with LLM integration"
|
||||
version: 1.0.0
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# YX Framework for DI and state management
|
||||
yx_scope: ^1.1.2
|
||||
yx_state: ^1.0.0
|
||||
|
||||
# HTTP client
|
||||
dio: ^5.3.3
|
||||
|
||||
# Immutable data models
|
||||
freezed_annotation: ^2.4.1
|
||||
json_annotation: ^4.7.0
|
||||
|
||||
# Common utilities
|
||||
meta: ^1.8.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# Code generation
|
||||
build_runner: ^2.4.13
|
||||
freezed: ^2.4.5
|
||||
json_serializable: ^6.8.0
|
||||
|
||||
# Linting
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
# Testing utilities
|
||||
mocktail: ^1.0.3
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
359
chat/mnemo_cards_chat/test/domain/models/chat_message_test.dart
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mnemo_cards_chat/domain/models/chat_message.dart';
|
||||
|
||||
void main() {
|
||||
group('ChatMessage', () {
|
||||
group('MessageStatus', () {
|
||||
test('has correct display names', () {
|
||||
expect(MessageStatus.sending.displayName, 'Отправляется...');
|
||||
expect(MessageStatus.sent.displayName, 'Отправлено');
|
||||
expect(MessageStatus.delivered.displayName, 'Доставлено');
|
||||
expect(MessageStatus.error.displayName, 'Ошибка');
|
||||
});
|
||||
|
||||
test('has correct status checks', () {
|
||||
expect(MessageStatus.sending.isSending, isTrue);
|
||||
expect(MessageStatus.sent.isSending, isFalse);
|
||||
expect(MessageStatus.delivered.isSending, isFalse);
|
||||
expect(MessageStatus.error.isSending, isFalse);
|
||||
|
||||
expect(MessageStatus.error.isError, isTrue);
|
||||
expect(MessageStatus.sent.isError, isFalse);
|
||||
|
||||
expect(MessageStatus.sent.isDelivered, isTrue);
|
||||
expect(MessageStatus.delivered.isDelivered, isTrue);
|
||||
expect(MessageStatus.sending.isDelivered, isFalse);
|
||||
expect(MessageStatus.error.isDelivered, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('ChatParticipant', () {
|
||||
test('creates user participant correctly', () {
|
||||
final participant = ChatParticipant.user(
|
||||
id: 'user_123',
|
||||
name: 'John Doe',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
);
|
||||
|
||||
expect(participant, isA<ChatParticipantUser>());
|
||||
final user = participant as ChatParticipantUser;
|
||||
expect(user.id, 'user_123');
|
||||
expect(user.name, 'John Doe');
|
||||
expect(user.avatarUrl, 'https://example.com/avatar.jpg');
|
||||
});
|
||||
|
||||
test('creates assistant participant correctly', () {
|
||||
final participant = ChatParticipant.assistant(
|
||||
id: 'assistant_123',
|
||||
name: 'AI Assistant',
|
||||
avatarUrl: 'https://example.com/bot.jpg',
|
||||
model: 'gpt-4',
|
||||
);
|
||||
|
||||
expect(participant, isA<ChatParticipantAssistant>());
|
||||
final assistant = participant as ChatParticipantAssistant;
|
||||
expect(assistant.id, 'assistant_123');
|
||||
expect(assistant.name, 'AI Assistant');
|
||||
expect(assistant.avatarUrl, 'https://example.com/bot.jpg');
|
||||
expect(assistant.model, 'gpt-4');
|
||||
});
|
||||
|
||||
test('user participant supports optional avatar', () {
|
||||
final participant = ChatParticipant.user(
|
||||
id: 'user_123',
|
||||
name: 'John Doe',
|
||||
);
|
||||
|
||||
final user = participant as ChatParticipantUser;
|
||||
expect(user.avatarUrl, isNull);
|
||||
});
|
||||
|
||||
test('assistant participant supports optional model', () {
|
||||
final participant = ChatParticipant.assistant(
|
||||
id: 'assistant_123',
|
||||
name: 'AI Assistant',
|
||||
);
|
||||
|
||||
final assistant = participant as ChatParticipantAssistant;
|
||||
expect(assistant.model, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('TextMessage', () {
|
||||
test('creates text message correctly', () {
|
||||
final timestamp = DateTime.now();
|
||||
final sender = ChatParticipant.user(id: 'user_123', name: 'User');
|
||||
|
||||
final message = TextMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
content: 'Hello, world!',
|
||||
sender: sender,
|
||||
timestamp: timestamp,
|
||||
status: MessageStatus.sent,
|
||||
metadata: {'source': 'test'},
|
||||
);
|
||||
|
||||
expect(message.id, 'msg_123');
|
||||
expect(message.sessionId, 'session_123');
|
||||
expect(message.content, 'Hello, world!');
|
||||
expect(message.sender, sender);
|
||||
expect(message.timestamp, timestamp);
|
||||
expect(message.status, MessageStatus.sent);
|
||||
expect(message.metadata, {'source': 'test'});
|
||||
});
|
||||
|
||||
test('supports optional metadata', () {
|
||||
final message = TextMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
content: 'Hello',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(message.metadata, isNull);
|
||||
});
|
||||
|
||||
test('has default status', () {
|
||||
final message = TextMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
content: 'Hello',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(message.status, MessageStatus.sent);
|
||||
});
|
||||
});
|
||||
|
||||
group('AudioMessage', () {
|
||||
test('creates audio message correctly', () {
|
||||
final timestamp = DateTime.now();
|
||||
final sender = ChatParticipant.user(id: 'user_123', name: 'User');
|
||||
const duration = Duration(seconds: 30);
|
||||
|
||||
final message = AudioMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
audioUrl: 'https://example.com/audio.mp3',
|
||||
sender: sender,
|
||||
timestamp: timestamp,
|
||||
duration: duration,
|
||||
fileSize: 1024000,
|
||||
status: MessageStatus.sent,
|
||||
transcription: 'Hello from audio',
|
||||
metadata: {'format': 'mp3'},
|
||||
);
|
||||
|
||||
expect(message.id, 'msg_123');
|
||||
expect(message.sessionId, 'session_123');
|
||||
expect(message.audioUrl, 'https://example.com/audio.mp3');
|
||||
expect(message.sender, sender);
|
||||
expect(message.timestamp, timestamp);
|
||||
expect(message.duration, duration);
|
||||
expect(message.fileSize, 1024000);
|
||||
expect(message.status, MessageStatus.sent);
|
||||
expect(message.transcription, 'Hello from audio');
|
||||
expect(message.metadata, {'format': 'mp3'});
|
||||
});
|
||||
|
||||
test('supports optional transcription and metadata', () {
|
||||
const duration = Duration(seconds: 10);
|
||||
final message = AudioMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
audioUrl: 'https://example.com/audio.wav',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.now(),
|
||||
duration: duration,
|
||||
fileSize: 512000,
|
||||
);
|
||||
|
||||
expect(message.transcription, isNull);
|
||||
expect(message.metadata, isNull);
|
||||
});
|
||||
|
||||
test('has default status', () {
|
||||
const duration = Duration(seconds: 5);
|
||||
final message = AudioMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
audioUrl: 'https://example.com/audio.webm',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.now(),
|
||||
duration: duration,
|
||||
fileSize: 256000,
|
||||
);
|
||||
|
||||
expect(message.status, MessageStatus.sent);
|
||||
});
|
||||
});
|
||||
|
||||
group('ChatMessage union', () {
|
||||
test('creates text message variant', () {
|
||||
final textMessage = TextMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
content: 'Hello',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
final chatMessage = ChatMessage.text(textMessage);
|
||||
expect(chatMessage, isA<ChatMessageText>());
|
||||
});
|
||||
|
||||
test('creates audio message variant', () {
|
||||
const duration = Duration(seconds: 10);
|
||||
final audioMessage = AudioMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
audioUrl: 'https://example.com/audio.mp3',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.now(),
|
||||
duration: duration,
|
||||
fileSize: 1024000,
|
||||
);
|
||||
|
||||
final chatMessage = ChatMessage.audio(audioMessage);
|
||||
expect(chatMessage, isA<ChatMessageAudio>());
|
||||
});
|
||||
});
|
||||
|
||||
group('JSON serialization', () {
|
||||
test('ChatParticipant serializes and deserializes correctly', () {
|
||||
final user = ChatParticipant.user(
|
||||
id: 'user_123',
|
||||
name: 'John Doe',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
);
|
||||
|
||||
final json = user.toJson();
|
||||
final deserialized = ChatParticipant.fromJson(json);
|
||||
|
||||
expect(deserialized, equals(user));
|
||||
});
|
||||
|
||||
test('TextMessage serializes and deserializes correctly', () {
|
||||
final message = TextMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
content: 'Hello, world!',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||
status: MessageStatus.sent,
|
||||
metadata: {'source': 'test'},
|
||||
);
|
||||
|
||||
final json = message.toJson();
|
||||
final deserialized = TextMessage.fromJson(json);
|
||||
|
||||
expect(deserialized, equals(message));
|
||||
});
|
||||
|
||||
test('AudioMessage serializes and deserializes correctly', () {
|
||||
final message = AudioMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
audioUrl: 'https://example.com/audio.mp3',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||
duration: const Duration(seconds: 30),
|
||||
fileSize: 1024000,
|
||||
status: MessageStatus.sent,
|
||||
transcription: 'Hello from audio',
|
||||
);
|
||||
|
||||
final json = message.toJson();
|
||||
final deserialized = AudioMessage.fromJson(json);
|
||||
|
||||
expect(deserialized, equals(message));
|
||||
});
|
||||
});
|
||||
|
||||
group('Equality and hashCode', () {
|
||||
test('TextMessage equality works correctly', () {
|
||||
final message1 = TextMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
content: 'Hello',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||
);
|
||||
|
||||
final message2 = TextMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
content: 'Hello',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||
);
|
||||
|
||||
final message3 = TextMessage(
|
||||
id: 'msg_456',
|
||||
sessionId: 'session_123',
|
||||
content: 'Hello',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||
);
|
||||
|
||||
expect(message1, equals(message2));
|
||||
expect(message1, isNot(equals(message3)));
|
||||
expect(message1.hashCode, equals(message2.hashCode));
|
||||
expect(message1.hashCode, isNot(equals(message3.hashCode)));
|
||||
});
|
||||
|
||||
test('AudioMessage equality works correctly', () {
|
||||
const duration = Duration(seconds: 30);
|
||||
final message1 = AudioMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
audioUrl: 'https://example.com/audio.mp3',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||
duration: duration,
|
||||
fileSize: 1024000,
|
||||
);
|
||||
|
||||
final message2 = AudioMessage(
|
||||
id: 'msg_123',
|
||||
sessionId: 'session_123',
|
||||
audioUrl: 'https://example.com/audio.mp3',
|
||||
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||
duration: duration,
|
||||
fileSize: 1024000,
|
||||
);
|
||||
|
||||
expect(message1, equals(message2));
|
||||
expect(message1.hashCode, equals(message2.hashCode));
|
||||
});
|
||||
|
||||
test('ChatParticipant equality works correctly', () {
|
||||
final user1 = ChatParticipant.user(
|
||||
id: 'user_123',
|
||||
name: 'John',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
);
|
||||
|
||||
final user2 = ChatParticipant.user(
|
||||
id: 'user_123',
|
||||
name: 'John',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
);
|
||||
|
||||
final user3 = ChatParticipant.user(
|
||||
id: 'user_456',
|
||||
name: 'John',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
);
|
||||
|
||||
expect(user1, equals(user2));
|
||||
expect(user1, isNot(equals(user3)));
|
||||
expect(user1.hashCode, equals(user2.hashCode));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
255
chat/mnemo_cards_chat/test/domain/models/chat_session_test.dart
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mnemo_cards_chat/domain/models/chat_session.dart';
|
||||
|
||||
void main() {
|
||||
group('ChatSession', () {
|
||||
group('ChatSessionStatus', () {
|
||||
test('has correct display names', () {
|
||||
expect(ChatSessionStatus.active.displayName, 'Активный');
|
||||
expect(ChatSessionStatus.archived.displayName, 'Архивирован');
|
||||
expect(ChatSessionStatus.deleted.displayName, 'Удален');
|
||||
});
|
||||
|
||||
test('has correct status checks', () {
|
||||
expect(ChatSessionStatus.active.isActive, isTrue);
|
||||
expect(ChatSessionStatus.archived.isActive, isFalse);
|
||||
expect(ChatSessionStatus.deleted.isActive, isFalse);
|
||||
|
||||
expect(ChatSessionStatus.archived.isArchived, isTrue);
|
||||
expect(ChatSessionStatus.active.isArchived, isFalse);
|
||||
expect(ChatSessionStatus.deleted.isArchived, isFalse);
|
||||
|
||||
expect(ChatSessionStatus.deleted.isDeleted, isTrue);
|
||||
expect(ChatSessionStatus.active.isDeleted, isFalse);
|
||||
expect(ChatSessionStatus.archived.isDeleted, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('ChatSession', () {
|
||||
test('creates chat session correctly', () {
|
||||
final createdAt = DateTime.parse('2024-01-01T10:00:00Z');
|
||||
final updatedAt = DateTime.parse('2024-01-01T11:00:00Z');
|
||||
|
||||
final session = ChatSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_456',
|
||||
title: 'Test Chat Session',
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
status: ChatSessionStatus.active,
|
||||
description: 'A test chat session',
|
||||
tags: ['test', 'ai'],
|
||||
messageCount: 42,
|
||||
lastMessagePreview: 'Hello, how are you?',
|
||||
lastMessageAt: DateTime.parse('2024-01-01T11:30:00Z'),
|
||||
);
|
||||
|
||||
expect(session.id, 'session_123');
|
||||
expect(session.userId, 'user_456');
|
||||
expect(session.title, 'Test Chat Session');
|
||||
expect(session.createdAt, createdAt);
|
||||
expect(session.updatedAt, updatedAt);
|
||||
expect(session.status, ChatSessionStatus.active);
|
||||
expect(session.description, 'A test chat session');
|
||||
expect(session.tags, ['test', 'ai']);
|
||||
expect(session.messageCount, 42);
|
||||
expect(session.lastMessagePreview, 'Hello, how are you?');
|
||||
expect(session.lastMessageAt, DateTime.parse('2024-01-01T11:30:00Z'));
|
||||
});
|
||||
|
||||
test('supports optional fields', () {
|
||||
final session = ChatSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_456',
|
||||
title: 'Minimal Session',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(session.description, isNull);
|
||||
expect(session.tags, isEmpty);
|
||||
expect(session.messageCount, 0);
|
||||
expect(session.lastMessagePreview, isNull);
|
||||
expect(session.lastMessageAt, isNull);
|
||||
expect(session.status, ChatSessionStatus.active);
|
||||
});
|
||||
|
||||
test('has default values', () {
|
||||
final session = ChatSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_456',
|
||||
title: 'Test',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
expect(session.status, ChatSessionStatus.active);
|
||||
expect(session.tags, isEmpty);
|
||||
expect(session.messageCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('CreateChatSessionRequest', () {
|
||||
test('creates request correctly', () {
|
||||
final request = CreateChatSessionRequest(
|
||||
title: 'New Chat Session',
|
||||
description: 'A new chat',
|
||||
tags: ['ai', 'assistant'],
|
||||
);
|
||||
|
||||
expect(request.title, 'New Chat Session');
|
||||
expect(request.description, 'A new chat');
|
||||
expect(request.tags, ['ai', 'assistant']);
|
||||
});
|
||||
|
||||
test('supports optional fields', () {
|
||||
final request = CreateChatSessionRequest(title: 'Simple Chat');
|
||||
|
||||
expect(request.title, 'Simple Chat');
|
||||
expect(request.description, isNull);
|
||||
expect(request.tags, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('UpdateChatSessionRequest', () {
|
||||
test('creates update request correctly', () {
|
||||
final request = UpdateChatSessionRequest(
|
||||
title: 'Updated Title',
|
||||
description: 'Updated description',
|
||||
tags: ['updated'],
|
||||
status: ChatSessionStatus.archived,
|
||||
);
|
||||
|
||||
expect(request.title, 'Updated Title');
|
||||
expect(request.description, 'Updated description');
|
||||
expect(request.tags, ['updated']);
|
||||
expect(request.status, ChatSessionStatus.archived);
|
||||
});
|
||||
|
||||
test('supports partial updates', () {
|
||||
final request = UpdateChatSessionRequest(
|
||||
title: 'New Title',
|
||||
// Other fields null for partial update
|
||||
);
|
||||
|
||||
expect(request.title, 'New Title');
|
||||
expect(request.description, isNull);
|
||||
expect(request.tags, isNull);
|
||||
expect(request.status, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('JSON serialization', () {
|
||||
test('ChatSession serializes and deserializes correctly', () {
|
||||
final session = ChatSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_456',
|
||||
title: 'Test Session',
|
||||
createdAt: DateTime.parse('2024-01-01T10:00:00Z'),
|
||||
updatedAt: DateTime.parse('2024-01-01T11:00:00Z'),
|
||||
status: ChatSessionStatus.active,
|
||||
description: 'Test description',
|
||||
tags: ['test'],
|
||||
messageCount: 5,
|
||||
lastMessagePreview: 'Hello',
|
||||
lastMessageAt: DateTime.parse('2024-01-01T11:30:00Z'),
|
||||
);
|
||||
|
||||
final json = session.toJson();
|
||||
final deserialized = ChatSession.fromJson(json);
|
||||
|
||||
expect(deserialized, equals(session));
|
||||
});
|
||||
|
||||
test('CreateChatSessionRequest serializes and deserializes correctly', () {
|
||||
final request = CreateChatSessionRequest(
|
||||
title: 'New Session',
|
||||
description: 'Description',
|
||||
tags: ['tag1', 'tag2'],
|
||||
);
|
||||
|
||||
final json = request.toJson();
|
||||
final deserialized = CreateChatSessionRequest.fromJson(json);
|
||||
|
||||
expect(deserialized, equals(request));
|
||||
});
|
||||
|
||||
test('UpdateChatSessionRequest serializes and deserializes correctly', () {
|
||||
final request = UpdateChatSessionRequest(
|
||||
title: 'Updated',
|
||||
status: ChatSessionStatus.archived,
|
||||
);
|
||||
|
||||
final json = request.toJson();
|
||||
final deserialized = UpdateChatSessionRequest.fromJson(json);
|
||||
|
||||
expect(deserialized, equals(request));
|
||||
});
|
||||
});
|
||||
|
||||
group('Equality and hashCode', () {
|
||||
test('ChatSession equality works correctly', () {
|
||||
final session1 = ChatSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_456',
|
||||
title: 'Test',
|
||||
createdAt: DateTime.parse('2024-01-01T10:00:00Z'),
|
||||
updatedAt: DateTime.parse('2024-01-01T11:00:00Z'),
|
||||
status: ChatSessionStatus.active,
|
||||
description: 'Desc',
|
||||
tags: ['tag'],
|
||||
messageCount: 1,
|
||||
);
|
||||
|
||||
final session2 = ChatSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_456',
|
||||
title: 'Test',
|
||||
createdAt: DateTime.parse('2024-01-01T10:00:00Z'),
|
||||
updatedAt: DateTime.parse('2024-01-01T11:00:00Z'),
|
||||
status: ChatSessionStatus.active,
|
||||
description: 'Desc',
|
||||
tags: ['tag'],
|
||||
messageCount: 1,
|
||||
);
|
||||
|
||||
final session3 = ChatSession(
|
||||
id: 'session_456',
|
||||
userId: 'user_456',
|
||||
title: 'Test',
|
||||
createdAt: DateTime.parse('2024-01-01T10:00:00Z'),
|
||||
updatedAt: DateTime.parse('2024-01-01T11:00:00Z'),
|
||||
);
|
||||
|
||||
expect(session1, equals(session2));
|
||||
expect(session1, isNot(equals(session3)));
|
||||
expect(session1.hashCode, equals(session2.hashCode));
|
||||
expect(session1.hashCode, isNot(equals(session3.hashCode)));
|
||||
});
|
||||
|
||||
test('CreateChatSessionRequest equality works correctly', () {
|
||||
final request1 = CreateChatSessionRequest(
|
||||
title: 'Test',
|
||||
description: 'Desc',
|
||||
tags: ['tag'],
|
||||
);
|
||||
|
||||
final request2 = CreateChatSessionRequest(
|
||||
title: 'Test',
|
||||
description: 'Desc',
|
||||
tags: ['tag'],
|
||||
);
|
||||
|
||||
final request3 = CreateChatSessionRequest(
|
||||
title: 'Different',
|
||||
description: 'Desc',
|
||||
tags: ['tag'],
|
||||
);
|
||||
|
||||
expect(request1, equals(request2));
|
||||
expect(request1, isNot(equals(request3)));
|
||||
expect(request1.hashCode, equals(request2.hashCode));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:mnemo_cards_chat/mnemo_cards_chat.dart';
|
||||
|
||||
// Mock classes
|
||||
class MockChatRepository extends Mock implements ChatRepository {}
|
||||
|
||||
// Fallback values for mocktail
|
||||
class CreateChatSessionRequestFake extends Fake implements CreateChatSessionRequest {}
|
||||
class SendTextMessageRequestFake extends Fake implements SendTextMessageRequest {}
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
registerFallbackValue(CreateChatSessionRequestFake());
|
||||
registerFallbackValue(SendTextMessageRequestFake());
|
||||
});
|
||||
|
||||
group('ChatService', () {
|
||||
late ChatService chatService;
|
||||
late MockChatRepository mockChatRepository;
|
||||
|
||||
setUp(() {
|
||||
mockChatRepository = MockChatRepository();
|
||||
chatService = ChatService(chatRepository: mockChatRepository);
|
||||
});
|
||||
|
||||
test('can be instantiated', () {
|
||||
expect(chatService, isNotNull);
|
||||
expect(chatService, isA<ChatService>());
|
||||
});
|
||||
|
||||
test('has proper constructor signature', () {
|
||||
expect(
|
||||
() => ChatService(httpRepository: mockChatRepository),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
group('createSession', () {
|
||||
test('creates session successfully', () async {
|
||||
const title = 'Test Chat';
|
||||
const sessionId = 'session_123';
|
||||
final expectedSession = ChatBasicSession(
|
||||
id: sessionId,
|
||||
userId: 'user_123',
|
||||
title: title,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
when(() => mockChatRepository.createChatSession(any()))
|
||||
.thenAnswer((_) async => expectedSession);
|
||||
|
||||
final result = await chatService.createSession(title: title);
|
||||
|
||||
expect(result, equals(expectedSession));
|
||||
verify(() => mockChatRepository.createChatSession(
|
||||
any(that: isA<CreateChatSessionRequest>()
|
||||
.having((r) => r.title, 'title', title))
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('creates session with description', () async {
|
||||
const title = 'Test Chat';
|
||||
const description = 'Test Description';
|
||||
|
||||
when(() => mockChatRepository.createChatSession(any()))
|
||||
.thenAnswer((_) async => ChatBasicSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_123',
|
||||
title: title,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
));
|
||||
|
||||
await chatService.createSession(title: title, description: description);
|
||||
|
||||
verify(() => mockChatRepository.createChatSession(
|
||||
any(that: isA<CreateChatSessionRequest>()
|
||||
.having((r) => r.title, 'title', title))
|
||||
)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('getSession', () {
|
||||
test('gets session successfully', () async {
|
||||
const sessionId = 'session_123';
|
||||
final expectedSession = ChatBasicSession(
|
||||
id: sessionId,
|
||||
userId: 'user_123',
|
||||
title: 'Test Chat',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
when(() => mockChatRepository.getChatSession(sessionId))
|
||||
.thenAnswer((_) async => expectedSession);
|
||||
|
||||
final result = await chatService.getSession(sessionId);
|
||||
|
||||
expect(result, equals(expectedSession));
|
||||
verify(() => mockChatRepository.getChatSession(sessionId)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('getSessions', () {
|
||||
test('gets sessions with default parameters', () async {
|
||||
final sessions = [
|
||||
ChatBasicSession(
|
||||
id: 'session_1',
|
||||
userId: 'user_123',
|
||||
title: 'Chat 1',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
when(() => mockChatRepository.getChatSessions(limit: 20, afterSessionId: null))
|
||||
.thenAnswer((_) async => sessions);
|
||||
|
||||
final result = await chatService.getSessions();
|
||||
|
||||
expect(result, equals(sessions));
|
||||
verify(() => mockChatRepository.getChatSessions(limit: 20, afterSessionId: null)).called(1);
|
||||
});
|
||||
|
||||
test('gets sessions with custom parameters', () async {
|
||||
const limit = 10;
|
||||
const afterSessionId = 'session_123';
|
||||
final sessions = <ChatBasicSession>[];
|
||||
|
||||
when(() => mockChatRepository.getChatSessions(limit: limit, afterSessionId: afterSessionId))
|
||||
.thenAnswer((_) async => sessions);
|
||||
|
||||
final result = await chatService.getSessions(
|
||||
limit: limit,
|
||||
afterSessionId: afterSessionId,
|
||||
);
|
||||
|
||||
expect(result, equals(sessions));
|
||||
verify(() => mockChatRepository.getChatSessions(
|
||||
limit: limit,
|
||||
afterSessionId: afterSessionId,
|
||||
)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('sendTextMessage', () {
|
||||
test('sends text message successfully', () async {
|
||||
const sessionId = 'session_123';
|
||||
const content = 'Hello, world!';
|
||||
final expectedResponse = ChatMessageResponse(
|
||||
messageId: 'msg_123',
|
||||
sessionId: sessionId,
|
||||
content: 'Assistant response',
|
||||
senderId: 'assistant',
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
when(() => mockChatRepository.sendTextMessage(any()))
|
||||
.thenAnswer((_) async => expectedResponse);
|
||||
|
||||
final result = await chatService.sendTextMessage(sessionId, content);
|
||||
|
||||
expect(result, equals(expectedResponse));
|
||||
verify(() => mockChatRepository.sendTextMessage(
|
||||
any(that: isA<SendTextMessageRequest>()
|
||||
.having((r) => r.sessionId, 'sessionId', sessionId)
|
||||
.having((r) => r.content, 'content', content))
|
||||
)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('sendAudioMessage', () {
|
||||
test('sends audio message successfully', () async {
|
||||
const sessionId = 'session_123';
|
||||
final audioData = Uint8List(1024);
|
||||
const duration = Duration(seconds: 5);
|
||||
final expectedResponse = ChatMessageResponse(
|
||||
messageId: 'msg_123',
|
||||
sessionId: sessionId,
|
||||
content: 'Audio processed',
|
||||
senderId: 'assistant',
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
when(() => mockChatRepository.sendAudioMessage(
|
||||
sessionId,
|
||||
audioData,
|
||||
duration,
|
||||
fileName: null,
|
||||
mimeType: null,
|
||||
)).thenAnswer((_) async => expectedResponse);
|
||||
|
||||
final result = await chatService.sendAudioMessage(
|
||||
sessionId,
|
||||
audioData,
|
||||
duration,
|
||||
);
|
||||
|
||||
expect(result, equals(expectedResponse));
|
||||
verify(() => mockChatRepository.sendAudioMessage(
|
||||
sessionId,
|
||||
audioData,
|
||||
duration,
|
||||
fileName: null,
|
||||
mimeType: null,
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('sends audio message with metadata', () async {
|
||||
const sessionId = 'session_123';
|
||||
final audioData = Uint8List(2048);
|
||||
const duration = Duration(seconds: 10);
|
||||
const transcription = 'Hello world';
|
||||
const fileName = 'recording.webm';
|
||||
const mimeType = 'audio/webm';
|
||||
|
||||
when(() => mockChatRepository.sendAudioMessage(
|
||||
sessionId,
|
||||
audioData,
|
||||
duration,
|
||||
fileName: fileName,
|
||||
mimeType: mimeType,
|
||||
)).thenAnswer((_) async => ChatMessageResponse(
|
||||
messageId: 'msg_123',
|
||||
sessionId: sessionId,
|
||||
content: 'Response',
|
||||
senderId: 'assistant',
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
|
||||
await chatService.sendAudioMessage(
|
||||
sessionId,
|
||||
audioData,
|
||||
duration,
|
||||
transcription: transcription,
|
||||
fileName: fileName,
|
||||
mimeType: mimeType,
|
||||
);
|
||||
|
||||
verify(() => mockChatRepository.sendAudioMessage(
|
||||
sessionId,
|
||||
audioData,
|
||||
duration,
|
||||
fileName: fileName,
|
||||
mimeType: mimeType,
|
||||
)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('getMessages', () {
|
||||
test('gets messages with default parameters', () async {
|
||||
const sessionId = 'session_123';
|
||||
final messages = [
|
||||
ChatMessageResponse(
|
||||
messageId: 'msg_1',
|
||||
sessionId: sessionId,
|
||||
content: 'Hello',
|
||||
senderId: 'user',
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
when(() => mockChatRepository.getChatMessages(
|
||||
sessionId,
|
||||
limit: 50,
|
||||
beforeMessageId: null,
|
||||
)).thenAnswer((_) async => messages);
|
||||
|
||||
final result = await chatService.getMessages(sessionId);
|
||||
|
||||
expect(result, equals(messages));
|
||||
verify(() => mockChatRepository.getChatMessages(
|
||||
sessionId,
|
||||
limit: 50,
|
||||
beforeMessageId: null,
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('gets messages with custom parameters', () async {
|
||||
const sessionId = 'session_123';
|
||||
const limit = 20;
|
||||
const beforeMessageId = 'msg_123';
|
||||
final messages = <ChatMessageResponse>[];
|
||||
|
||||
when(() => mockChatRepository.getChatMessages(
|
||||
sessionId,
|
||||
limit: limit,
|
||||
beforeMessageId: beforeMessageId,
|
||||
)).thenAnswer((_) async => messages);
|
||||
|
||||
final result = await chatService.getMessages(
|
||||
sessionId,
|
||||
limit: limit,
|
||||
beforeMessageId: beforeMessageId,
|
||||
);
|
||||
|
||||
expect(result, equals(messages));
|
||||
verify(() => mockChatRepository.getChatMessages(
|
||||
sessionId,
|
||||
limit: limit,
|
||||
beforeMessageId: beforeMessageId,
|
||||
)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('updateSession', () {
|
||||
test('updates session successfully', () async {
|
||||
const sessionId = 'session_123';
|
||||
final updates = {'title': 'New Title'};
|
||||
final expectedSession = ChatBasicSession(
|
||||
id: sessionId,
|
||||
userId: 'user_123',
|
||||
title: 'New Title',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
when(() => mockChatRepository.updateChatSession(sessionId, updates))
|
||||
.thenAnswer((_) async => expectedSession);
|
||||
|
||||
final result = await chatService.updateSession(sessionId, updates);
|
||||
|
||||
expect(result, equals(expectedSession));
|
||||
verify(() => mockChatRepository.updateChatSession(sessionId, updates)).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('deleteSession', () {
|
||||
test('deletes session successfully', () async {
|
||||
const sessionId = 'session_123';
|
||||
|
||||
when(() => mockChatRepository.deleteChatSession(sessionId))
|
||||
.thenAnswer((_) async => {});
|
||||
|
||||
await chatService.deleteSession(sessionId);
|
||||
|
||||
verify(() => mockChatRepository.deleteChatSession(sessionId)).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:mnemo_cards_chat/domain/models/chat_api_simple.dart';
|
||||
import 'package:mnemo_cards_chat/domain/models/chat_basic.dart';
|
||||
import 'package:mnemo_cards_chat/domain/models/chat_message.dart';
|
||||
import 'package:mnemo_cards_chat/domain/services/chat_service.dart';
|
||||
import 'package:mnemo_cards_chat/domain/state/chat_state_manager.dart';
|
||||
|
||||
// Mock classes
|
||||
class MockChatService extends Mock implements ChatService {}
|
||||
|
||||
void main() {
|
||||
group('ChatStateManager', () {
|
||||
late ChatStateManager stateManager;
|
||||
late MockChatService mockChatService;
|
||||
|
||||
setUp(() {
|
||||
mockChatService = MockChatService();
|
||||
stateManager = ChatStateManager(chatService: mockChatService);
|
||||
});
|
||||
|
||||
test('should initialize with loading state', () {
|
||||
expect(stateManager.state, equals(const ChatState.loading()));
|
||||
});
|
||||
|
||||
test('can be instantiated with proper dependencies', () {
|
||||
expect(stateManager, isNotNull);
|
||||
expect(stateManager, isA<ChatStateManager>());
|
||||
});
|
||||
|
||||
test('has proper constructor signature', () {
|
||||
expect(
|
||||
() => ChatStateManager(chatService: mockChatService),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('state is accessible', () {
|
||||
expect(stateManager.state, isA<ChatState>());
|
||||
});
|
||||
|
||||
test('has initializeChat method', () {
|
||||
expect(stateManager.initializeChat, isA<Function>());
|
||||
});
|
||||
|
||||
test('has sendTextMessage method', () {
|
||||
expect(stateManager.sendTextMessage, isA<Function>());
|
||||
});
|
||||
|
||||
test('has sendAudioMessage method', () {
|
||||
expect(stateManager.sendAudioMessage, isA<Function>());
|
||||
});
|
||||
|
||||
test('has loadMoreMessages method', () {
|
||||
expect(stateManager.loadMoreMessages, isA<Function>());
|
||||
});
|
||||
|
||||
test('has clearError method', () {
|
||||
expect(stateManager.clearError, isA<Function>());
|
||||
});
|
||||
|
||||
test('has currentSession getter', () {
|
||||
expect(stateManager.currentSession, isNull);
|
||||
});
|
||||
|
||||
test('has messagesCount getter', () {
|
||||
expect(stateManager.messagesCount, equals(0));
|
||||
});
|
||||
|
||||
group('initializeChat', () {
|
||||
test('initializes chat successfully', () async {
|
||||
const sessionId = 'session_123';
|
||||
final session = ChatBasicSession(
|
||||
id: sessionId,
|
||||
userId: 'user_123',
|
||||
title: 'Test Chat',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
final messages = [
|
||||
ChatMessageResponse(
|
||||
messageId: 'msg_1',
|
||||
sessionId: sessionId,
|
||||
content: 'Hello',
|
||||
senderId: 'user',
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
when(() => mockChatService.getSession(sessionId))
|
||||
.thenAnswer((_) async => session);
|
||||
when(() => mockChatService.getMessages(sessionId, limit: 50))
|
||||
.thenAnswer((_) async => messages);
|
||||
|
||||
await stateManager.initializeChat(sessionId);
|
||||
|
||||
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||
final loadedState = stateManager.state as ChatStateLoaded;
|
||||
expect(loadedState.session, equals(session));
|
||||
expect(loadedState.messages.length, equals(1));
|
||||
expect(loadedState.isSendingMessage, isFalse);
|
||||
expect(loadedState.hasMoreMessages, isFalse);
|
||||
expect(stateManager.currentSession, equals(session));
|
||||
expect(stateManager.messagesCount, equals(1));
|
||||
|
||||
verify(() => mockChatService.getSession(sessionId)).called(1);
|
||||
verify(() => mockChatService.getMessages(sessionId, limit: 50)).called(1);
|
||||
});
|
||||
|
||||
test('handles initialization error', () async {
|
||||
const sessionId = 'session_123';
|
||||
final error = Exception('Network error');
|
||||
|
||||
when(() => mockChatService.getSession(sessionId))
|
||||
.thenThrow(error);
|
||||
|
||||
await stateManager.initializeChat(sessionId);
|
||||
|
||||
expect(stateManager.state, isA<ChatStateError>());
|
||||
final errorState = stateManager.state as ChatStateError;
|
||||
expect(errorState.message, contains('Failed to load chat'));
|
||||
expect(stateManager.currentSession, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('sendTextMessage', () {
|
||||
late ChatBasicSession session;
|
||||
|
||||
setUp(() {
|
||||
session = ChatBasicSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_123',
|
||||
title: 'Test Chat',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
});
|
||||
|
||||
test('sends text message successfully when chat is initialized', () async {
|
||||
// First initialize chat
|
||||
when(() => mockChatService.getSession(any()))
|
||||
.thenAnswer((_) async => session);
|
||||
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||
.thenAnswer((_) async => []);
|
||||
await stateManager.initializeChat(session.id);
|
||||
|
||||
// Now send message
|
||||
const content = 'Hello, world!';
|
||||
final response = ChatMessageResponse(
|
||||
messageId: 'msg_123',
|
||||
sessionId: session.id,
|
||||
content: 'Assistant response',
|
||||
senderId: 'assistant',
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
when(() => mockChatService.sendTextMessage(session.id, content))
|
||||
.thenAnswer((_) async => response);
|
||||
|
||||
await stateManager.sendTextMessage(content);
|
||||
|
||||
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||
final loadedState = stateManager.state as ChatStateLoaded;
|
||||
expect(loadedState.messages.length, equals(2)); // User + Assistant messages
|
||||
expect(loadedState.isSendingMessage, isFalse);
|
||||
|
||||
// Check user message
|
||||
final userMessage = loadedState.messages[0] as TextMessage;
|
||||
expect(userMessage.sender.id, equals('current_user'));
|
||||
expect(userMessage.content, equals(content));
|
||||
expect(userMessage.status, equals(MessageStatus.sent));
|
||||
|
||||
// Check assistant message
|
||||
final assistantMessage = loadedState.messages[1] as TextMessage;
|
||||
expect(assistantMessage.sender.id, equals('assistant'));
|
||||
expect(assistantMessage.content, equals('Assistant response'));
|
||||
|
||||
verify(() => mockChatService.sendTextMessage(session.id, content)).called(1);
|
||||
});
|
||||
|
||||
test('handles send text message error', () async {
|
||||
// Initialize chat first
|
||||
when(() => mockChatService.getSession(any()))
|
||||
.thenAnswer((_) async => session);
|
||||
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||
.thenAnswer((_) async => []);
|
||||
await stateManager.initializeChat(session.id);
|
||||
|
||||
// Send message with error
|
||||
const content = 'Hello';
|
||||
final error = Exception('Network error');
|
||||
|
||||
when(() => mockChatService.sendTextMessage(session.id, content))
|
||||
.thenThrow(error);
|
||||
|
||||
await stateManager.sendTextMessage(content);
|
||||
|
||||
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||
final loadedState = stateManager.state as ChatStateLoaded;
|
||||
expect(loadedState.isSendingMessage, isFalse);
|
||||
expect(loadedState.errorMessage, contains('Failed to send message'));
|
||||
expect(loadedState.messages.length, equals(1)); // Only user message
|
||||
|
||||
// Check that user message has error status
|
||||
final userMessage = loadedState.messages[0] as AudioMessage;
|
||||
expect(userMessage.status, equals(MessageStatus.error));
|
||||
});
|
||||
|
||||
test('does nothing if chat not initialized', () async {
|
||||
await stateManager.sendTextMessage('Hello');
|
||||
|
||||
verifyNever(() => mockChatService.sendTextMessage(any(), any()));
|
||||
});
|
||||
});
|
||||
|
||||
group('sendAudioMessage', () {
|
||||
late ChatBasicSession session;
|
||||
|
||||
setUp(() {
|
||||
session = ChatBasicSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_123',
|
||||
title: 'Test Chat',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
});
|
||||
|
||||
test('sends audio message successfully', () async {
|
||||
// Initialize chat
|
||||
when(() => mockChatService.getSession(any()))
|
||||
.thenAnswer((_) async => session);
|
||||
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||
.thenAnswer((_) async => []);
|
||||
await stateManager.initializeChat(session.id);
|
||||
|
||||
// Send audio message
|
||||
final audioData = Uint8List(1024);
|
||||
const duration = Duration(seconds: 5);
|
||||
final response = ChatMessageResponse(
|
||||
messageId: 'msg_123',
|
||||
sessionId: session.id,
|
||||
content: 'Audio processed',
|
||||
senderId: 'assistant',
|
||||
timestamp: DateTime.now(),
|
||||
audioUrl: 'https://example.com/audio.mp3',
|
||||
durationMs: 5000,
|
||||
fileSize: 1024,
|
||||
);
|
||||
|
||||
when(() => mockChatService.sendAudioMessage(
|
||||
session.id,
|
||||
audioData,
|
||||
duration,
|
||||
transcription: null,
|
||||
)).thenAnswer((_) async => response);
|
||||
|
||||
await stateManager.sendAudioMessage(audioData, duration);
|
||||
|
||||
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||
final loadedState = stateManager.state as ChatStateLoaded;
|
||||
expect(loadedState.messages.length, equals(2)); // User + Assistant messages
|
||||
|
||||
// Check user audio message
|
||||
final userMessage = loadedState.messages[0] as AudioMessage;
|
||||
expect(userMessage.sender.id, equals('current_user'));
|
||||
expect(userMessage.duration, equals(duration));
|
||||
expect(userMessage.status, equals(MessageStatus.sent));
|
||||
|
||||
// Check assistant message
|
||||
final assistantMessage = loadedState.messages[1] as TextMessage;
|
||||
expect(assistantMessage.sender.id, equals('assistant'));
|
||||
expect(assistantMessage.content, equals('Audio processed'));
|
||||
|
||||
verify(() => mockChatService.sendAudioMessage(
|
||||
session.id,
|
||||
audioData,
|
||||
duration,
|
||||
transcription: null,
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('sends audio message with transcription', () async {
|
||||
// Initialize chat
|
||||
when(() => mockChatService.getSession(any()))
|
||||
.thenAnswer((_) async => session);
|
||||
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||
.thenAnswer((_) async => []);
|
||||
await stateManager.initializeChat(session.id);
|
||||
|
||||
// Send audio with transcription
|
||||
final audioData = Uint8List(2048);
|
||||
const duration = Duration(seconds: 3);
|
||||
const transcription = 'Hello from audio';
|
||||
|
||||
when(() => mockChatService.sendAudioMessage(
|
||||
session.id,
|
||||
audioData,
|
||||
duration,
|
||||
transcription: transcription,
|
||||
)).thenAnswer((_) async => ChatMessageResponse(
|
||||
messageId: 'msg_123',
|
||||
sessionId: session.id,
|
||||
content: 'Response',
|
||||
senderId: 'assistant',
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
|
||||
await stateManager.sendAudioMessage(audioData, duration, transcription: transcription);
|
||||
|
||||
verify(() => mockChatService.sendAudioMessage(
|
||||
session.id,
|
||||
audioData,
|
||||
duration,
|
||||
transcription: transcription,
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('does nothing if chat not initialized', () async {
|
||||
final audioData = Uint8List(1024);
|
||||
await stateManager.sendAudioMessage(audioData, const Duration(seconds: 1));
|
||||
|
||||
verifyNever(() => mockChatService.sendAudioMessage(any(), any(), any()));
|
||||
});
|
||||
});
|
||||
|
||||
group('loadMoreMessages', () {
|
||||
late ChatBasicSession session;
|
||||
|
||||
setUp(() {
|
||||
session = ChatBasicSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_123',
|
||||
title: 'Test Chat',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
});
|
||||
|
||||
test('loads more messages successfully', () async {
|
||||
// Initialize with one message
|
||||
final initialMessages = [
|
||||
ChatMessageResponse(
|
||||
messageId: 'msg_1',
|
||||
sessionId: session.id,
|
||||
content: 'Initial message',
|
||||
senderId: 'user',
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
when(() => mockChatService.getSession(any()))
|
||||
.thenAnswer((_) async => session);
|
||||
when(() => mockChatService.getMessages(any(), limit: 50))
|
||||
.thenAnswer((_) async => initialMessages);
|
||||
await stateManager.initializeChat(session.id);
|
||||
|
||||
// Load more messages
|
||||
final moreMessages = [
|
||||
ChatMessageResponse(
|
||||
messageId: 'msg_0',
|
||||
sessionId: session.id,
|
||||
content: 'Older message',
|
||||
senderId: 'assistant',
|
||||
timestamp: DateTime.now().subtract(const Duration(hours: 1)),
|
||||
),
|
||||
];
|
||||
|
||||
when(() => mockChatService.getMessages(
|
||||
session.id,
|
||||
limit: 50,
|
||||
beforeMessageId: 'msg_1',
|
||||
)).thenAnswer((_) async => moreMessages);
|
||||
|
||||
await stateManager.loadMoreMessages();
|
||||
|
||||
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||
final loadedState = stateManager.state as ChatStateLoaded;
|
||||
expect(loadedState.messages.length, equals(2));
|
||||
expect(loadedState.isLoadingMore, isFalse);
|
||||
expect(loadedState.hasMoreMessages, isFalse); // Only 1 more message loaded
|
||||
|
||||
// Messages should be in chronological order (oldest first)
|
||||
expect(loadedState.messages[0].maybeMap(
|
||||
text: (msg) => msg.content,
|
||||
audio: (msg) => msg.transcription,
|
||||
), equals('Older message'));
|
||||
});
|
||||
|
||||
test('does nothing if already loading more', () async {
|
||||
// Initialize chat
|
||||
when(() => mockChatService.getSession(any()))
|
||||
.thenAnswer((_) async => session);
|
||||
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||
.thenAnswer((_) async => []);
|
||||
await stateManager.initializeChat(session.id);
|
||||
|
||||
// Start loading more
|
||||
stateManager.state = (stateManager.state as ChatStateLoaded).copyWith(isLoadingMore: true);
|
||||
|
||||
await stateManager.loadMoreMessages();
|
||||
|
||||
verifyNever(() => mockChatService.getMessages(any(), limit: any(named: 'limit'), beforeMessageId: any(named: 'beforeMessageId')));
|
||||
});
|
||||
|
||||
test('does nothing if no more messages', () async {
|
||||
// Initialize chat
|
||||
when(() => mockChatService.getSession(any()))
|
||||
.thenAnswer((_) async => session);
|
||||
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||
.thenAnswer((_) async => []);
|
||||
await stateManager.initializeChat(session.id);
|
||||
|
||||
// Set hasMoreMessages to false
|
||||
stateManager.state = (stateManager.state as ChatStateLoaded).copyWith(hasMoreMessages: false);
|
||||
|
||||
await stateManager.loadMoreMessages();
|
||||
|
||||
verifyNever(() => mockChatService.getMessages(any(), limit: any(named: 'limit'), beforeMessageId: any(named: 'beforeMessageId')));
|
||||
});
|
||||
});
|
||||
|
||||
group('clearError', () {
|
||||
test('clears error message from loaded state', () {
|
||||
stateManager.state = ChatState.loaded(
|
||||
session: ChatBasicSession(
|
||||
id: 'session_123',
|
||||
userId: 'user_123',
|
||||
title: 'Test',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
),
|
||||
messages: [],
|
||||
errorMessage: 'Some error',
|
||||
);
|
||||
|
||||
stateManager.clearError();
|
||||
|
||||
final loadedState = stateManager.state as ChatStateLoaded;
|
||||
expect(loadedState.errorMessage, isNull);
|
||||
});
|
||||
|
||||
test('does nothing for non-loaded states', () {
|
||||
stateManager.clearError();
|
||||
expect(stateManager.state, isA<ChatState>()); // Should not crash
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
34
debug_report/complete_report_20250904_223911.txt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
🎯 ИТОГОВЫЙ ОТЧЕТ ПОЛНОЙ АВТОМАТИЧЕСКОЙ СИСТЕМЫ
|
||||
============================================
|
||||
|
||||
🕐 Время выполнения: Thu Sep 4 22:39:20 MSK 2025
|
||||
🆔 ID сессии: 20250904_223911
|
||||
⏱️ Время мониторинга: 30 секунд
|
||||
🌐 Браузер: chrome
|
||||
|
||||
📋 РЕЗУЛЬТАТЫ ЭТАПОВ:
|
||||
🔍 Этап 1 (Отладка): ❌ Ошибки
|
||||
🔧 Этап 2 (Исправление): ❌ Ошибки
|
||||
✅ Этап 3 (Валидация): ❌ Ошибки
|
||||
🧪 Этап 4 (Тестирование): ❌ Ошибки
|
||||
|
||||
📊 ФИНАЛЬНАЯ СТАТИСТИКА ПРОБЛЕМ:
|
||||
📋 Всего проблем: 10370
|
||||
❌ Ошибок: 9881
|
||||
⚠️ Предупреждений: 215
|
||||
|
||||
💡 РЕКОМЕНДАЦИИ:
|
||||
🔧 Требуются дополнительные исправления
|
||||
📋 Проверьте логи ошибок
|
||||
🔄 Запустите систему повторно
|
||||
🧪 Настройте автоматические тесты
|
||||
📊 Интегрируйте в CI/CD pipeline
|
||||
|
||||
📁 СОЗДАННЫЕ ФАЙЛЫ:
|
||||
📋 Отчеты отладки: debug_report/error_report_*.txt
|
||||
🔧 Отчеты исправлений: debug_report/smart_fix_report_*.txt
|
||||
📊 Анализ: debug_report/flutter_analysis*.txt
|
||||
🧪 Тестирование: debug_report/build_test.log
|
||||
📝 Логи: debug_report/auto_fix_*.log, debug_report/smart_fix_*.log
|
||||
|
||||
|
||||
10374
debug_report/validation_analysis.txt
Normal file
43
funny_letters/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
30
funny_letters/.metadata
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "b25305a8832cfc6ba632a7f87ad455e319dccce8"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||
- platform: web
|
||||
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
25
funny_letters/.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "funny_letters",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "funny_letters (profile mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "funny_letters (release mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
funny_letters/Funny_letters_compat.zip
Normal file
16
funny_letters/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# funny_letters
|
||||
|
||||
Funny letters
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
28
funny_letters/analysis_options.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
plugins {
|
||||
id "com.android.application"
|
||||
// START: FlutterFire Configuration
|
||||
id 'com.google.gms.google-services'
|
||||
// END: FlutterFire Configuration
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
|
@ -25,15 +22,8 @@ if (flutterVersionName == null) {
|
|||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
namespace "com.cinnabarflower.mnemo_cards"
|
||||
namespace "com.example.funny_letters"
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
|
|
@ -52,26 +42,20 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.cinnabarflower.mnemo_cards"
|
||||
applicationId "com.example.funny_letters"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion 25
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,8 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<application
|
||||
android:label="Mnemo cards"
|
||||
android:label="funny_letters"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||
android:value="ca-app-pub-3940256099942544~3347511713"/>
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
|
@ -35,9 +30,4 @@
|
|||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TTS_SERVICE" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.cinnabarflower.mnemo_cards
|
||||
package com.example.funny_letters
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
18
funny_letters/android/app/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -3,8 +3,5 @@
|
|||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<application
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
|
@ -1,16 +1,11 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.8.20'
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
// START: FlutterFire Configuration
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
|
||||
// END: FlutterFire Configuration
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
org.gradle.jvmargs=-Xmx4608M
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
5
funny_letters/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
29
funny_letters/android/settings.gradle
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}
|
||||
settings.ext.flutterSdkPath = flutterSdkPath()
|
||||
|
||||
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
BIN
funny_letters/assets/effect.mp3
Normal file
BIN
funny_letters/assets/go.mp3
Normal file
BIN
funny_letters/assets/images/Match3Clover.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
funny_letters/assets/images/Match3Heart.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
funny_letters/assets/images/Match3Moon.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
funny_letters/assets/images/Match3Star.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
funny_letters/assets/images/Match3Water.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
funny_letters/assets/images/button_back.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
funny_letters/assets/images/button_ok.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
funny_letters/assets/images/button_play.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
funny_letters/assets/images/button_rule.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
funny_letters/assets/images/button_settings.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
funny_letters/assets/images/game_over.png
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
funny_letters/assets/images/health_back.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
funny_letters/assets/images/health_front.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
funny_letters/assets/images/heart.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
BIN
funny_letters/assets/images/icons/play.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
funny_letters/assets/images/icons/restart.png
Normal file
|
After Width: | Height: | Size: 264 B |
BIN
funny_letters/assets/images/letters.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
funny_letters/assets/images/letters/icon_a.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
funny_letters/assets/images/letters/icon_b.png
Normal file
|
After Width: | Height: | Size: 9 KiB |