Compare commits

...

3 Commits

7 changed files with 2223 additions and 2717 deletions
+94
View File
@@ -0,0 +1,94 @@
# Локальный OCR-сервис Xiaomi-OCR-0
## Контекст
На этой машине развёрнута и работает локальная OCR-модель Xiaomi-OCR-0 (SeerRay-Lab/Xiaomi-OCR-0). Это vision-language модель на 0.8B параметров (основа — Qwen3.5-0.8B, архитектура Qwen3_5ForConditionalGeneration). Она умеет:
- Разбор документов → Markdown (таблицы в OTSL → HTML, формулы в LaTeX, порядок чтения).
- KIE (извлечение ключевой информации) → JSON по заданной схеме полей.
- VQA — короткие ответы на вопросы по изображению.
- Разбор отдельных регионов: текст, таблица, формула.
- Сканы, чеки, формулы (в т.ч. рукописные), рукописный текст, каллиграфию, PDF (через рендер страниц).
- Всё работает только локально, без облачных API. Ключ API не нужен.
## Окружение
| Параметр | Значение |
|---|---|
| ОС | Ubuntu 26.04, Linux x86_64 |
| GPU | NVIDIA RTX 4060 Laptop, 8 ГБ VRAM (vLLM занимает ~75%) |
| Каталог проекта | `/home/alexives/OCR Xiaomi neuro` (в пути есть пробел — всегда бери его в кавычки) |
| Python venv | `/home/alexives/OCR Xiaomi neuro/.venv` → симлинк на `/mnt/data/alexives_cache/xiaomi_ocr_venv` (Python 3.12) |
| Кэш HF (веса модели) | `/mnt/data/alexives_cache/huggingface` |
| Движок инференса | vLLM 0.31.0, CUDA 13 |
| Обязательная переменная окружения | `VLLM_USE_FLASHINFER_SAMPLER=0` (без неё vLLM падает: нет системного nvcc/CUDA toolkit для JIT FlashInfer) |
> На системном разделе `/` мало места (~3.7 ГБ). Любые тяжёлые кэши и зависимости клади в `/mnt/data/alexives_cache/`.
## Запуск и остановка
```bash
"/home/alexives/OCR Xiaomi neuro/start.sh" # запускает vLLM (:8000) и веб-демо (:8787), ждёт готовности
"/home/alexives/OCR Xiaomi neuro/stop.sh" # останавливает оба процесса
```
Холодный старт vLLM занимает ~2 минуты (torch.compile + захват графов CUDA). Перед запросами опрашивай `GET /v1/models`, пока не придёт ответ.
Логи: `vllm.log` и `demo.log` в каталоге проекта.
## Сервисы
| Сервис | URL | Назначение |
|---|---|---|
| vLLM (OpenAI-совместимый API) | `http://127.0.0.1:8000/v1` | Прямой инференс. Основная точка интеграции. |
| Веб-демо + REST | `http://127.0.0.1:8787` | UI в браузере и упрощённый REST с готовой пред- и постобработкой |
- Имя модели в запросах: `SeerRay-Lab/Xiaomi-OCR-0` (именно так, с префиксом).
- Проверка готовности: `curl -s http://127.0.0.1:8000/v1/models` и `curl -s http://127.0.0.1:8787/api/health` (`"ready": true`).
- Сервисы слушают только loopback (`127.0.0.1`). Не открывай их наружу.
## Вариант A: прямой вызов vLLM (рекомендуется)
### Обязательные правила запроса
1. Ровно одно изображение на запрос, сначала изображение, потом текст, в одном user-сообщении.
2. Изображение передаётся как data-URL: `data:image/png;base64,...`.
3. Thinking-режим выключен: `"chat_template_kwargs": {"enable_thinking": false}`.
4. Жадное декодирование (как в эталонном пайплайне):
`"temperature": 0, "top_p": 0.8, "presence_penalty": 0.0, "repetition_penalty": 1.0`
5. `max_tokens`: 4096 по умолчанию для целой страницы (максимум 16384, но картинка + промпт + вывод должны влезть в контекст 16384).
6. Если `finish_reason == "length"` — вывод обрезан: увеличь `max_tokens` или уменьши картинку / раздели её на части.
7. Без системного сообщения. Промпты — на английском, дословно.
### Промпты (дословно)
- **Целая страница документа → Markdown:**
`Extract all information from the main body of the document image and represent it in markdown format, ignoring headers and footers. Tables should be expressed in OTSL format, formulas in the document should be represented using LATEX format, and the parsing should be organized according to the reading order.`
- **Регион с текстом / заголовком:**
`Extract the text in the image.`
- **Регион с таблицей:**
`Parse the table in the image into OTSL.`
- **Регион с формулой:**
`Identify the formula in the image and represent it using LATEX format.`
- **KIE с известными полями:**
`Extract key information in the image`
`Please output the key information in JSON format according to the following schema:`
`{"company": "", "date": "", "total": ""}`
- **KIE без заданных полей:**
`Extract the key information from the document image as a JSON object. Use concise field names. Return JSON only; use an empty string for missing values.`
- **VQA:**
`<ВОПРОС>
Answer the question using a single word or phrase.`
### Постобработка
Таблицы приходят в OTSL (токены `<fcel>`, `<ecel>`, `<lcel>`, `<ucel>`, `<xcel>`, `<nl>`). Конвертируй в HTML:
```python
import sys; sys.path.insert(0, "/home/alexives/OCR Xiaomi neuro")
from postprocess.otsl import convert_otsl_to_html
from postprocess.document import finalize_document
md = finalize_document(raw_model_text)
```
## Вариант B: REST веб-демо (:8787)
POST `http://127.0.0.1:8787/api/ocr`, `Content-Type: application/json`:
```json
{
"task": "document",
"image_base64": "<base64 или data:image/...;base64,...>",
"image_mime": "image/jpeg",
"max_tokens": 4096,
"temperature": 0
}
```
+2 -2
View File
@@ -5265,9 +5265,9 @@
"id": "card_17",
"name": "Аист",
"symbol": "🪶 🕊️",
"lore": "Аист олицетворяет закон циклического обновления, сдвигающий застывшую ситуацию с места через перемену внешних условий, переоценку ценностей и неизбежное возвращение к своим истокам.",
"lore": "Аист олицетворяет закон циклического обновления, сдвигающий застывшую ситуацию с места через перемену внешних условий, переоценку ценностей и неизбежное возвращение к своим истокам. По Дж. Куперу — солярная птица вод творения, благовещения и рождения новой жизни.",
"group": "Малая колода · №17",
"meaning": "Образ аиста соединяет в себе порыв к перелетам и безусловную верность родному гнезду. Это символ плодородия, заботы о потомстве, чистоты намерений и устойчивой связи со своими корнями и традициями. Птица ежегодно улетает в чужие края, но неизменно возвращается домой, олицетворяя гармоничное сочетание открытости новому опыту и преданности постоянным ценностям.\n\nВ раскладе карта выступает активным двигателем: она выводит любую ситуацию из тупика и застоя, делая перемены неотвратимыми. Направление динамики всецело задают соседние карты. Предшествующая карта показывает источник, причину или область зарождения перемен, а последующая раскрывает их характер и результат. В гармоничном окружении Аист сулит счастливые перемены и рост, но в сочетании с неблагоприятными символами сигнализирует о сдвиге к худшему. Рядом с картой Ребенка или бланкой женщины прямо указывает на зачатие, беременность и рождение детей.\n\nСроки событий по Аисту охватывают период от 20 до 55 дней. Изменения носят не мимолетный, а долговременный характер. Временной рисунок карты подчинен цикличности и равным интервалам: развитие ситуации идет закономерными волнами, часто возвращая в настоящее незавершенные вопросы из прошлого, которые требуют окончательного решения на новом уровне.\n\nПрактическое проявление карты охватывает любые формы смены обстановки — от локальной перестановки в доме до переезда на новую квартиру, дальних перелетов и командировок. В делах карта дает импульс к развитию, указывает на прибыльную торговлю, зарубежные контакты и прогрессивные инициативы. В отношениях она знаменует внутреннюю переоценку, возрождение чувств, выход на этап зрелых обязательств или встречу со старыми близкими. На телесном уровне связана с ногами и двигательной функцией, а для восстановления сил рекомендует смену обстановки или поездку на оздоровительный курорт.\n\nВ роли сигнификатора человека Аист описывает зрелую личность (примерно от 35 до 50 лет) — динамичную, общительную, любезную и образованную, искренне преданную семье и дому. Часто указывает на заботливую женщину или даму сердца. В профессиональном плане представляет людей помогающих специальностей (врачи, акушеры, целители, учителя, воспитатели), а также тех, чья деятельность связана с дорогами, движением и публичностью — перевозчиков, спортсменов, артистов и журналистов.",
"meaning": "Образ аиста соединяет в себе порыв к перелетам и безусловную верность родному гнезду. Это символ плодородия, заботы о потомстве, чистоты намерений и устойчивой связи со своими корнями и традициями. Птица ежегодно улетает в чужие края, но неизменно возвращается домой, олицетворяя гармоничное сочетание открытости новому опыту и преданности постоянным ценностям.\n\nВ раскладе карта выступает активным двигателем: она выводит любую ситуацию из тупика и застоя, делая перемены неотвратимыми. Направление динамики всецело задают соседние карты. Предшествующая карта показывает источник, причину или область зарождения перемен, а последующая раскрывает их характер и результат. В гармоничном окружении Аист сулит счастливые перемены и рост, но в сочетании с неблагоприятными символами сигнализирует о сдвиге к худшему. Рядом с картой Ребенка или бланкой женщины прямо указывает на зачатие, беременность и рождение детей.\n\nСроки событий по Аисту охватывают период от 20 до 55 дней. Изменения носят не мимолетный, а долговременный характер. Временной рисунок карты подчинен цикличности и равным интервалам: развитие ситуации идет закономерными волнами, часто возвращая в настоящее незавершенные вопросы из прошлого, которые требуют окончательного решения на новом уровне.\n\nПрактическое проявление карты охватывает любые формы смены обстановки — от локальной перестановки в доме до переезда на новую квартиру, дальних перелетов и командировок. В делах карта дает импульс к развитию, указывает на прибыльную торговлю, зарубежные контакты и прогрессивные инициативы. В отношениях она знаменует внутреннюю переоценку, возрождение чувств, выход на этап зрелых обязательств или встречу со старыми близкими. На телесном уровне связана с ногами и двигательной функцией, а для восстановления сил рекомендует смену обстановки или поездку на оздоровительный курорт.\n\nВ роли сигнификатора человека Аист описывает зрелую личность (примерно от 35 до 50 лет) — динамичную, общительную, любезную и образованную, искренне преданную семье и дому. Часто указывает на заботливую женщину или даму сердца. В профессиональном плане представляет людей помогающих специальностей (врачи, акушеры, целители, учителя, воспитатели), а также тех, чья деятельность связана с дорогами, движением и публичностью — перевозчиков, спортсменов, артистов и журналистов.\n\nСакральный символизм (по Дж. Куперу): солярная птица, уничтожающая зловещих рептилий и связанная с водами творения. «Приносимые аистом» дети в виде эмбрионов покоятся в лоне Матери-Земли и водах бытия, где их находят аисты. Символизирует приход весны, Благовещение, чистоту, целомудрие, сыновнюю почтительность к родителям и защиту богини Геры / Юноны как подательницы жизни.",
"shadow": "Тень закона заключается в нестабильности, вечном беспокойстве и утрате точки опоры. При негативном раскладе стремление к обновлению оборачивается вынужденным бегством, потерями и разрушением привычного уклада. Вместо плодотворного развития человек рискует оказаться в ловушке дурной бесконечности, раз за разом наступая на одни и те же нерешенные проблемы прошлого.",
"meanings": [],
"pairs": [
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -73937,29 +73937,29 @@
"state_destructive": "Перемена места и рождение, которые путают с вечным переездом.",
"culture_sumer_light": "",
"culture_sumer_shadow": "",
"culture_egypt_light": "",
"culture_egypt_light": "[Дж. Купер · «Энциклопедия символов»]: «У египтян аист олицетворял сыновнюю почтительность, так как считалось, что он кормит своих родителей в их старости.»",
"culture_egypt_shadow": "",
"culture_kabbalah_light": "",
"culture_kabbalah_shadow": "",
"culture_antiquity_light": "[Джек Тресиддер · «Словарь символов»]: «На Востоке - популярная эмблема долголетия, а в даосизме - бессмертия. Во многих культурах аист символизирует дочернюю и сыновнюю преданность из-за поверья, что аисты кормят своих родителей, как и своих детей. Эта трогательная забота и ассоциация с новой жизнью (аисты прилетают весной) явились причиной того, что аист стал священной птицей древнегреческой богини Геры (в римской...»",
"culture_antiquity_light": "[Джек Тресиддер · «Словарь символов»]: «На Востоке - популярная эмблема долголетия, а в даосизме - бессмертия. Во многих культурах аист символизирует дочернюю и сыновнюю преданность из-за поверья, что аисты кормят своих родителей, как и своих детей. Эта трогательная забота и ассоциация с новой жизнью (аисты прилетают весной) явились причиной того, что аист стал священной птицей древнегреческой богини Геры (в римской...»\n\n[Дж. Купер · «Энциклопедия символов»]: «В греческих мистериях богиня аистов изображалась в виде женщины — подательницы жизни, кормилицы и являлась атрибутом Геры. У римлян аист символизировал почтительность, сыновнюю привязанность и считался атрибутом Юноны.»",
"culture_antiquity_shadow": "",
"culture_celtic_light": "",
"culture_celtic_shadow": "",
"culture_nordic_light": "",
"culture_nordic_shadow": "",
"culture_slavic_light": "",
"culture_slavic_light": "[Дж. Купер · «Энциклопедия символов»]: «В славянских поверьях и христианской традиции аист символизирует целомудрие, чистоту, благочестие, покровительство семейному очагу и наступление весны.»",
"culture_slavic_shadow": "",
"culture_india_light": "",
"culture_india_shadow": "",
"culture_persia_light": "",
"culture_persia_shadow": "",
"culture_china_light": "",
"culture_china_light": "[Дж. Купер · «Энциклопедия символов»]: «У китайцев аист означает долгожительство, счастливую и умиротворенную старость, сыновнюю почтительность достойного, чуждого соблазнов, живущего в уединении отшельника.»",
"culture_china_shadow": "",
"culture_japan_light": "",
"culture_japan_shadow": "",
"culture_islam_light": "",
"culture_islam_shadow": "",
"culture_hermetic_light": "",
"culture_hermetic_light": "[Дж. Купер · «Энциклопедия символов»]: «Наряду с орлом и ибисом, аист уничтожает зловещих рептилий и является потому солнечной птицей. Но, как существо, связанное с водой и рыбой, он ассоциируется с водами творения. \"Приносимые аистом\" дети в виде эмбрионов находятся в лоне Матери-Земли и в водах творения, где их и находят при ловле рыбы аисты. Символизирует приход весны и новой жизни, благовещенская птица.»",
"culture_hermetic_shadow": "",
"culture_mesoamerica_light": "",
"culture_mesoamerica_shadow": "",
@@ -73973,7 +73973,7 @@
"will_vector": "Ты несёшь новое в гнездо или только меняешь крышу?",
"shadow_warning": "Перемена места и рождение, которые путают с вечным переездом."
},
"filled_count": 14,
"filled_count": 18,
"is_complete": false,
"updated_at": "2026-10-08T13:39:29.998946",
"illustrations": []
@@ -15,6 +15,7 @@ class CrossSystemComparisonScreen extends StatefulWidget {
}
class _CrossSystemComparisonScreenState extends State<CrossSystemComparisonScreen> {
final _repo = WordsRepository();
late SyncreticSession _session;
@override
@@ -152,6 +153,35 @@ class _CrossSystemComparisonScreenState extends State<CrossSystemComparisonScree
);
}
void _confirmDeleteReading(SyncreticReading reading) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: const Color(0xFF151829),
title: const Text('Удалить расклад?', style: TextStyle(color: Color(0xFFFFF9C4), fontSize: 16, fontWeight: FontWeight.bold)),
content: Text('Удалить «${reading.systemName} (${reading.layoutTitle})» из текущей сессии дня?', style: const TextStyle(color: Color(0xFFECEFF1), fontSize: 13)),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Отмена', style: TextStyle(color: Color(0xFF8C93B8))),
),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFFEF5350), foregroundColor: Colors.white),
onPressed: () async {
Navigator.pop(ctx);
await _repo.removeReadingFromSyncreticSession(reading.id);
setState(() {
_session = _repo.currentSyncreticSession;
});
HapticFeedback.mediumImpact();
},
child: const Text('Удалить'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final readings = _session.readings;
@@ -332,6 +362,11 @@ class _CrossSystemComparisonScreenState extends State<CrossSystemComparisonScree
],
),
),
IconButton(
icon: const Icon(Icons.close_rounded, size: 20, color: Color(0xFFEF5350)),
tooltip: 'Удалить из сессии',
onPressed: () => _confirmDeleteReading(reading),
),
],
),
const SizedBox(height: 12),
-16
View File
@@ -415,22 +415,6 @@ class _DivinationHubScreenState extends State<DivinationHubScreen> {
// Section 1: Core 999 Systems
_buildSectionHeader('СИСТЕМЫ ОРАКУЛА 999', Icons.auto_awesome_rounded),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: _buildSystemCard(
title: 'Слово Дня',
instructionId: 'word_of_day',
subtitle: 'Календарь & Квест',
icon: '☀️',
desc: 'Ежедневный архетип, коан для ума, микродействие и дневник',
accentColor: const Color(0xFFFFD54F),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const WordOfTheDayScreen())).then((_) => setState(() {})),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
+7
View File
@@ -715,6 +715,13 @@ class WordsRepository {
await saveSyncreticSession(updatedSession);
}
Future<void> removeReadingFromSyncreticSession(String readingId) async {
final current = currentSyncreticSession;
final updatedReadings = current.readings.where((r) => r.id != readingId).toList();
final updatedSession = current.copyWith(readings: updatedReadings);
await saveSyncreticSession(updatedSession);
}
Future<void> saveSyncreticSession(SyncreticSession session) async {
_currentSyncreticSession = session;
final idx = _syncreticSessions.indexWhere((s) => s.id == session.id);