diff --git a/AGENT.md b/AGENT.md index 5319f10..f839b33 100644 --- a/AGENT.md +++ b/AGENT.md @@ -1,4 +1,5 @@ -# Agent Guardrails (NDC_1C) +CRITICAL ENCODING RULE: Always read/write text files strictly as UTF-8, never ANSI/CP1251 fallback, and verify no mojibake before finishing. +# Agent Guardrails (NDC_1C) ## Scope This repository has two assistant lanes: diff --git a/AGENTS.md b/AGENTS.md index 07df375..58decb8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,6 @@ +## encoding_rule +- All source/code/config/docs files must be saved and edited in UTF-8 without BOM; never write mojibake placeholders or replacement characters. + ## graphify This project has a graphify knowledge graph at graphify-out/. diff --git a/docs/TECH/README.md b/docs/TECH/README.md new file mode 100644 index 0000000..a03ae0b --- /dev/null +++ b/docs/TECH/README.md @@ -0,0 +1,10 @@ +# TECH Docs Index + +Актуальные документы по operational-контру ассистента: + +1. `assistant_canon.md` - канон поведения ассистента. +2. `capabilities_registry.json` - реестр поддерживаемых возможностей. +3. `manual_case_decision_schema.json` - схема решений ручной разметки. +4. `ui_markup_system.md` - рабочий процесс разметки через GUI. +5. `history_colibration.md` - сводка статуса и ближайших задач. + diff --git a/docs/TECH/history_colibration.md b/docs/TECH/history_colibration.md index 6b5f55e..455bb32 100644 --- a/docs/TECH/history_colibration.md +++ b/docs/TECH/history_colibration.md @@ -1,567 +1,85 @@ -Да, тут уже напрашивается не просто “ещё одно поле в автопрогонах”, а **нормальная управляющая схема**. -То есть у вас должно быть не только “модель ответила / оценка 5-балльная”, а три опоры: - -1. **эталон идеального поведения ассистента**; -2. **ручная разметка результата прогона с управленческим смыслом**; -3. **канонический файл возможностей ассистента по отработанным маршрутам 1С**. - -И тогда автопрогоны перестают быть просто логами, а становятся контуром развития системы. - -Ниже я собрал это так, чтобы можно было почти целиком отдать в Codex. - ---- - -# Как я бы это сформулировал концептуально - -## 1. Нужен отдельный блок: «Эталон поведения ассистента» - -Это не просто описание “каким хотелось бы видеть ответ”. -Это должен быть **формальный канон**, который понимают: - -* сам ассистент; -* система автопрогонов; -* пост-анализ; -* Codex, который потом дорабатывает маршруты и поведение. - -То есть это не prose-блок “идеальная работа”, а именно **Assistant Canon / Behavior Canon**. - -### Что в нём должно быть - -#### А. Что такое хороший ответ - -Хороший ответ ассистента: - -* отвечает по существу, если кейс реально покрыт; -* не врёт, если кейс не покрыт; -* не выдаёт технические внутренности вместо нормальной коммуникации; -* не ломается на смежных вопросах; -* умеет мягко ограничить себя; -* умеет предложить близкий поддерживаемый сценарий; -* умеет подсказать, где это обычно смотреть в 1С; -* не вываливает полный список возможностей без запроса; -* раскрывает возможности по группам и по мере уточнения. - -#### Б. Что такое плохой ответ - -Плохой ответ ассистента: - -* выдумывает функцию, которой нет; -* делает вид, что может точно ответить, когда не может; -* отвечает внутренним техническим языком; -* сухо отказывает без пользы; -* валит пользователя в огромный список умений; -* не понимает, когда вопрос надо передать в “не покрыто, но рядом”; -* не различает безопасный общий ответ и рискованный прикладной совет. - -#### В. Какой идеал поведения на границе покрытия - -Вот это вообще ключевой блок. - -Ассистент должен: - -* честно понимать границу своих возможностей; -* не маскировать отсутствие маршрута; -* не ломаться; -* не отвечать “не поддерживается” в лоб; -* не уходить в системные сообщения; -* давать человекочитаемое ограничение; -* предлагать ближайший полезный путь. - -Это и есть ваш **эталон**. - ---- - -## 2. Нужна ручная разметка не только по качеству, но и по судьбе вопроса - -Вот это очень сильная мысль. -Пятибалльная оценка сама по себе почти бесполезна, потому что она **не говорит, что делать дальше**. - -Нужна ещё одна сущность: - -## **Decision Markup / Route Decision Markup** - -То есть по каждому прогону вы размечаете не только “норм / не норм”, а **каково управленческое решение по классу вопроса**. - -### Я бы добавил в UI не просто кнопку, а выпадающий классификатор - -Например: - -* `covered_ok` — кейс нормальный, покрывается, поведение ок; -* `covered_but_bad_answer` — кейс должен покрываться, но ответ плохой; -* `good_question_to_implement` — хороший вопрос, его надо брать в отработку; -* `out_of_scope_but_answer_softly` — вопрос не планируется покрывать, но нужно мягко и полезно отвечать; -* `unsafe_question_limit_strictly` — вопрос рискованный, на него нужно отвечать осторожно и ограниченно; -* `bad_test_case` — сам тестовый вопрос мусорный / нерелевантный; -* `needs_routing_extension` — нужен новый маршрут или расширение маршрутизации; -* `needs_capability_registry_update` — кейс выявил дыру в файле возможностей; -* `needs_dialog_policy_fix` — маршрут, возможно, не нужен, но политика ответа плохая. - -Вот это уже даст системе смысл. - -### Если хочется совсем по-простому - -Можно ввести более короткий список: - -* **Отрабатывается** -* **Должно отрабатываться** -* **Не будет отрабатываться** -* **Отвечать мягким ограничением** -* **Высокорисковый вопрос** -* **Плохой тест-кейс** - -Но я бы всё-таки оставил более инженерный набор, а в UI уже сделал человекочитаемые названия. - ---- - -## 3. Нужен канонический файл возможностей ассистента - -Да, это обязательно. И это должен быть не просто текстовый файл “что умеем”. -Это должен быть **Capabilities Registry / Supported Routes Registry**. - -И он должен быть источником истины для трёх вещей: - -* ответа ассистента; -* классификации покрываемости; -* автопрогонов и анализа. - -### Что там должно быть - -Для каждого маршрута / домена: - -* код маршрута; -* человекочитаемая группа; -* краткое описание; -* что реально умеется; -* что не умеется; -* обязательные параметры; -* типовые формулировки вопросов; -* похожие смежные сценарии; -* безопасные альтернативы; -* подсказка, где это обычно смотреть в 1С; -* уровень риска; -* статус зрелости: - - * production-ready - * partial - * planned - * deprecated - -### Пример групп - -Не надо сразу показывать всё пользователю. -Надо хранить глубоко, а наружу отдавать по группам. - -Например: - -* НДС -* Контрагенты -* Задолженности -* Деньги и остатки -* Платежи и движения -* Аналитика по периодам -* Справочные бухгалтерские вопросы - -А дальше уже внутри группы: - -* что умеется конкретно. - -То есть если юзер спрашивает “что ты можешь по НДС?”, ассистент отвечает не списком из 80 пунктов, а компактной группой возможностей по НДС. - ---- - -## 4. Надо отдельно зафиксировать правило раскрытия возможностей - -Это тоже очень важный продуктовый момент. - -### Ассистент не должен - -* автоматически вываливать весь список поддерживаемого; -* отвечать каталогом без запроса; -* перегружать пользователя техническими деталями маршрутов. - -### Ассистент должен - -* раскрывать возможности **по группам**; -* сначала давать верхнеуровневую сегментацию; -* при уточнении — углубляться; -* говорить в продуктовой, а не внутренне-технической логике. - -То есть: -“Могу помочь с НДС, остатками и движением денег, контрагентами и задолженностями, а также с частью аналитики по периодам. Если хочешь, могу уточнить отдельно по любому из этих блоков.” - -А уже потом: -“По НДС могу показать суммы, динамику по периодам, сверку по организации, сравнительные разрезы...” - ---- - -## 5. Нужен отдельный тип разметки: «вопрос хороший, но ещё не покрыт» - -Это, по сути, мост между автопрогоном и roadmap. - -То есть если в прогоне всплыл вопрос: - -* он адекватный; -* он реально нужен; -* пользователь его точно задаст; -* сейчас он не покрыт, - -то это не просто “ответ плохой”. -Это **кандидат на новый маршрут / на расширение текущего покрытия**. - -Поэтому в ручной разметке должен быть отдельный флаг: - -* `candidate_for_implementation` - или -* `planned_route_gap` - -Именно его потом должен видеть Codex и дальше использовать как список задач на развитие. - ---- - -## 6. Надо разделить две разные проблемы - -Сейчас у тебя в одном описании смешаны две вещи, а их лучше развести. - -### Проблема 1. Функциональное покрытие - -Что система реально умеет по данным и маршрутам. - -### Проблема 2. Поведенческая зрелость - -Как система ведёт себя, когда вопрос: - -* вне покрытия; -* частично в покрытии; -* опасный; -* слишком общий; -* смежный; -* абстрактный. - -То есть даже если маршрут не реализован, поведение всё равно может быть: - -* хорошим; -* плохим; -* опасным; -* слишком техническим; -* бесполезным. - -И автопрогоны должны это различать. - ---- - -# Ниже — готовая мини-ТЗшка для Codex - -## Мини-ТЗ: эталон поведения ассистента, ручная разметка прогонов и канонический файл возможностей - -### Цель - -Доработать систему автопрогонов бухгалтерского ассистента так, чтобы она оценивала не только качество конкретного ответа, но и соответствие эталонному поведению ассистента, а также позволяла вручную размечать судьбу вопроса: покрывается, должен быть отработан, не будет отрабатываться, должен обрабатываться мягким ограничением, требует нового маршрута или требует доработки политики ответа. - ---- - -## 1. Ввести отдельную сущность: Assistant Behavior Canon - -Нужен канонический блок, описывающий эталонную работу ассистента. - -### Требования - -Создать отдельную структуру/файл, который будет использоваться: - -* в логике ответа ассистента; -* в пост-анализе автопрогонов; -* в интерфейсе разметки прогонов; -* в дальнейшей работе Codex по развитию маршрутов и поведения. - -### В Assistant Behavior Canon зафиксировать: - -#### 1.1. Поведение на покрытых кейсах - -* отвечать уверенно и по существу; -* не уходить в лишние оговорки; -* не использовать технические внутренние формулировки. - -#### 1.2. Поведение на частично покрытых кейсах - -* явно разделять, что ассистент может сделать, а что нет; -* не маскировать ограничения; -* предлагать полезное продолжение. - -#### 1.3. Поведение на непокрытых, но близких кейсах - -* не выдумывать поддержку функциональности; -* мягко и по-человечески объяснять ограничение; -* предлагать ближайший поддерживаемый сценарий; -* при уместности подсказывать, где это обычно посмотреть в 1С. - -#### 1.4. Поведение на высокорисковых вопросах - -* не выдавать неподтверждённые рекомендации как надёжный ответ; -* не делать вид, что прикладная логика существует, если её нет; -* сохранять полезность без ложной уверенности. - -#### 1.5. Поведение при вопросе “что ты умеешь” - -* не вываливать весь список возможностей сразу; -* раскрывать возможности по крупным группам; -* углубляться только после уточнения; -* использовать человекочитаемые продуктовые группы, а не внутренние названия маршрутов. - ---- - -## 2. Ввести канонический файл возможностей ассистента - -Нужен отдельный реестр отработанных возможностей ассистента по маршрутам 1С. - -### Назначение - -Этот файл является источником истины для: - -* определения покрытия вопроса; -* ответа ассистента на вопросы о своих возможностях; -* similarity-логики; -* автопрогонов и пост-анализа; -* Codex при дальнейшем развитии маршрутов. - -### Для каждого маршрута / домена хранить: - -* `route_code` -* `group_code` -* `group_title` -* `title` -* `description` -* `supported_operations` -* `unsupported_operations` -* `required_entities` -* `optional_entities` -* `typical_queries` -* `related_routes` -* `safe_alternatives` -* `one_c_hints` -* `risk_level` -* `maturity_status` (`production_ready`, `partial`, `planned`, `deprecated`) - -### Требования к пользовательскому раскрытию возможностей - -Ассистент должен уметь: - -* сначала показывать верхнеуровневые группы; -* по запросу раскрывать детали внутри выбранной группы; -* не использовать длинные технические перечни без необходимости. - ---- - -## 3. Доработать интерфейс автопрогонов: ручная управленческая разметка - -Существующую 5-балльную оценку оставить, но дополнить отдельной выпадающей ручной классификацией результата прогона. - -### Добавить новое поле: - -`manual_case_decision` - -### Возможные значения: - -* `covered_ok` -* `covered_but_bad_answer` -* `candidate_for_implementation` -* `needs_routing_extension` -* `out_of_scope_but_answer_softly` -* `unsafe_question_limit_strictly` -* `needs_dialog_policy_fix` -* `needs_capability_registry_update` -* `bad_test_case` - -### Смысл значений - -* `covered_ok` — кейс уже покрыт, поведение нормальное; -* `covered_but_bad_answer` — кейс покрывается, но ответ/диалог плохой; -* `candidate_for_implementation` — хороший пользовательский кейс, которого пока нет, его стоит брать в разработку; -* `needs_routing_extension` — нужен новый маршрут или расширение существующего; -* `out_of_scope_but_answer_softly` — кейс не планируется покрывать, но нужен качественный мягкий ответ без техничности; -* `unsafe_question_limit_strictly` — кейс относится к рискованным, и ассистент должен ограничивать себя особенно строго; -* `needs_dialog_policy_fix` — проблема не в маршруте, а в стиле/логике ответа; -* `needs_capability_registry_update` — реестр возможностей неактуален или недостаточно формализован; -* `bad_test_case` — вопрос мусорный, нерелевантный или бесполезный для развития системы. - -### Дополнительно - -Для каждой ручной метки предусмотреть: - -* короткий комментарий; -* автора разметки; -* timestamp; -* возможность использовать эту разметку в пост-анализе и отборе задач для Codex. - ---- - -## 4. Доработать логику пост-анализа прогонов - -После прогона система должна уметь отделять: - -* ошибки покрытия; -* ошибки маршрутизации; -* ошибки политики ответа; -* хорошие, но ещё не покрытые кейсы; -* мусорные тест-кейсы; -* высокорисковые кейсы; -* кейсы на обновление файла возможностей. - -### На выходе пост-анализа нужны агрегаты: - -* список кейсов на доработку маршрутов; -* список кейсов на доработку policy; -* список кейсов на обновление capabilities registry; -* список кейсов, которые сознательно не будут покрываться, но требуют мягкого ограничения; -* список кейсов, пригодных для новых regression suites. - ---- - -## 5. Встроить связь между ручной разметкой и дальнейшей работой Codex - -Codex должен видеть не только сам диалог и оценку, но и управленческое решение по нему. - -### Требование - -При выгрузке данных для дальнейшего анализа и доработок обязательно передавать: - -* question / dialog trace; -* current route / current coverage decision; -* 5-балльную оценку; -* `manual_case_decision`; -* комментарий аналитика; -* ссылку на ближайший домен из capabilities registry; -* признак: нужно ли брать кейс в маршрутную отработку. - -### Ожидаемое поведение - -Если кейс помечен как: - -* `candidate_for_implementation` или `needs_routing_extension` — Codex рассматривает его как материал для новой/расширенной маршрутной логики; -* `out_of_scope_but_answer_softly` — Codex улучшает не маршруты, а policy-слой ответа; -* `needs_capability_registry_update` — Codex актуализирует реестр возможностей; -* `unsafe_question_limit_strictly` — Codex усиливает безопасное поведение и ограничения; -* `covered_but_bad_answer` — Codex чинит существующий покрываемый сценарий, а не создаёт новый. - ---- - -## 6. Добавить в эталон обязательное правило минимизации технических ответов - -Это отдельное критичное требование. - -### Ассистент не должен - -* отвечать внутренними техническими терминами; -* ссылаться на отсутствие маршрута, домена, интента, пайплайна, классификатора; -* создавать ощущение поломки системы. - -### Ассистент должен - -* говорить естественно; -* объяснять ограничения человеческим языком; -* сохранять полезность даже при отказе; -* ориентировать пользователя в доступных соседних возможностях. - ---- - -## 7. Добавить в эталон обязательное правило сегментированного раскрытия возможностей - -Если пользователь спрашивает: - -* “что ты умеешь?” -* “что можешь по НДС?” -* “что можешь по остаткам?” -* “что умеешь по деньгам / поставщикам / задолженностям?” - -ассистент должен отвечать через иерархию: - -### Уровень 1 - -Крупные продуктовые группы: - -* НДС -* Контрагенты -* Задолженности -* Деньги и остатки -* Движение и платежи -* Аналитика по периодам -* Справочные бухгалтерские вопросы - -### Уровень 2 - -Уточнение по выбранной группе: - -* что внутри группы реально доступно; -* какие ограничения есть; -* что можно сделать следующим шагом. - -### Уровень 3 - -Точечный ответ по конкретному запросу. - ---- - -## 8. Критерии приёмки - -1. В системе появился отдельный канонический блок эталонного поведения ассистента. -2. Появился отдельный файл/реестр возможностей по маршрутам 1С. -3. В UI автопрогонов добавлена ручная управленческая разметка результата. -4. Ручная разметка сохраняется в логах и участвует в пост-анализе. -5. Система умеет отделять “не покрыто, но стоит реализовать” от “не покрыто и не планируется, но нужно мягко отвечать”. -6. Ассистент перестаёт отвечать внутренним техническим языком на границе покрытия. -7. Ассистент умеет раскрывать свои возможности по группам, а не полным списком. -8. Codex получает достаточно данных, чтобы понимать, что чинить: маршрут, policy, capabilities registry или сам тест-кейс. - ---- - -# Что я бы ещё добавил от себя - -Я бы прямо выделил в ТЗ отдельный артефакт: - -## `assistant_canon.md` - -В нём: - -* идеал поведения; -* анти-паттерны; -* примеры хороших ответов; -* примеры плохих ответов; -* правила раскрытия возможностей; -* правила мягкого ограничения; -* правила поведения на рискованных вопросах. - -И отдельно: - -## `capabilities_registry.json` - -или `capabilities_registry.yaml` - -И ещё: - -## `manual_case_decision_schema.json` - -Чтобы UI и пост-анализ работали по одному словарю значений. - ---- - -# Если совсем коротко, в чём суть - -Тебе сейчас нужен не просто “ещё один контрол в модалке”, а вот такая конструкция: - -**Эталон ассистента** -→ задаёт идеальное поведение - -**Файл возможностей** -→ задаёт фактическое покрытие - -**Ручная разметка кейса** -→ задаёт управленческое решение, что с этим вопросом делать дальше - -**Codex** -→ уже понимает, нужно ли: - -* чинить ответ, -* расширять маршрут, -* обновлять capabilities, -* улучшать мягкий отказ, -* или вообще выкинуть тест-кейс. - -Если хочешь, я следующим сообщением могу собрать это ещё в более прикладной форме: **короткое ТЗ на 30–40 строк для прямой отправки в Codex**, без пояснений и лирики. +# История калибровки: статус на 2026-04-09 + +Этот документ фиксирует текущее состояние системы автопрогонов и ручной разметки в GUI. +Ранее здесь был концептуальный черновик. Теперь это рабочая сводка "что уже внедрено / что осталось". + +## 1. Что уже формализовано + +1. Канон поведения ассистента: + - `docs/TECH/assistant_canon.md` +2. Реестр возможностей ассистента: + - `docs/TECH/capabilities_registry.json` +3. Схема управленческой разметки кейсов: + - `docs/TECH/manual_case_decision_schema.json` + +## 2. Что реализовано в интерфейсе "История автопрогонов" + +1. Генерация вопросов: + - режимы `qwen_seed` и `codex_creative`; + - редактируемая пачка вопросов перед запуском; + - выбор "личности" генерации; + - отдельный prompt для выбранной личности. +2. Асинхронные прогоны: + - запуск через `POST /api/eval/run-async/start`; + - проверка статуса через `GET /api/eval/run-async/:job_id`; + - обновление экрана в live-цикле polling. +3. Разметка ответа ассистента: + - рейтинг `1..5`; + - комментарий; + - `manual_case_decision`; + - автор разметки. +4. Операции по комментарию: + - отметка `resolved` / `unresolved`; + - фильтр "скрыть выполненные"; + - фильтр по `manual_case_decision`. +5. Пост-анализ: + - очереди фиксов из решений разметки; + - агрегаты по доменам и категориям. + +## 3. Файлы данных, которые формируются рантаймом + +1. Разметка ответов: + - `llm_normalizer/data/autorun_annotations/annotations.json` +2. История автогенерации: + - `llm_normalizer/data/autorun_generators/history.json` +3. Сгенерированные кейс-сеты: + - `llm_normalizer/data/eval_cases/*.json` +4. Диалоги кейсов: + - `llm_normalizer/data/assistant_sessions/*.json` + +## 4. Управленческие решения `manual_case_decision` + +Текущий enum: + +1. `covered_ok` +2. `covered_but_bad_answer` +3. `candidate_for_implementation` +4. `needs_routing_extension` +5. `out_of_scope_but_answer_softly` +6. `unsafe_question_limit_strictly` +7. `needs_dialog_policy_fix` +8. `needs_capability_registry_update` +9. `bad_test_case` + +Используются для очередей пост-анализа: + +1. `none` +2. `policy_fix` +3. `routing_extension` +4. `soft_boundary` +5. `safety_policy` +6. `capability_registry` +7. `testset_hygiene` + +## 5. Что осталось в ближайшем цикле + +1. Дожать UX стабильность модалок разметки (без "вечного сохранения" в UI). +2. Довести live-визуал прогона до полностью прозрачного режима вопрос/ответ в реальном времени для длинных серий. +3. Укрепить контроль кодировки UTF-8 на всех точках экспорта/рендера. +4. Добавить регулярный цикл "разметка -> автокандидаты фиксов -> пакетный ремонт маршрутов". + +## 6. Ссылки на подробный документ процесса + +Подробная спецификация по разметке из GUI: + +- `docs/TECH/ui_markup_system.md` diff --git a/docs/TECH/ui_markup_system.md b/docs/TECH/ui_markup_system.md new file mode 100644 index 0000000..5ba4ce5 --- /dev/null +++ b/docs/TECH/ui_markup_system.md @@ -0,0 +1,224 @@ +# Система разметки через GUI (автопрогоны) + +Документ описывает практический контур, который используется оператором в интерфейсе +`История автопрогонов`: генерация вопросов, запуск прогонов, разметка ответов, закрытие кейсов и пост-анализ. + +Дата актуализации: `2026-04-09` + +--- + +## 1. Назначение + +Цель системы: + +1. Прогонять реалистичные пользовательские вопросы сериями. +2. Видеть фактические ответы ассистента в диалоговом формате. +3. Размечать качество ответов и управленческое решение по кейсу. +4. Формировать очередь фикс-пакетов без ручной выгрузки логов в чат. + +--- + +## 2. Где находится источник истины + +1. Канон поведения ассистента: + - `docs/TECH/assistant_canon.md` +2. Реестр возможностей: + - `docs/TECH/capabilities_registry.json` +3. Схема решений ручной разметки: + - `docs/TECH/manual_case_decision_schema.json` + +--- + +## 3. Основной сценарий оператора + +### Шаг 1. Настройка генерации + +Оператор задает: + +1. режим генерации (`qwen_seed` / `codex_creative`); +2. количество вопросов; +3. "личность" автогенерации; +4. prompt выбранной личности; +5. автора генерации; +6. флаг сохранения кейс-сета в `eval_cases`. + +Важно: + +`qwen_seed` использует тот же активный LLM-контур, что и ответы ассистента +(тот же provider/model/baseUrl), но в роли генератора вопросов. + +### Шаг 2. Генерация пачки + +По кнопке "Сгенерировать пачку" создается generation record. + +Запрос: + +- `POST /api/autoruns/autogen/generate` + +История доступна через: + +- `GET /api/autoruns/autogen/history` + +### Шаг 3. Ручная правка вопросов + +Перед запуском оператор редактирует список "Вопросы к запуску": + +1. удаляет нерелевантные; +2. правит формулировки; +3. оставляет итоговую пачку для прогона. + +### Шаг 4. Запуск прогонов + +Запуск идет асинхронно (на текущем этапе для `assistant_stage1`): + +- `POST /api/eval/run-async/start` + +В payload передается итоговый массив `questions[]`. + +### Шаг 5. Live-мониторинг + +Статус job обновляется polling-ом: + +- `GET /api/eval/run-async/:job_id` + +По мере обработки кейсов интерфейс подхватывает: + +1. прогон; +2. кейсы; +3. сообщения вопрос/ответ в диалоге. + +### Шаг 6. Разметка ответа + +Размечается только сообщение `role=assistant`. + +Через модалку задаются: + +1. rating `1..5`; +2. comment; +3. `manual_case_decision`; +4. author. + +Сохранение: + +- `POST /api/autoruns/annotations` + +### Шаг 7. Закрытие кейса + +В комментариях доступен статус `resolved`: + +- отметить выполненным; +- вернуть в открытые. + +Запрос: + +- `PATCH /api/autoruns/annotations/:annotation_id` + +### Шаг 8. Фильтрация и пост-анализ + +Доступно: + +1. фильтр по `manual_case_decision`; +2. скрытие выполненных (`resolved=true`); +3. обновление пост-анализа и очередей фиксов. + +Запросы: + +- `GET /api/autoruns/annotations` +- `GET /api/autoruns/post-analysis` + +--- + +## 4. Решения ручной разметки (`manual_case_decision`) + +Текущее множество: + +1. `covered_ok` +2. `covered_but_bad_answer` +3. `candidate_for_implementation` +4. `needs_routing_extension` +5. `out_of_scope_but_answer_softly` +6. `unsafe_question_limit_strictly` +7. `needs_dialog_policy_fix` +8. `needs_capability_registry_update` +9. `bad_test_case` + +Queue mapping: + +1. `covered_ok` -> `none` +2. `covered_but_bad_answer` -> `policy_fix` +3. `candidate_for_implementation` -> `routing_extension` +4. `needs_routing_extension` -> `routing_extension` +5. `out_of_scope_but_answer_softly` -> `soft_boundary` +6. `unsafe_question_limit_strictly` -> `safety_policy` +7. `needs_dialog_policy_fix` -> `policy_fix` +8. `needs_capability_registry_update` -> `capability_registry` +9. `bad_test_case` -> `testset_hygiene` + +--- + +## 5. Хранилища данных + +1. Аннотации: + - `llm_normalizer/data/autorun_annotations/annotations.json` +2. История генерации: + - `llm_normalizer/data/autorun_generators/history.json` +3. Кейс-сеты генератора: + - `llm_normalizer/data/eval_cases/*.json` +4. Диалоги сессий: + - `llm_normalizer/data/assistant_sessions/*.json` + +--- + +## 6. API-карта раздела + +### История прогонов + +1. `GET /api/autoruns/history` +2. `GET /api/autoruns/history/:run_id` +3. `GET /api/autoruns/history/:run_id/case/:case_id/dialog` + +### Разметка + +1. `GET /api/autoruns/annotations` +2. `POST /api/autoruns/annotations` +3. `PATCH /api/autoruns/annotations/:annotation_id` +4. `GET /api/autoruns/manual-decision-schema` + +### Пост-анализ + +1. `GET /api/autoruns/post-analysis` + +### Автогенерация + +1. `GET /api/autoruns/autogen/personality-catalog` +2. `POST /api/autoruns/autogen/generate` +3. `GET /api/autoruns/autogen/history` + +### Асинхронный запуск + +1. `POST /api/eval/run-async/start` +2. `GET /api/eval/run-async/:job_id` + +--- + +## 7. Тех-проверки после изменений + +Минимальный чек: + +1. Генерация пачки вопросов работает. +2. Async run запускается и отдает live-статус без падения. +3. В диалоге видны пары вопрос/ответ. +4. Аннотация сохраняется и редактируется повторно. +5. `resolved` переключается без рассинхрона в UI. +6. Фильтр "скрыть выполненные" корректно исключает `resolved=true`. +7. Пост-анализ показывает очереди и кандидатов. +8. Текст в интерфейсе читается без mojibake. + +--- + +## 8. Ограничения текущей версии + +1. Async run ограничен `assistant_stage1`. +2. Качество live-данных зависит от заполнения session-файлов на стороне рантайма. +3. Пост-анализ основан на фактической ручной разметке; без нее очереди пустые. + diff --git a/llm_normalizer/README.md b/llm_normalizer/README.md index d3447a3..62ca95e 100644 --- a/llm_normalizer/README.md +++ b/llm_normalizer/README.md @@ -107,3 +107,60 @@ npm.cmd run dev:all cd X:\1C\NDC_1C\llm_normalizer\backend npm test ``` + +## История автопрогонов и разметка + +В интерфейсе есть отдельный режим `История автопрогонов` с операционным циклом: + +1. Настроить генерацию вопросов (режим, количество, личность, prompt личности). +2. Сгенерировать пачку вопросов. +3. Отредактировать вопросы перед запуском. +4. Запустить асинхронный прогон (`assistant_stage1`, `single-pass-strict`). +5. Смотреть диалог прогона в live-режиме (polling статуса + сообщения по кейсам). +6. Разметить ответы ассистента: + - рейтинг `1..5`, + - комментарий, + - `manual_case_decision`, + - автор. +7. Отметить кейс выполненным (`resolved`) или вернуть в открытые. +8. Смотреть пост-анализ и очереди фиксов по категориям. + +### Важный момент по `qwen_seed` + +`qwen_seed` использует тот же активный LLM-контур подключения, что и ответы ассистента +(тот же provider/model/baseUrl), но в другой роли: генератор вопросов. + +### Основные API для автопрогонов + +- `GET /api/autoruns/history` +- `GET /api/autoruns/history/:run_id` +- `GET /api/autoruns/history/:run_id/case/:case_id/dialog` +- `GET /api/autoruns/annotations` +- `POST /api/autoruns/annotations` +- `PATCH /api/autoruns/annotations/:annotation_id` +- `GET /api/autoruns/manual-decision-schema` +- `GET /api/autoruns/post-analysis` +- `GET /api/autoruns/autogen/history` +- `GET /api/autoruns/autogen/personality-catalog` +- `POST /api/autoruns/autogen/generate` +- `POST /api/eval/run-async/start` +- `GET /api/eval/run-async/:job_id` + +### Где лежат данные автопрогонов + +- аннотации и ручная разметка: + - `llm_normalizer/data/autorun_annotations/annotations.json` +- история генераций: + - `llm_normalizer/data/autorun_generators/history.json` +- сгенерированные кейс-сеты (если включено сохранение): + - `llm_normalizer/data/eval_cases/*.json` +- сессии диалогов ассистента по кейсам: + - `llm_normalizer/data/assistant_sessions/*.json` + +### Канонические техдоки + +- `docs/TECH/assistant_canon.md` +- `docs/TECH/capabilities_registry.json` +- `docs/TECH/manual_case_decision_schema.json` +- `docs/TECH/ui_markup_system.md` +- `docs/TECH/history_colibration.md` diff --git a/llm_normalizer/backend/dist/routes/autoRuns.js b/llm_normalizer/backend/dist/routes/autoRuns.js index eab10d1..4927354 100644 --- a/llm_normalizer/backend/dist/routes/autoRuns.js +++ b/llm_normalizer/backend/dist/routes/autoRuns.js @@ -7,9 +7,11 @@ exports.buildAutoRunsRouter = buildAutoRunsRouter; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const express_1 = require("express"); +const iconv_lite_1 = __importDefault(require("iconv-lite")); const config_1 = require("../config"); const http_1 = require("../utils/http"); const capabilitiesRegistry_1 = require("../services/capabilitiesRegistry"); +const openaiResponsesClient_1 = require("../services/openaiResponsesClient"); const MANUAL_CASE_DECISIONS = [ "covered_ok", "covered_but_bad_answer", @@ -102,6 +104,10 @@ function parseAnnotationAuthor(value) { return null; return author.slice(0, 80); } +function parseAnnotationResolved(value, fallback = false) { + const parsed = toBooleanSafe(value); + return parsed === null ? fallback : parsed; +} function readManualDecisionSchema() { const fallback = { schema_version: "manual_case_decision_schema_v1_fallback", @@ -150,6 +156,8 @@ function readAutoGenHistory() { questions: toArray(item.questions) .map((q) => toStringSafe(q)) .filter((q) => q !== null) + .map((q) => sanitizeGeneratedQuestion(q)) + .filter((q) => q.length > 0) .slice(0, 500), generated_by: toStringSafe(item.generated_by), saved_case_set_file: toStringSafe(item.saved_case_set_file), @@ -160,6 +168,12 @@ function readAutoGenHistory() { assistant_prompt_version: toStringSafe(toRecord(item.context)?.assistant_prompt_version), decomposition_prompt_version: toStringSafe(toRecord(item.context)?.decomposition_prompt_version), prompt_fingerprint: toStringSafe(toRecord(item.context)?.prompt_fingerprint) + ? repairAutogenMojibake(String(toRecord(item.context)?.prompt_fingerprint)) + : null, + autogen_personality_id: toStringSafe(toRecord(item.context)?.autogen_personality_id), + autogen_personality_prompt: toStringSafe(toRecord(item.context)?.autogen_personality_prompt) + ? repairAutogenMojibake(String(toRecord(item.context)?.autogen_personality_prompt)) + : null } : null })) @@ -207,11 +221,11 @@ function collectCanonicalQuestions(limit = 300) { for (const testCase of cases) { const rawQuestion = toStringSafe(testCase.raw_question) ?? toStringSafe(testCase.user_message) ?? toStringSafe(testCase.query); if (rawQuestion) { - questions.push(rawQuestion); + questions.push(sanitizeGeneratedQuestion(rawQuestion)); } } } - return Array.from(new Set(questions)).slice(0, limit); + return Array.from(new Set(questions.filter((item) => item.length > 0))).slice(0, limit); } function normalizeDomainHint(value) { const domain = toStringSafe(value); @@ -219,6 +233,49 @@ function normalizeDomainHint(value) { return null; return domain.toLowerCase(); } +function buildAutogenPromptFromCapabilityGroup(group) { + const supported = group.supported_operations.slice(0, 3).join(", "); + const examples = group.typical_queries.slice(0, 2).join(" | "); + const hints = group.one_c_hints.slice(0, 2).join(", "); + const operationsPart = supported ? ` Опирайся на операции: ${supported}.` : ""; + const examplesPart = examples ? ` Ближайшие формулировки: ${examples}.` : ""; + const hintsPart = hints ? ` Можно мягко упоминать контекст 1С: ${hints}.` : ""; + return (`Генерируй реалистичные вопросы бухгалтера по группе "${group.group_title}".` + + ` Добавляй живую разговорную форму и опечатки, но сохраняй бизнес-смысл.${operationsPart}${examplesPart}${hintsPart}` + + " Не выдумывай операции вне read-only режима."); +} +function buildAutogenPersonalityCatalog() { + const builtIn = [ + { + id: "general", + label: "Общий контур", + domain: null, + default_prompt: "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл.", + source: "built_in" + } + ]; + const registry = (0, capabilitiesRegistry_1.loadCapabilitiesRegistry)(); + const registryBased = registry.groups.map((group) => ({ + id: `registry_${group.group_code}`, + label: `${group.group_title} (реестр)`, + domain: group.group_code, + default_prompt: buildAutogenPromptFromCapabilityGroup(group), + source: "capabilities_registry" + })); + const dedup = new Map(); + for (const item of [...builtIn, ...registryBased]) { + if (!item.id.trim()) + continue; + if (!dedup.has(item.id)) { + dedup.set(item.id, item); + } + } + return [...dedup.values()].map((item) => ({ + ...item, + label: repairAutogenMojibake(item.label), + default_prompt: repairAutogenMojibake(item.default_prompt) + })); +} function fallbackDomainTemplates(domain) { if (domain?.includes("vat") || domain?.includes("ндс")) { return [ @@ -276,9 +333,9 @@ function generateQwenSeedQuestions(count, domain) { const out = []; for (let index = 0; index < count; index += 1) { const base = bag[index % bag.length]; - out.push(mutateIntoQwenStyle(base, index)); + out.push(sanitizeGeneratedQuestion(mutateIntoQwenStyle(base, index))); } - return Array.from(new Set(out)).slice(0, count); + return Array.from(new Set(out.filter((item) => item.length > 0))).slice(0, count); } function generateCodexCreativeQuestions(count, domain) { const domainTemplates = fallbackDomainTemplates(domain); @@ -293,9 +350,9 @@ function generateCodexCreativeQuestions(count, domain) { for (let index = 0; index < count; index += 1) { const base = domainTemplates[index % domainTemplates.length]; const pattern = patterns[index % patterns.length]; - out.push(pattern.replace("{q}", base)); + out.push(sanitizeGeneratedQuestion(pattern.replace("{q}", base))); } - return Array.from(new Set(out)).slice(0, count); + return Array.from(new Set(out.filter((item) => item.length > 0))).slice(0, count); } function generateAutogenId() { return `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`; @@ -325,6 +382,9 @@ function readAnnotations() { comment: toStringSafe(item.comment) ?? "", manual_case_decision: parseManualCaseDecision(item.manual_case_decision), annotation_author: parseAnnotationAuthor(item.annotation_author), + resolved: parseAnnotationResolved(item.resolved), + resolved_at: toStringSafe(item.resolved_at), + resolved_by: parseAnnotationAuthor(item.resolved_by), created_at: toStringSafe(item.created_at) ?? new Date().toISOString(), updated_at: toStringSafe(item.updated_at) ?? new Date().toISOString(), context: { @@ -334,7 +394,9 @@ function readAnnotations() { eval_target: toStringSafe(context?.eval_target) ?? "unknown", prompt_version: toStringSafe(context?.prompt_version), domain: toStringSafe(context?.domain), - query_class: toStringSafe(context?.query_class) + query_class: toStringSafe(context?.query_class), + question_text: toStringSafe(context?.question_text), + answer_text: toStringSafe(context?.answer_text) } }; }) @@ -946,6 +1008,37 @@ function withMessageAnnotations(runId, caseId, messages, annotations) { }; }); } +function buildRunAggregateDialog(run, annotations) { + const cases = buildCaseSummaries(run.report, run.run_id, false); + const messages = []; + const decomposition = []; + let globalMessageIndex = 0; + for (const item of cases) { + const caseId = item.case_id; + const caseDialog = loadSessionDialog(run.run_id, caseId) ?? buildFallbackDialog(run, caseId); + const annotatedCaseMessages = withMessageAnnotations(run.run_id, caseId, caseDialog.messages, annotations); + for (const caseMessage of annotatedCaseMessages) { + const localMessageIndex = toNumberSafe(caseMessage.message_index) ?? 0; + messages.push({ + ...caseMessage, + case_id: caseId, + case_message_index: localMessageIndex, + message_index: globalMessageIndex + }); + globalMessageIndex += 1; + } + if (caseDialog.decomposition.length > 0) { + decomposition.push(...caseDialog.decomposition.map((step) => `[${caseId}] ${step}`)); + } + } + return { + source: "run_aggregate", + session_id: `${run.run_id}::__all__`, + messages, + decomposition, + assistant_mode: null + }; +} function generateAnnotationId() { return `ann-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`; } @@ -975,6 +1068,265 @@ function parseAutogenDomain(value) { return null; return domain.slice(0, 80); } +function parseAutogenLlmRuntimeConfig(body, context) { + const llm = toRecord(body.llm); + const providerRaw = toStringSafe(llm?.llm_provider ?? context?.llm_provider)?.toLowerCase() ?? ""; + const model = toStringSafe(llm?.model ?? context?.model); + if (!model || (providerRaw !== "openai" && providerRaw !== "local")) { + return null; + } + return { + llm_provider: providerRaw === "local" ? "local" : "openai", + api_key: toStringSafe(llm?.api_key) ?? "", + model, + base_url: toStringSafe(llm?.base_url), + temperature: toNumberSafe(llm?.temperature), + max_output_tokens: toNumberSafe(llm?.max_output_tokens) + }; +} +function textMojibakeScore(value) { + const source = String(value ?? ""); + const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length; + const latin = (source.match(/[A-Za-z]/g) ?? []).length; + const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/g) ?? []).length; + const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length; + const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length; + return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2; +} +function looksLikeMojibake(value) { + const source = String(value ?? ""); + if (!source.trim()) { + return false; + } + if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/.test(source)) { + return true; + } + if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) { + return true; + } + return (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length >= 2; +} +function repairAutogenMojibake(value) { + const source = String(value ?? ""); + if (!looksLikeMojibake(source)) { + return source; + } + let candidate = source; + for (let pass = 0; pass < 3; pass += 1) { + let improved = false; + try { + const fromWin1251 = iconv_lite_1.default.encode(candidate, "win1251").toString("utf8"); + if (textMojibakeScore(fromWin1251) > textMojibakeScore(candidate)) { + candidate = fromWin1251; + improved = true; + } + } + catch { + // ignore + } + try { + const fromLatin1 = Buffer.from(candidate, "latin1").toString("utf8"); + if (textMojibakeScore(fromLatin1) > textMojibakeScore(candidate)) { + candidate = fromLatin1; + improved = true; + } + } + catch { + // ignore + } + if (!improved) { + break; + } + } + return candidate; +} +function sanitizeGeneratedQuestion(value) { + return repairAutogenMojibake(String(value ?? "")) + .replace(/\r/g, " ") + .replace(/\t/g, " ") + .replace(/\s+/g, " ") + .trim(); +} +function splitQuestionCandidates(rawText) { + const normalized = repairAutogenMojibake(rawText).replace(/\r/g, "\n").trim(); + if (!normalized) + return []; + const unescaped = normalized.replace(/\\"/g, '"').replace(/\\n/g, "\n"); + const byLines = unescaped + .split(/\n+/g) + .map((line) => line.replace(/^\s*(?:[-*•]|\d{1,3}[).:]?)\s*/, "")) + .map((line) => sanitizeGeneratedQuestion(line)) + .filter((line) => line.length > 0); + if (byLines.length > 1) { + return byLines; + } + const questionMarkCount = (unescaped.match(/\?/g) ?? []).length; + if (questionMarkCount > 1) { + const byQuestion = unescaped + .split("?") + .map((chunk) => sanitizeGeneratedQuestion(chunk)) + .filter((chunk) => chunk.length > 0) + .map((chunk) => (chunk.endsWith("?") ? chunk : `${chunk}?`)); + if (byQuestion.length > 1) { + return byQuestion; + } + } + const quoted = Array.from(unescaped.matchAll(/"([^"\n]{6,}?)"/g)) + .map((match) => sanitizeGeneratedQuestion(match[1])) + .filter((line) => line.length > 0); + if (quoted.length > 1) { + return quoted; + } + const cleaned = sanitizeGeneratedQuestion(unescaped); + return cleaned ? [cleaned] : []; +} +function parseAutogenOutputJson(rawText) { + const cleaned = repairAutogenMojibake(rawText) + .trim() + .replace(/^```json\s*/i, "") + .replace(/^```\s*/i, "") + .replace(/```$/i, "") + .trim(); + if (!cleaned) + return null; + try { + return JSON.parse(cleaned); + } + catch { + // continue + } + const arrayStart = cleaned.indexOf("["); + const arrayEnd = cleaned.lastIndexOf("]"); + if (arrayStart >= 0 && arrayEnd > arrayStart) { + const fragment = cleaned.slice(arrayStart, arrayEnd + 1); + try { + return JSON.parse(fragment); + } + catch { + // continue + } + } + const objStart = cleaned.indexOf("{"); + const objEnd = cleaned.lastIndexOf("}"); + if (objStart >= 0 && objEnd > objStart) { + const fragment = cleaned.slice(objStart, objEnd + 1); + try { + return JSON.parse(fragment); + } + catch { + return null; + } + } + return null; +} +function collectQuestionsFromCandidate(value, depth = 0) { + if (depth > 5 || value === null || value === undefined) { + return []; + } + if (Array.isArray(value)) { + return value.flatMap((item) => collectQuestionsFromCandidate(item, depth + 1)); + } + if (typeof value === "string") { + const text = value.trim(); + if (!text) + return []; + const nestedParsed = parseAutogenOutputJson(text); + if (nestedParsed !== null) { + const nestedQuestions = collectQuestionsFromCandidate(nestedParsed, depth + 1); + if (nestedQuestions.length > 0) { + return nestedQuestions; + } + } + try { + const decoded = JSON.parse(text); + if (decoded !== text) { + const decodedQuestions = collectQuestionsFromCandidate(decoded, depth + 1); + if (decodedQuestions.length > 0) { + return decodedQuestions; + } + } + } + catch { + // ignore non-JSON strings + } + return splitQuestionCandidates(text); + } + const record = toRecord(value); + if (!record) { + return []; + } + const fromQuestions = collectQuestionsFromCandidate(record.questions, depth + 1); + if (fromQuestions.length > 0) { + return fromQuestions; + } + const fallbackText = toStringSafe(record.question ?? record.user_message ?? record.text); + return fallbackText ? splitQuestionCandidates(fallbackText) : []; +} +function extractQuestionsFromAutogenOutput(rawText) { + const parsed = parseAutogenOutputJson(rawText); + const fromParsed = collectQuestionsFromCandidate(parsed); + if (fromParsed.length > 0) { + return fromParsed; + } + return collectQuestionsFromCandidate(rawText); +} +async function generateQwenSeedQuestionsLive(input) { + const seedExamples = collectCanonicalQuestions(40); + const fallbackExamples = fallbackDomainTemplates(input.domain); + const examples = (seedExamples.length > 0 ? seedExamples : fallbackExamples).slice(0, 8); + const personalityPrompt = input.personalityPrompt ?? + "Генерируй реалистичные вопросы бухгалтера по 1С. Разговорный стиль допустим, но смысл должен быть четким."; + const repairedPersonalityPrompt = repairAutogenMojibake(personalityPrompt); + const maxOutputTokens = clampInt(input.llmConfig.max_output_tokens, 300, 3000, 1200); + const temperature = input.llmConfig.temperature === null ? 0.5 : Math.max(0, Math.min(1.5, input.llmConfig.temperature)); + const systemPrompt = [ + "Ты генератор вопросов для автопрогонов бухгалтерского ассистента по 1С.", + "Возвращай только JSON и никаких пояснений.", + "Ассистент работает в read-only режиме: не проси действий изменения базы." + ].join(" "); + const repairedSystemPrompt = repairAutogenMojibake(systemPrompt); + const developerPrompt = [ + `Нужно сгенерировать ровно ${input.count} вопросов.`, + "Формат ответа строго:", + '{"questions":["вопрос 1","вопрос 2"]}', + "Требования:", + "1) каждый вопрос отдельный, без дубликатов;", + "2) живой пользовательский язык;", + "3) допустимы легкие разговорные сокращения;", + "4) не выдавай мета-комментарии и не описывай правила." + ].join("\n"); + const repairedDeveloperPrompt = repairAutogenMojibake(developerPrompt); + const userMessage = [ + `Домен: ${input.domain ?? "general"}.`, + `Промпт личности: ${repairedPersonalityPrompt}`, + "Примеры ориентиров по стилю и тематике:", + ...examples.map((item, index) => `${index + 1}. ${item}`) + ].join("\n"); + const repairedUserMessage = repairAutogenMojibake(userMessage); + const response = await input.client.chat({ + llmProvider: input.llmConfig.llm_provider, + apiKey: input.llmConfig.api_key, + model: input.llmConfig.model, + baseUrl: input.llmConfig.base_url ?? undefined, + temperature, + maxOutputTokens: maxOutputTokens + }, { + systemPrompt: repairedSystemPrompt, + developerPrompt: repairedDeveloperPrompt, + userMessage: repairedUserMessage, + temperature, + maxOutputTokens + }); + const extracted = extractQuestionsFromAutogenOutput(response.outputText); + const normalized = Array.from(new Set(extracted.map((item) => sanitizeGeneratedQuestion(item)).filter((item) => item.length > 0))); + if (normalized.length === 0) { + throw new http_1.ApiError("AUTOGEN_LLM_EMPTY_OUTPUT", "Qwen не вернул пригодные вопросы для автогенерации.", 502, { + model: input.llmConfig.model + }); + } + const fallback = generateQwenSeedQuestions(input.count, input.domain); + return Array.from(new Set([...normalized, ...fallback])).slice(0, input.count); +} function hasAnyRunFilterQuery(query) { return Boolean(toStringSafe(query.from) ?? toStringSafe(query.to) ?? @@ -996,7 +1348,8 @@ function buildAutogenCaseSetFileName(mode, generationId) { return `assistant_autogen_${mode}_${stamp}_${generationId}.json`; } function buildAutogenCaseSetPayload(input) { - const cases = input.questions.map((question, index) => ({ + const normalizedQuestions = Array.from(new Set(input.questions.map((item) => sanitizeGeneratedQuestion(item)).filter((item) => item.length > 0))); + const cases = normalizedQuestions.map((question, index) => ({ case_id: `AUTO-${String(index + 1).padStart(3, "0")}`, scenario_tag: `${input.mode}_${input.domain ?? "general"}`, question_type: "direct", @@ -1103,7 +1456,7 @@ function collectPostAnalysis(annotations, runMap, limitPerQueue) { ].slice(0, 60) }; } -function buildAutoRunsRouter() { +function buildAutoRunsRouter(openaiClient = new openaiResponsesClient_1.OpenAIResponsesClient()) { const router = (0, express_1.Router)(); router.get("/api/autoruns/history", (req, res) => { const filters = parseFilters(req.query); @@ -1175,9 +1528,22 @@ function buildAutoRunsRouter() { if (!run) { throw new http_1.ApiError("AUTORUN_NOT_FOUND", `Run not found: ${runId}`, 404); } + const annotations = readAnnotations(); + if (caseId === "__all__") { + const dialog = buildRunAggregateDialog(run, annotations); + (0, http_1.ok)(res, { + ok: true, + run_id: runId, + case_id: "__all__", + ...dialog, + annotations: annotations + .filter((item) => item.run_id === runId) + .sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at)) + }); + return; + } const sessionDialog = loadSessionDialog(runId, caseId); const dialog = sessionDialog ?? buildFallbackDialog(run, caseId); - const annotations = readAnnotations(); const messages = withMessageAnnotations(runId, caseId, dialog.messages, annotations); (0, http_1.ok)(res, { ok: true, @@ -1307,6 +1673,9 @@ function buildAutoRunsRouter() { if (targetRole !== "assistant") { throw new http_1.ApiError("AUTORUN_MESSAGE_NOT_ASSISTANT", "Only assistant answers can be annotated", 400); } + const pairedUserQuestion = [...dialog.messages.slice(0, messageIndex)] + .reverse() + .find((item) => (toStringSafe(item.role) ?? "") === "user"); const nowIso = new Date().toISOString(); const annotations = readAnnotations(); const key = annotationKey(runId, caseId, messageIndex); @@ -1322,6 +1691,9 @@ function buildAutoRunsRouter() { comment, manual_case_decision: manualCaseDecision, annotation_author: annotationAuthor, + resolved: existing?.resolved ?? false, + resolved_at: existing?.resolved_at ?? null, + resolved_by: existing?.resolved_by ?? null, created_at: existing?.created_at ?? nowIso, updated_at: nowIso, context: { @@ -1331,7 +1703,9 @@ function buildAutoRunsRouter() { eval_target: run.eval_target, prompt_version: toStringSafe(run.report.prompt_version), domain: caseSummary.domain, - query_class: caseSummary.query_class + query_class: caseSummary.query_class, + question_text: toStringSafe(pairedUserQuestion?.text), + answer_text: toStringSafe(targetMessage.text) } }; if (existingIndex >= 0) { @@ -1353,6 +1727,49 @@ function buildAutoRunsRouter() { next(error); } }); + router.patch("/api/autoruns/annotations/:annotation_id", (req, res, next) => { + try { + const annotationId = toStringSafe(req.params.annotation_id); + if (!annotationId) { + throw new http_1.ApiError("INVALID_ANNOTATION_ID", "annotation_id is required", 400); + } + const body = toRecord(req.body); + if (!body) { + throw new http_1.ApiError("INVALID_ANNOTATION_PATCH", "JSON body is required", 400); + } + const resolved = toBooleanSafe(body.resolved); + if (resolved === null) { + throw new http_1.ApiError("INVALID_ANNOTATION_PATCH", "resolved flag is required", 400); + } + const resolvedBy = parseAnnotationAuthor(body.resolved_by); + const annotations = readAnnotations(); + const index = annotations.findIndex((item) => item.annotation_id === annotationId); + if (index < 0) { + throw new http_1.ApiError("ANNOTATION_NOT_FOUND", `Annotation not found: ${annotationId}`, 404); + } + const nowIso = new Date().toISOString(); + const current = annotations[index]; + const updated = { + ...current, + resolved, + resolved_at: resolved ? nowIso : null, + resolved_by: resolved ? resolvedBy ?? current.resolved_by ?? null : null, + updated_at: nowIso + }; + annotations[index] = updated; + writeAnnotations(annotations); + const statsByCase = buildAnnotationStatsMap(updated.run_id, annotations); + const caseStats = statsByCase.get(updated.case_id) ?? null; + (0, http_1.ok)(res, { + ok: true, + annotation: updated, + case_annotation_stats: caseStats + }); + } + catch (error) { + next(error); + } + }); router.get("/api/autoruns/manual-decision-schema", (_req, res) => { (0, http_1.ok)(res, { ok: true, @@ -1416,7 +1833,19 @@ function buildAutoRunsRouter() { next(error); } }); - router.post("/api/autoruns/autogen/generate", (req, res, next) => { + router.get("/api/autoruns/autogen/personality-catalog", (_req, res, next) => { + try { + (0, http_1.ok)(res, { + ok: true, + generated_at: new Date().toISOString(), + items: buildAutogenPersonalityCatalog() + }); + } + catch (error) { + next(error); + } + }); + router.post("/api/autoruns/autogen/generate", async (req, res, next) => { try { const body = toRecord(req.body); if (!body) { @@ -1428,9 +1857,25 @@ function buildAutoRunsRouter() { const persistCaseSet = toBooleanSafe(body.persist_to_eval_cases) ?? true; const generatedBy = parseAnnotationAuthor(body.generated_by); const context = toRecord(body.context); - const questions = mode === "qwen_seed" - ? generateQwenSeedQuestions(count, domain) - : generateCodexCreativeQuestions(count, domain); + const llmConfig = parseAutogenLlmRuntimeConfig(body, context); + const personalityPrompt = toStringSafe(context?.autogen_personality_prompt); + let questions = []; + if (mode === "qwen_seed") { + if (!llmConfig) { + throw new http_1.ApiError("AUTOGEN_LLM_CONFIG_REQUIRED", "Для режима qwen_seed нужен активный LLM-контур (provider/model/baseUrl) из настроек подключения.", 400); + } + questions = await generateQwenSeedQuestionsLive({ + count, + domain, + personalityPrompt, + llmConfig, + client: openaiClient + }); + } + else { + questions = generateCodexCreativeQuestions(count, domain); + } + questions = Array.from(new Set(questions.map((item) => sanitizeGeneratedQuestion(item)).filter((item) => item.length > 0))).slice(0, count); const generationId = generateAutogenId(); let savedCaseSetFile = null; if (persistCaseSet) { @@ -1464,6 +1909,12 @@ function buildAutoRunsRouter() { assistant_prompt_version: toStringSafe(context.assistant_prompt_version), decomposition_prompt_version: toStringSafe(context.decomposition_prompt_version), prompt_fingerprint: toStringSafe(context.prompt_fingerprint) + ? repairAutogenMojibake(String(context.prompt_fingerprint)) + : null, + autogen_personality_id: toStringSafe(context.autogen_personality_id), + autogen_personality_prompt: toStringSafe(context.autogen_personality_prompt) + ? repairAutogenMojibake(String(context.autogen_personality_prompt)) + : null } : null }; diff --git a/llm_normalizer/backend/dist/routes/eval.js b/llm_normalizer/backend/dist/routes/eval.js index e60ea2c..b203e89 100644 --- a/llm_normalizer/backend/dist/routes/eval.js +++ b/llm_normalizer/backend/dist/routes/eval.js @@ -1,27 +1,275 @@ "use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildEvalRouter = buildEvalRouter; +const fs_1 = __importDefault(require("fs")); +const path_1 = __importDefault(require("path")); +const nanoid_1 = require("nanoid"); const express_1 = require("express"); +const config_1 = require("../config"); const http_1 = require("../utils/http"); +const ASYNC_JOBS = new Map(); +const MAX_ASYNC_JOBS = 80; +function toRecord(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } + return value; +} +function toStringSafe(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} +function toArray(value) { + return Array.isArray(value) ? value : []; +} +function normalizeQuestionChunk(value) { + return String(value ?? "") + .replace(/\r/g, " ") + .replace(/\t/g, " ") + .replace(/\s+/g, " ") + .trim(); +} +function splitQuestionCandidate(raw) { + const normalized = String(raw ?? "").replace(/\r/g, "\n").trim(); + if (!normalized) { + return []; + } + const byLines = normalized + .split(/\n+/g) + .map((line) => line.replace(/^\s*(?:[-*•]|\d{1,3}[).:]?)\s*/, "").trim()) + .filter((line) => line.length > 0); + const source = byLines.length > 1 ? byLines : [normalized]; + const chunks = []; + for (const line of source) { + const questionLike = Array.from(line.matchAll(/[^?]+(?:\?|$)/g)) + .map((match) => normalizeQuestionChunk(match[0])) + .filter((item) => item.length > 0); + if (questionLike.length > 1) { + for (const item of questionLike) { + chunks.push(item.endsWith("?") ? item : `${item}?`); + } + continue; + } + chunks.push(normalizeQuestionChunk(line)); + } + return chunks.filter((item) => item.length > 0); +} +function normalizeRuntimeQuestions(value) { + const raw = toArray(value) + .map((item) => (typeof item === "string" ? item.trim() : "")) + .filter((item) => item.length > 0); + if (raw.length === 0) { + return []; + } + const expanded = raw.flatMap((item) => splitQuestionCandidate(item)); + const deduped = []; + const seen = new Set(); + for (const item of expanded) { + const normalized = normalizeQuestionChunk(item); + if (!normalized) + continue; + if (seen.has(normalized)) + continue; + seen.add(normalized); + deduped.push(normalized); + } + return deduped; +} +function normalizeCaseIds(value) { + if (!Array.isArray(value)) { + return undefined; + } + const normalized = value + .map((item) => (typeof item === "string" ? item.trim() : "")) + .filter((item) => item.length > 0); + return normalized.length > 0 ? normalized : undefined; +} +function buildEvalPayloadFromBody(body) { + return { + normalizeConfig: (body.normalizeConfig ?? {}), + caseIds: normalizeCaseIds(body.caseIds), + useMock: Boolean(body.useMock), + mode: body.mode ?? "standard", + caseSetFile: typeof body.caseSetFile === "string" ? body.caseSetFile : undefined, + rawQuestions: typeof body.rawQuestions === "string" ? body.rawQuestions : undefined, + evalTarget: body.eval_target ?? "normalizer", + compareWithReportFile: typeof body.compare_with_report_file === "string" + ? body.compare_with_report_file + : typeof body.comparisonBaselineReportFile === "string" + ? body.comparisonBaselineReportFile + : undefined + }; +} +function resolveReadablePath(inputPath) { + if (path_1.default.isAbsolute(inputPath)) { + return inputPath; + } + const candidates = [ + path_1.default.resolve(config_1.EVAL_CASES_DIR, inputPath), + path_1.default.resolve(config_1.EVAL_DATASETS_DIR, inputPath), + path_1.default.resolve(inputPath) + ]; + for (const candidate of candidates) { + if (fs_1.default.existsSync(candidate)) { + return candidate; + } + } + return candidates[0]; +} +function readAssistantSuiteCaseSeeds(inputPath) { + const filePath = resolveReadablePath(inputPath); + const raw = fs_1.default.readFileSync(filePath, "utf-8").replace(/^\uFEFF/, ""); + const parsed = JSON.parse(raw); + const record = toRecord(parsed); + const cases = toArray(record?.cases); + return cases + .map((item) => toRecord(item)) + .filter((item) => item !== null) + .map((item) => { + const caseId = toStringSafe(item.case_id); + const turns = toArray(item.turns); + if (!caseId || turns.length === 0) { + return null; + } + return { + case_id: caseId, + turns_total: turns.length + }; + }) + .filter((item) => item !== null); +} +function writeRuntimeAssistantSuiteFromQuestions(jobId, questions) { + if (!fs_1.default.existsSync(config_1.EVAL_CASES_DIR)) { + fs_1.default.mkdirSync(config_1.EVAL_CASES_DIR, { recursive: true }); + } + const cases = questions.map((question, index) => { + const caseId = `AUTO-${String(index + 1).padStart(3, "0")}`; + return { + case_id: caseId, + scenario_tag: "autogen_runtime", + question_type: "direct", + broadness_level: "medium", + turns: [{ user_message: question }] + }; + }); + const payload = { + suite_id: `assistant_autogen_runtime_${jobId}`, + suite_version: "0.1.0", + schema_version: "assistant_autogen_runtime_v0_1", + scenario_count: cases.length, + case_ids: cases.map((item) => item.case_id), + cases + }; + const fileName = `assistant_autogen_runtime_${jobId}.json`; + fs_1.default.writeFileSync(path_1.default.resolve(config_1.EVAL_CASES_DIR, fileName), JSON.stringify(payload, null, 2), "utf-8"); + return fileName; +} +function readSessionConversation(runId, caseId) { + const sessionId = `${runId}-${caseId}`; + const filePath = path_1.default.resolve(config_1.ASSISTANT_SESSIONS_DIR, `${sessionId}.json`); + if (!fs_1.default.existsSync(filePath)) { + return []; + } + try { + const parsed = JSON.parse(fs_1.default.readFileSync(filePath, "utf-8")); + const record = toRecord(parsed); + const conversation = toArray(record?.conversation) + .map((item) => toRecord(item)) + .filter((item) => item !== null); + return conversation.map((item, index) => ({ + message_id: toStringSafe(item.message_id), + role: toStringSafe(item.role) ?? "unknown", + text: toStringSafe(item.text) ?? "", + created_at: toStringSafe(item.created_at), + trace_id: toStringSafe(item.trace_id), + reply_type: toStringSafe(item.reply_type), + message_index: index, + case_id: caseId, + case_message_index: index + })); + } + catch { + return []; + } +} +function syncJobWithSessions(job) { + if (!job.run_id || !job.eval_target.startsWith("assistant_")) { + return; + } + let completed = 0; + let hasRunning = false; + for (const item of job.cases) { + const messages = readSessionConversation(job.run_id, item.case_id); + item.messages = messages; + const assistantMessages = messages.filter((entry) => entry.role === "assistant").length; + const userMessages = messages.filter((entry) => entry.role === "user").length; + if (assistantMessages >= item.turns_total && item.turns_total > 0) { + item.status = "completed"; + completed += 1; + continue; + } + if (userMessages > 0 || messages.length > 0) { + item.status = "running"; + hasRunning = true; + continue; + } + item.status = "queued"; + } + job.completed_cases = completed; + if (job.status === "running" && !hasRunning && completed === job.total_cases && job.total_cases > 0) { + job.status = "completed"; + } +} +function trimAsyncJobsStore() { + if (ASYNC_JOBS.size <= MAX_ASYNC_JOBS) + return; + const sorted = Array.from(ASYNC_JOBS.values()).sort((a, b) => Date.parse(a.updated_at) - Date.parse(b.updated_at)); + for (const item of sorted) { + if (ASYNC_JOBS.size <= MAX_ASYNC_JOBS) + break; + ASYNC_JOBS.delete(item.job_id); + } +} +function snapshotJob(job) { + return { + job_id: job.job_id, + status: job.status, + created_at: job.created_at, + updated_at: job.updated_at, + eval_target: job.eval_target, + run_id: job.run_id, + case_set_file: job.case_set_file, + total_cases: job.total_cases, + completed_cases: job.completed_cases, + error: job.error, + cases: job.cases, + report_summary: job.report + ? { + run_id: toStringSafe(job.report.run_id), + run_timestamp: toStringSafe(job.report.run_timestamp) ?? toStringSafe(job.report.timestamp), + score_index: typeof job.report.score_index === "number" + ? Number(job.report.score_index) + : toRecord(job.report.metrics) && typeof toRecord(job.report.metrics)?.score_index === "number" + ? Number(toRecord(job.report.metrics)?.score_index) + : null, + cases_total: typeof job.report.cases_total === "number" ? Number(job.report.cases_total) : null + } + : null + }; +} function buildEvalRouter(services) { const router = (0, express_1.Router)(); router.post("/api/eval/run", async (req, res, next) => { try { const body = (req.body ?? {}); - const report = await services.evalService.run({ - normalizeConfig: (body.normalizeConfig ?? {}), - caseIds: Array.isArray(body.caseIds) ? body.caseIds : undefined, - useMock: Boolean(body.useMock), - mode: body.mode ?? "standard", - caseSetFile: typeof body.caseSetFile === "string" ? body.caseSetFile : undefined, - rawQuestions: typeof body.rawQuestions === "string" ? body.rawQuestions : undefined, - evalTarget: body.eval_target ?? "normalizer", - compareWithReportFile: typeof body.compare_with_report_file === "string" - ? body.compare_with_report_file - : typeof body.comparisonBaselineReportFile === "string" - ? body.comparisonBaselineReportFile - : undefined - }); + const payload = buildEvalPayloadFromBody(body); + const report = await services.evalService.run(payload); (0, http_1.ok)(res, { ok: true, report @@ -31,5 +279,106 @@ function buildEvalRouter(services) { next(error); } }); + router.post("/api/eval/run-async/start", async (req, res, next) => { + try { + const body = (req.body ?? {}); + const payload = buildEvalPayloadFromBody(body); + if (payload.evalTarget !== "assistant_stage1") { + throw new http_1.ApiError("UNSUPPORTED_ASYNC_EVAL_TARGET", "Async eval currently supports assistant_stage1 only.", 400); + } + const questions = normalizeRuntimeQuestions(body.questions); + const jobId = `job-${(0, nanoid_1.nanoid)(10)}`; + const runId = `assistant-stage1-${(0, nanoid_1.nanoid)(10)}`; + const runtimeCaseSetFile = questions.length > 0 + ? writeRuntimeAssistantSuiteFromQuestions(jobId, questions) + : payload.caseSetFile + ? payload.caseSetFile + : undefined; + if (!runtimeCaseSetFile) { + throw new http_1.ApiError("ASYNC_CASESET_REQUIRED", "Async assistant_stage1 run requires caseSetFile or explicit questions[] payload.", 400); + } + const caseSeeds = readAssistantSuiteCaseSeeds(runtimeCaseSetFile); + if (caseSeeds.length === 0) { + throw new http_1.ApiError("ASYNC_CASESET_EMPTY", "No runnable cases found in selected case-set.", 400); + } + const nowIso = new Date().toISOString(); + const job = { + job_id: jobId, + status: "queued", + created_at: nowIso, + updated_at: nowIso, + eval_target: payload.evalTarget, + run_id: runId, + case_set_file: runtimeCaseSetFile, + total_cases: caseSeeds.length, + completed_cases: 0, + cases: caseSeeds.map((item) => ({ + case_id: item.case_id, + turns_total: item.turns_total, + status: "queued", + messages: [] + })), + error: null, + report: null + }; + ASYNC_JOBS.set(job.job_id, job); + trimAsyncJobsStore(); + setImmediate(() => { + void (async () => { + const target = ASYNC_JOBS.get(job.job_id); + if (!target) + return; + target.status = "running"; + target.updated_at = new Date().toISOString(); + try { + const report = await services.evalService.run({ + ...payload, + caseSetFile: runtimeCaseSetFile, + runId + }); + target.report = report; + syncJobWithSessions(target); + target.completed_cases = target.total_cases; + target.status = "completed"; + target.updated_at = new Date().toISOString(); + } + catch (error) { + syncJobWithSessions(target); + target.status = "failed"; + target.error = error instanceof Error ? error.message : String(error); + target.updated_at = new Date().toISOString(); + } + })(); + }); + (0, http_1.ok)(res, { + ok: true, + job: snapshotJob(job) + }); + } + catch (error) { + next(error); + } + }); + router.get("/api/eval/run-async/:job_id", (req, res, next) => { + try { + const jobId = String(req.params.job_id ?? "").trim(); + if (!jobId) { + throw new http_1.ApiError("INVALID_ASYNC_JOB_ID", "job_id is required.", 400); + } + const job = ASYNC_JOBS.get(jobId); + if (!job) { + throw new http_1.ApiError("ASYNC_JOB_NOT_FOUND", `Async eval job not found: ${jobId}`, 404); + } + syncJobWithSessions(job); + job.updated_at = new Date().toISOString(); + (0, http_1.ok)(res, { + ok: true, + job: snapshotJob(job) + }); + } + catch (error) { + next(error); + } + }); return router; } diff --git a/llm_normalizer/backend/dist/server.js b/llm_normalizer/backend/dist/server.js index 3fa8348..a58b278 100644 --- a/llm_normalizer/backend/dist/server.js +++ b/llm_normalizer/backend/dist/server.js @@ -61,7 +61,7 @@ function createApp() { app.use((0, normalize_1.buildNormalizeRouter)(services)); app.use((0, eval_1.buildEvalRouter)(services)); app.use((0, assistant_1.buildAssistantRouter)(services)); - app.use((0, autoRuns_1.buildAutoRunsRouter)()); + app.use((0, autoRuns_1.buildAutoRunsRouter)(openaiClient)); app.use((0, history_1.buildHistoryRouter)()); app.use((0, presets_1.buildPresetsRouter)()); app.use((0, accountingAgent_1.buildAccountingAgentRouter)(services)); diff --git a/llm_normalizer/backend/dist/services/assistantService.js b/llm_normalizer/backend/dist/services/assistantService.js index bc862b4..3b855ff 100644 --- a/llm_normalizer/backend/dist/services/assistantService.js +++ b/llm_normalizer/backend/dist/services/assistantService.js @@ -4834,6 +4834,10 @@ class AssistantService { debug: null }; this.sessions.appendItem(sessionId, userItem); + const sessionAfterUserAppend = this.sessions.getSession(sessionId); + if (sessionAfterUserAppend) { + this.sessionLogger.persistSession(sessionAfterUserAppend); + } const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items); const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => { const safeAddressReply = sanitizeOutgoingAssistantText(addressLane.reply_text); diff --git a/llm_normalizer/backend/dist/services/evalService.js b/llm_normalizer/backend/dist/services/evalService.js index e04bbde..1430358 100644 --- a/llm_normalizer/backend/dist/services/evalService.js +++ b/llm_normalizer/backend/dist/services/evalService.js @@ -1552,7 +1552,7 @@ class EvalService { } const suite = parseAssistantSuiteFile(payload.caseSetFile); const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id)); - const runId = `assistant-stage1-${(0, nanoid_1.nanoid)(10)}`; + const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage1-${(0, nanoid_1.nanoid)(10)}`; const assistantService = new assistantService_1.AssistantService(this.normalizerService, new assistantSessionStore_1.AssistantSessionStore()); const diagnostics = []; let requestsTotal = 0; @@ -1568,6 +1568,7 @@ class EvalService { user_message: turn.user_message, message: turn.user_message, mode: "assistant", + llmProvider: payload.normalizeConfig.llmProvider, apiKey: payload.normalizeConfig.apiKey, model: payload.normalizeConfig.model, baseUrl: payload.normalizeConfig.baseUrl, @@ -1885,7 +1886,7 @@ class EvalService { } const suite = parseAssistantStage2SuiteFile(payload.caseSetFile); const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id)); - const runId = `assistant-stage2-${(0, nanoid_1.nanoid)(10)}`; + const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage2-${(0, nanoid_1.nanoid)(10)}`; const assistantService = new assistantService_1.AssistantService(this.normalizerService, new assistantSessionStore_1.AssistantSessionStore()); const diagnostics = []; let requestsTotal = 0; @@ -1903,6 +1904,7 @@ class EvalService { user_message: turn.user_message, message: turn.user_message, mode: "assistant", + llmProvider: payload.normalizeConfig.llmProvider, apiKey: payload.normalizeConfig.apiKey, model: payload.normalizeConfig.model, baseUrl: payload.normalizeConfig.baseUrl, @@ -2177,7 +2179,8 @@ class EvalService { useMock: payload.useMock, mode, caseSetFile: payload.caseSetFile, - compareWithReportFile: payload.compareWithReportFile + compareWithReportFile: payload.compareWithReportFile, + runId: payload.runId }); } if (evalTarget === "assistant_stage2") { @@ -2187,7 +2190,8 @@ class EvalService { useMock: payload.useMock, mode, caseSetFile: payload.caseSetFile, - compareWithReportFile: payload.compareWithReportFile + compareWithReportFile: payload.compareWithReportFile, + runId: payload.runId }); } if (evalTarget === "assistant_p0") { diff --git a/llm_normalizer/backend/src/routes/autoRuns.ts b/llm_normalizer/backend/src/routes/autoRuns.ts index deda1aa..6f615de 100644 --- a/llm_normalizer/backend/src/routes/autoRuns.ts +++ b/llm_normalizer/backend/src/routes/autoRuns.ts @@ -1,6 +1,7 @@ import fs from "fs"; import path from "path"; import { Router } from "express"; +import iconv from "iconv-lite"; import { ASSISTANT_SESSIONS_DIR, AUTORUN_ANNOTATIONS_FILE, @@ -11,7 +12,8 @@ import { REPORTS_DIR } from "../config"; import { ApiError, ok } from "../utils/http"; -import { loadCapabilitiesRegistry, resolveNearestCapabilityGroup } from "../services/capabilitiesRegistry"; +import { loadCapabilitiesRegistry, resolveNearestCapabilityGroup, type CapabilityGroup } from "../services/capabilitiesRegistry"; +import { OpenAIResponsesClient } from "../services/openaiResponsesClient"; type AutoRunTarget = "normalizer" | "assistant_stage1" | "assistant_stage2" | "assistant_p0" | "unknown"; type AutoRunTrend = "up" | "down" | "flat"; @@ -144,6 +146,9 @@ interface AutoRunAnnotationRecord { comment: string; manual_case_decision: ManualCaseDecision; annotation_author: string | null; + resolved: boolean; + resolved_at: string | null; + resolved_by: string | null; created_at: string; updated_at: string; context: { @@ -154,6 +159,8 @@ interface AutoRunAnnotationRecord { prompt_version: string | null; domain: string | null; query_class: string | null; + question_text: string | null; + answer_text: string | null; }; } @@ -178,9 +185,28 @@ interface AutoGenHistoryRecord { assistant_prompt_version: string | null; decomposition_prompt_version: string | null; prompt_fingerprint: string | null; + autogen_personality_id: string | null; + autogen_personality_prompt: string | null; } | null; } +interface AutoGenPersonalityCatalogItem { + id: string; + label: string; + domain: string | null; + default_prompt: string; + source: "built_in" | "capabilities_registry"; +} + +interface AutoGenLlmRuntimeConfig { + llm_provider: "openai" | "local"; + api_key: string; + model: string; + base_url: string | null; + temperature: number | null; + max_output_tokens: number | null; +} + function toRecord(value: unknown): Record | null { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; @@ -254,6 +280,11 @@ function parseAnnotationAuthor(value: unknown): string | null { return author.slice(0, 80); } +function parseAnnotationResolved(value: unknown, fallback = false): boolean { + const parsed = toBooleanSafe(value); + return parsed === null ? fallback : parsed; +} + function readManualDecisionSchema(): Record { const fallback: Record = { schema_version: "manual_case_decision_schema_v1_fallback", @@ -300,6 +331,8 @@ function readAutoGenHistory(): AutoGenHistoryRecord[] { questions: toArray(item.questions) .map((q) => toStringSafe(q)) .filter((q): q is string => q !== null) + .map((q) => sanitizeGeneratedQuestion(q)) + .filter((q) => q.length > 0) .slice(0, 500), generated_by: toStringSafe(item.generated_by), saved_case_set_file: toStringSafe(item.saved_case_set_file), @@ -310,6 +343,12 @@ function readAutoGenHistory(): AutoGenHistoryRecord[] { assistant_prompt_version: toStringSafe(toRecord(item.context)?.assistant_prompt_version), decomposition_prompt_version: toStringSafe(toRecord(item.context)?.decomposition_prompt_version), prompt_fingerprint: toStringSafe(toRecord(item.context)?.prompt_fingerprint) + ? repairAutogenMojibake(String(toRecord(item.context)?.prompt_fingerprint)) + : null, + autogen_personality_id: toStringSafe(toRecord(item.context)?.autogen_personality_id), + autogen_personality_prompt: toStringSafe(toRecord(item.context)?.autogen_personality_prompt) + ? repairAutogenMojibake(String(toRecord(item.context)?.autogen_personality_prompt)) + : null } : null })) @@ -356,11 +395,11 @@ function collectCanonicalQuestions(limit = 300): string[] { for (const testCase of cases) { const rawQuestion = toStringSafe(testCase.raw_question) ?? toStringSafe(testCase.user_message) ?? toStringSafe(testCase.query); if (rawQuestion) { - questions.push(rawQuestion); + questions.push(sanitizeGeneratedQuestion(rawQuestion)); } } } - return Array.from(new Set(questions)).slice(0, limit); + return Array.from(new Set(questions.filter((item) => item.length > 0))).slice(0, limit); } function normalizeDomainHint(value: unknown): string | null { @@ -369,6 +408,55 @@ function normalizeDomainHint(value: unknown): string | null { return domain.toLowerCase(); } +function buildAutogenPromptFromCapabilityGroup(group: CapabilityGroup): string { + const supported = group.supported_operations.slice(0, 3).join(", "); + const examples = group.typical_queries.slice(0, 2).join(" | "); + const hints = group.one_c_hints.slice(0, 2).join(", "); + const operationsPart = supported ? ` Опирайся на операции: ${supported}.` : ""; + const examplesPart = examples ? ` Ближайшие формулировки: ${examples}.` : ""; + const hintsPart = hints ? ` Можно мягко упоминать контекст 1С: ${hints}.` : ""; + return ( + `Генерируй реалистичные вопросы бухгалтера по группе "${group.group_title}".` + + ` Добавляй живую разговорную форму и опечатки, но сохраняй бизнес-смысл.${operationsPart}${examplesPart}${hintsPart}` + + " Не выдумывай операции вне read-only режима." + ); +} + +function buildAutogenPersonalityCatalog(): AutoGenPersonalityCatalogItem[] { + const builtIn: AutoGenPersonalityCatalogItem[] = [ + { + id: "general", + label: "Общий контур", + domain: null, + default_prompt: + "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл.", + source: "built_in" + } + ]; + + const registry = loadCapabilitiesRegistry(); + const registryBased = registry.groups.map((group) => ({ + id: `registry_${group.group_code}`, + label: `${group.group_title} (реестр)`, + domain: group.group_code, + default_prompt: buildAutogenPromptFromCapabilityGroup(group), + source: "capabilities_registry" + })); + + const dedup = new Map(); + for (const item of [...builtIn, ...registryBased]) { + if (!item.id.trim()) continue; + if (!dedup.has(item.id)) { + dedup.set(item.id, item); + } + } + return [...dedup.values()].map((item) => ({ + ...item, + label: repairAutogenMojibake(item.label), + default_prompt: repairAutogenMojibake(item.default_prompt) + })); +} + function fallbackDomainTemplates(domain: string | null): string[] { if (domain?.includes("vat") || domain?.includes("ндс")) { return [ @@ -428,9 +516,9 @@ function generateQwenSeedQuestions(count: number, domain: string | null): string const out: string[] = []; for (let index = 0; index < count; index += 1) { const base = bag[index % bag.length]; - out.push(mutateIntoQwenStyle(base, index)); + out.push(sanitizeGeneratedQuestion(mutateIntoQwenStyle(base, index))); } - return Array.from(new Set(out)).slice(0, count); + return Array.from(new Set(out.filter((item) => item.length > 0))).slice(0, count); } function generateCodexCreativeQuestions(count: number, domain: string | null): string[] { @@ -446,9 +534,9 @@ function generateCodexCreativeQuestions(count: number, domain: string | null): s for (let index = 0; index < count; index += 1) { const base = domainTemplates[index % domainTemplates.length]; const pattern = patterns[index % patterns.length]; - out.push(pattern.replace("{q}", base)); + out.push(sanitizeGeneratedQuestion(pattern.replace("{q}", base))); } - return Array.from(new Set(out)).slice(0, count); + return Array.from(new Set(out.filter((item) => item.length > 0))).slice(0, count); } function generateAutogenId(): string { @@ -480,6 +568,9 @@ function readAnnotations(): AutoRunAnnotationRecord[] { comment: toStringSafe(item.comment) ?? "", manual_case_decision: parseManualCaseDecision(item.manual_case_decision), annotation_author: parseAnnotationAuthor(item.annotation_author), + resolved: parseAnnotationResolved(item.resolved), + resolved_at: toStringSafe(item.resolved_at), + resolved_by: parseAnnotationAuthor(item.resolved_by), created_at: toStringSafe(item.created_at) ?? new Date().toISOString(), updated_at: toStringSafe(item.updated_at) ?? new Date().toISOString(), context: { @@ -489,7 +580,9 @@ function readAnnotations(): AutoRunAnnotationRecord[] { eval_target: (toStringSafe(context?.eval_target) as AutoRunTarget | null) ?? "unknown", prompt_version: toStringSafe(context?.prompt_version), domain: toStringSafe(context?.domain), - query_class: toStringSafe(context?.query_class) + query_class: toStringSafe(context?.query_class), + question_text: toStringSafe(context?.question_text), + answer_text: toStringSafe(context?.answer_text) } } satisfies AutoRunAnnotationRecord; }) @@ -1156,6 +1249,51 @@ function withMessageAnnotations( }); } +function buildRunAggregateDialog( + run: IndexedRun, + annotations: AutoRunAnnotationRecord[] +): { + source: "run_aggregate"; + session_id: string; + messages: Array>; + decomposition: string[]; + assistant_mode: Record | null; +} { + const cases = buildCaseSummaries(run.report, run.run_id, false); + const messages: Array> = []; + const decomposition: string[] = []; + let globalMessageIndex = 0; + + for (const item of cases) { + const caseId = item.case_id; + const caseDialog = loadSessionDialog(run.run_id, caseId) ?? buildFallbackDialog(run, caseId); + const annotatedCaseMessages = withMessageAnnotations(run.run_id, caseId, caseDialog.messages, annotations); + + for (const caseMessage of annotatedCaseMessages) { + const localMessageIndex = toNumberSafe(caseMessage.message_index) ?? 0; + messages.push({ + ...caseMessage, + case_id: caseId, + case_message_index: localMessageIndex, + message_index: globalMessageIndex + }); + globalMessageIndex += 1; + } + + if (caseDialog.decomposition.length > 0) { + decomposition.push(...caseDialog.decomposition.map((step) => `[${caseId}] ${step}`)); + } + } + + return { + source: "run_aggregate", + session_id: `${run.run_id}::__all__`, + messages, + decomposition, + assistant_mode: null + }; +} + function generateAnnotationId(): string { return `ann-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`; } @@ -1189,6 +1327,300 @@ function parseAutogenDomain(value: unknown): string | null { return domain.slice(0, 80); } +function parseAutogenLlmRuntimeConfig( + body: Record, + context: Record | null +): AutoGenLlmRuntimeConfig | null { + const llm = toRecord(body.llm); + const providerRaw = toStringSafe(llm?.llm_provider ?? context?.llm_provider)?.toLowerCase() ?? ""; + const model = toStringSafe(llm?.model ?? context?.model); + if (!model || (providerRaw !== "openai" && providerRaw !== "local")) { + return null; + } + + return { + llm_provider: providerRaw === "local" ? "local" : "openai", + api_key: toStringSafe(llm?.api_key) ?? "", + model, + base_url: toStringSafe(llm?.base_url), + temperature: toNumberSafe(llm?.temperature), + max_output_tokens: toNumberSafe(llm?.max_output_tokens) + }; +} + +function textMojibakeScore(value: string): number { + const source = String(value ?? ""); + const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length; + const latin = (source.match(/[A-Za-z]/g) ?? []).length; + const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/g) ?? []).length; + const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length; + const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length; + return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2; +} + +function looksLikeMojibake(value: string): boolean { + const source = String(value ?? ""); + if (!source.trim()) { + return false; + } + if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ‘’“”•–—™љ›њќћџ]/.test(source)) { + return true; + } + if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) { + return true; + } + return (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length >= 2; +} + +function repairAutogenMojibake(value: string): string { + const source = String(value ?? ""); + if (!looksLikeMojibake(source)) { + return source; + } + let candidate = source; + for (let pass = 0; pass < 3; pass += 1) { + let improved = false; + try { + const fromWin1251 = iconv.encode(candidate, "win1251").toString("utf8"); + if (textMojibakeScore(fromWin1251) > textMojibakeScore(candidate)) { + candidate = fromWin1251; + improved = true; + } + } catch { + // ignore + } + try { + const fromLatin1 = Buffer.from(candidate, "latin1").toString("utf8"); + if (textMojibakeScore(fromLatin1) > textMojibakeScore(candidate)) { + candidate = fromLatin1; + improved = true; + } + } catch { + // ignore + } + if (!improved) { + break; + } + } + return candidate; +} + +function sanitizeGeneratedQuestion(value: string): string { + return repairAutogenMojibake(String(value ?? "")) + .replace(/\r/g, " ") + .replace(/\t/g, " ") + .replace(/\s+/g, " ") + .trim(); +} + +function splitQuestionCandidates(rawText: string): string[] { + const normalized = repairAutogenMojibake(rawText).replace(/\r/g, "\n").trim(); + if (!normalized) return []; + + const unescaped = normalized.replace(/\\"/g, '"').replace(/\\n/g, "\n"); + const byLines = unescaped + .split(/\n+/g) + .map((line) => line.replace(/^\s*(?:[-*•]|\d{1,3}[).:]?)\s*/, "")) + .map((line) => sanitizeGeneratedQuestion(line)) + .filter((line) => line.length > 0); + if (byLines.length > 1) { + return byLines; + } + + const questionMarkCount = (unescaped.match(/\?/g) ?? []).length; + if (questionMarkCount > 1) { + const byQuestion = unescaped + .split("?") + .map((chunk) => sanitizeGeneratedQuestion(chunk)) + .filter((chunk) => chunk.length > 0) + .map((chunk) => (chunk.endsWith("?") ? chunk : `${chunk}?`)); + if (byQuestion.length > 1) { + return byQuestion; + } + } + + const quoted = Array.from(unescaped.matchAll(/"([^"\n]{6,}?)"/g)) + .map((match) => sanitizeGeneratedQuestion(match[1])) + .filter((line) => line.length > 0); + if (quoted.length > 1) { + return quoted; + } + + const cleaned = sanitizeGeneratedQuestion(unescaped); + return cleaned ? [cleaned] : []; +} + +function parseAutogenOutputJson(rawText: string): unknown | null { + const cleaned = repairAutogenMojibake(rawText) + .trim() + .replace(/^```json\s*/i, "") + .replace(/^```\s*/i, "") + .replace(/```$/i, "") + .trim(); + if (!cleaned) return null; + try { + return JSON.parse(cleaned) as unknown; + } catch { + // continue + } + + const arrayStart = cleaned.indexOf("["); + const arrayEnd = cleaned.lastIndexOf("]"); + if (arrayStart >= 0 && arrayEnd > arrayStart) { + const fragment = cleaned.slice(arrayStart, arrayEnd + 1); + try { + return JSON.parse(fragment) as unknown; + } catch { + // continue + } + } + + const objStart = cleaned.indexOf("{"); + const objEnd = cleaned.lastIndexOf("}"); + if (objStart >= 0 && objEnd > objStart) { + const fragment = cleaned.slice(objStart, objEnd + 1); + try { + return JSON.parse(fragment) as unknown; + } catch { + return null; + } + } + return null; +} + +function collectQuestionsFromCandidate(value: unknown, depth = 0): string[] { + if (depth > 5 || value === null || value === undefined) { + return []; + } + + if (Array.isArray(value)) { + return value.flatMap((item) => collectQuestionsFromCandidate(item, depth + 1)); + } + + if (typeof value === "string") { + const text = value.trim(); + if (!text) return []; + + const nestedParsed = parseAutogenOutputJson(text); + if (nestedParsed !== null) { + const nestedQuestions = collectQuestionsFromCandidate(nestedParsed, depth + 1); + if (nestedQuestions.length > 0) { + return nestedQuestions; + } + } + + try { + const decoded = JSON.parse(text) as unknown; + if (decoded !== text) { + const decodedQuestions = collectQuestionsFromCandidate(decoded, depth + 1); + if (decodedQuestions.length > 0) { + return decodedQuestions; + } + } + } catch { + // ignore non-JSON strings + } + + return splitQuestionCandidates(text); + } + + const record = toRecord(value); + if (!record) { + return []; + } + + const fromQuestions = collectQuestionsFromCandidate(record.questions, depth + 1); + if (fromQuestions.length > 0) { + return fromQuestions; + } + + const fallbackText = toStringSafe(record.question ?? record.user_message ?? record.text); + return fallbackText ? splitQuestionCandidates(fallbackText) : []; +} + +function extractQuestionsFromAutogenOutput(rawText: string): string[] { + const parsed = parseAutogenOutputJson(rawText); + const fromParsed = collectQuestionsFromCandidate(parsed); + if (fromParsed.length > 0) { + return fromParsed; + } + return collectQuestionsFromCandidate(rawText); +} + +async function generateQwenSeedQuestionsLive(input: { + count: number; + domain: string | null; + personalityPrompt: string | null; + llmConfig: AutoGenLlmRuntimeConfig; + client: OpenAIResponsesClient; +}): Promise { + const seedExamples = collectCanonicalQuestions(40); + const fallbackExamples = fallbackDomainTemplates(input.domain); + const examples = (seedExamples.length > 0 ? seedExamples : fallbackExamples).slice(0, 8); + const personalityPrompt = + input.personalityPrompt ?? + "Генерируй реалистичные вопросы бухгалтера по 1С. Разговорный стиль допустим, но смысл должен быть четким."; + const repairedPersonalityPrompt = repairAutogenMojibake(personalityPrompt); + const maxOutputTokens = clampInt(input.llmConfig.max_output_tokens, 300, 3000, 1200); + const temperature = input.llmConfig.temperature === null ? 0.5 : Math.max(0, Math.min(1.5, input.llmConfig.temperature)); + + const systemPrompt = [ + "Ты генератор вопросов для автопрогонов бухгалтерского ассистента по 1С.", + "Возвращай только JSON и никаких пояснений.", + "Ассистент работает в read-only режиме: не проси действий изменения базы." + ].join(" "); + const repairedSystemPrompt = repairAutogenMojibake(systemPrompt); + + const developerPrompt = [ + `Нужно сгенерировать ровно ${input.count} вопросов.`, + "Формат ответа строго:", + '{"questions":["вопрос 1","вопрос 2"]}', + "Требования:", + "1) каждый вопрос отдельный, без дубликатов;", + "2) живой пользовательский язык;", + "3) допустимы легкие разговорные сокращения;", + "4) не выдавай мета-комментарии и не описывай правила." + ].join("\n"); + const repairedDeveloperPrompt = repairAutogenMojibake(developerPrompt); + + const userMessage = [ + `Домен: ${input.domain ?? "general"}.`, + `Промпт личности: ${repairedPersonalityPrompt}`, + "Примеры ориентиров по стилю и тематике:", + ...examples.map((item, index) => `${index + 1}. ${item}`) + ].join("\n"); + const repairedUserMessage = repairAutogenMojibake(userMessage); + + const response = await input.client.chat( + { + llmProvider: input.llmConfig.llm_provider, + apiKey: input.llmConfig.api_key, + model: input.llmConfig.model, + baseUrl: input.llmConfig.base_url ?? undefined, + temperature, + maxOutputTokens: maxOutputTokens + }, + { + systemPrompt: repairedSystemPrompt, + developerPrompt: repairedDeveloperPrompt, + userMessage: repairedUserMessage, + temperature, + maxOutputTokens + } + ); + + const extracted = extractQuestionsFromAutogenOutput(response.outputText); + const normalized = Array.from(new Set(extracted.map((item) => sanitizeGeneratedQuestion(item)).filter((item) => item.length > 0))); + if (normalized.length === 0) { + throw new ApiError("AUTOGEN_LLM_EMPTY_OUTPUT", "Qwen не вернул пригодные вопросы для автогенерации.", 502, { + model: input.llmConfig.model + }); + } + + const fallback = generateQwenSeedQuestions(input.count, input.domain); + return Array.from(new Set([...normalized, ...fallback])).slice(0, input.count); +} + function hasAnyRunFilterQuery(query: Record): boolean { return Boolean( toStringSafe(query.from) ?? @@ -1219,7 +1651,10 @@ function buildAutogenCaseSetPayload(input: { domain: string | null; questions: string[]; }): Record { - const cases = input.questions.map((question, index) => ({ + const normalizedQuestions = Array.from( + new Set(input.questions.map((item) => sanitizeGeneratedQuestion(item)).filter((item) => item.length > 0)) + ); + const cases = normalizedQuestions.map((question, index) => ({ case_id: `AUTO-${String(index + 1).padStart(3, "0")}`, scenario_tag: `${input.mode}_${input.domain ?? "general"}`, question_type: "direct", @@ -1341,7 +1776,7 @@ function collectPostAnalysis( }; } -export function buildAutoRunsRouter(): Router { +export function buildAutoRunsRouter(openaiClient = new OpenAIResponsesClient()): Router { const router = Router(); router.get("/api/autoruns/history", (req, res) => { @@ -1423,9 +1858,23 @@ export function buildAutoRunsRouter(): Router { throw new ApiError("AUTORUN_NOT_FOUND", `Run not found: ${runId}`, 404); } + const annotations = readAnnotations(); + if (caseId === "__all__") { + const dialog = buildRunAggregateDialog(run, annotations); + ok(res, { + ok: true, + run_id: runId, + case_id: "__all__", + ...dialog, + annotations: annotations + .filter((item) => item.run_id === runId) + .sort((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at)) + }); + return; + } + const sessionDialog = loadSessionDialog(runId, caseId); const dialog = sessionDialog ?? buildFallbackDialog(run, caseId); - const annotations = readAnnotations(); const messages = withMessageAnnotations(runId, caseId, dialog.messages, annotations); ok(res, { ok: true, @@ -1563,6 +2012,9 @@ export function buildAutoRunsRouter(): Router { if (targetRole !== "assistant") { throw new ApiError("AUTORUN_MESSAGE_NOT_ASSISTANT", "Only assistant answers can be annotated", 400); } + const pairedUserQuestion = [...dialog.messages.slice(0, messageIndex)] + .reverse() + .find((item) => (toStringSafe(item.role) ?? "") === "user"); const nowIso = new Date().toISOString(); const annotations = readAnnotations(); @@ -1580,6 +2032,9 @@ export function buildAutoRunsRouter(): Router { comment, manual_case_decision: manualCaseDecision, annotation_author: annotationAuthor, + resolved: existing?.resolved ?? false, + resolved_at: existing?.resolved_at ?? null, + resolved_by: existing?.resolved_by ?? null, created_at: existing?.created_at ?? nowIso, updated_at: nowIso, context: { @@ -1589,7 +2044,9 @@ export function buildAutoRunsRouter(): Router { eval_target: run.eval_target, prompt_version: toStringSafe(run.report.prompt_version), domain: caseSummary.domain, - query_class: caseSummary.query_class + query_class: caseSummary.query_class, + question_text: toStringSafe(pairedUserQuestion?.text), + answer_text: toStringSafe(targetMessage.text) } }; @@ -1613,6 +2070,56 @@ export function buildAutoRunsRouter(): Router { } }); + router.patch("/api/autoruns/annotations/:annotation_id", (req, res, next) => { + try { + const annotationId = toStringSafe(req.params.annotation_id); + if (!annotationId) { + throw new ApiError("INVALID_ANNOTATION_ID", "annotation_id is required", 400); + } + + const body = toRecord(req.body); + if (!body) { + throw new ApiError("INVALID_ANNOTATION_PATCH", "JSON body is required", 400); + } + + const resolved = toBooleanSafe(body.resolved); + if (resolved === null) { + throw new ApiError("INVALID_ANNOTATION_PATCH", "resolved flag is required", 400); + } + const resolvedBy = parseAnnotationAuthor(body.resolved_by); + + const annotations = readAnnotations(); + const index = annotations.findIndex((item) => item.annotation_id === annotationId); + if (index < 0) { + throw new ApiError("ANNOTATION_NOT_FOUND", `Annotation not found: ${annotationId}`, 404); + } + + const nowIso = new Date().toISOString(); + const current = annotations[index]; + const updated: AutoRunAnnotationRecord = { + ...current, + resolved, + resolved_at: resolved ? nowIso : null, + resolved_by: resolved ? resolvedBy ?? current.resolved_by ?? null : null, + updated_at: nowIso + }; + + annotations[index] = updated; + writeAnnotations(annotations); + + const statsByCase = buildAnnotationStatsMap(updated.run_id, annotations); + const caseStats = statsByCase.get(updated.case_id) ?? null; + + ok(res, { + ok: true, + annotation: updated, + case_annotation_stats: caseStats + }); + } catch (error) { + next(error); + } + }); + router.get("/api/autoruns/manual-decision-schema", (_req, res) => { ok(res, { ok: true, @@ -1682,7 +2189,19 @@ export function buildAutoRunsRouter(): Router { } }); - router.post("/api/autoruns/autogen/generate", (req, res, next) => { + router.get("/api/autoruns/autogen/personality-catalog", (_req, res, next) => { + try { + ok(res, { + ok: true, + generated_at: new Date().toISOString(), + items: buildAutogenPersonalityCatalog() + }); + } catch (error) { + next(error); + } + }); + + router.post("/api/autoruns/autogen/generate", async (req, res, next) => { try { const body = toRecord(req.body); if (!body) { @@ -1694,11 +2213,32 @@ export function buildAutoRunsRouter(): Router { const persistCaseSet = toBooleanSafe(body.persist_to_eval_cases) ?? true; const generatedBy = parseAnnotationAuthor(body.generated_by); const context = toRecord(body.context); + const llmConfig = parseAutogenLlmRuntimeConfig(body, context); + const personalityPrompt = toStringSafe(context?.autogen_personality_prompt); - const questions = - mode === "qwen_seed" - ? generateQwenSeedQuestions(count, domain) - : generateCodexCreativeQuestions(count, domain); + let questions: string[] = []; + if (mode === "qwen_seed") { + if (!llmConfig) { + throw new ApiError( + "AUTOGEN_LLM_CONFIG_REQUIRED", + "Для режима qwen_seed нужен активный LLM-контур (provider/model/baseUrl) из настроек подключения.", + 400 + ); + } + questions = await generateQwenSeedQuestionsLive({ + count, + domain, + personalityPrompt, + llmConfig, + client: openaiClient + }); + } else { + questions = generateCodexCreativeQuestions(count, domain); + } + questions = Array.from(new Set(questions.map((item) => sanitizeGeneratedQuestion(item)).filter((item) => item.length > 0))).slice( + 0, + count + ); const generationId = generateAutogenId(); let savedCaseSetFile: string | null = null; @@ -1734,6 +2274,12 @@ export function buildAutoRunsRouter(): Router { assistant_prompt_version: toStringSafe(context.assistant_prompt_version), decomposition_prompt_version: toStringSafe(context.decomposition_prompt_version), prompt_fingerprint: toStringSafe(context.prompt_fingerprint) + ? repairAutogenMojibake(String(context.prompt_fingerprint)) + : null, + autogen_personality_id: toStringSafe(context.autogen_personality_id), + autogen_personality_prompt: toStringSafe(context.autogen_personality_prompt) + ? repairAutogenMojibake(String(context.autogen_personality_prompt)) + : null } : null }; @@ -1752,3 +2298,5 @@ export function buildAutoRunsRouter(): Router { return router; } + + diff --git a/llm_normalizer/backend/src/routes/eval.ts b/llm_normalizer/backend/src/routes/eval.ts index 2599378..2a57be0 100644 --- a/llm_normalizer/backend/src/routes/eval.ts +++ b/llm_normalizer/backend/src/routes/eval.ts @@ -1,30 +1,333 @@ +import fs from "fs"; +import path from "path"; +import { nanoid } from "nanoid"; import { Router } from "express"; +import { ASSISTANT_SESSIONS_DIR, EVAL_CASES_DIR, EVAL_DATASETS_DIR } from "../config"; import type { AppServices } from "../serverContext"; -import { ok } from "../utils/http"; +import { ApiError, ok } from "../utils/http"; import type { EvalRunMode, NormalizeRequestPayload } from "../types/normalizer"; import type { EvalTarget } from "../types/assistantEval"; +type EvalAsyncStatus = "queued" | "running" | "completed" | "failed"; + +interface EvalAsyncCaseInfo { + case_id: string; + turns_total: number; + status: EvalAsyncStatus; + messages: Array<{ + message_id: string | null; + role: string; + text: string; + created_at: string | null; + trace_id: string | null; + reply_type: string | null; + message_index: number; + case_id: string; + case_message_index: number; + }>; +} + +interface EvalAsyncJob { + job_id: string; + status: EvalAsyncStatus; + created_at: string; + updated_at: string; + eval_target: EvalTarget; + run_id: string; + case_set_file: string | null; + total_cases: number; + completed_cases: number; + cases: EvalAsyncCaseInfo[]; + error: string | null; + report: Record | null; +} + +const ASYNC_JOBS = new Map(); +const MAX_ASYNC_JOBS = 80; + +function toRecord(value: unknown): Record | null { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } + return value as Record; +} + +function toStringSafe(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +function toArray(value: unknown): unknown[] { + return Array.isArray(value) ? value : []; +} + +function normalizeQuestionChunk(value: string): string { + return String(value ?? "") + .replace(/\r/g, " ") + .replace(/\t/g, " ") + .replace(/\s+/g, " ") + .trim(); +} + +function splitQuestionCandidate(raw: string): string[] { + const normalized = String(raw ?? "").replace(/\r/g, "\n").trim(); + if (!normalized) { + return []; + } + + const byLines = normalized + .split(/\n+/g) + .map((line) => line.replace(/^\s*(?:[-*•]|\d{1,3}[).:]?)\s*/, "").trim()) + .filter((line) => line.length > 0); + const source = byLines.length > 1 ? byLines : [normalized]; + + const chunks: string[] = []; + for (const line of source) { + const questionLike = Array.from(line.matchAll(/[^?]+(?:\?|$)/g)) + .map((match) => normalizeQuestionChunk(match[0])) + .filter((item) => item.length > 0); + if (questionLike.length > 1) { + for (const item of questionLike) { + chunks.push(item.endsWith("?") ? item : `${item}?`); + } + continue; + } + chunks.push(normalizeQuestionChunk(line)); + } + return chunks.filter((item) => item.length > 0); +} + +function normalizeRuntimeQuestions(value: unknown): string[] { + const raw = toArray(value) + .map((item) => (typeof item === "string" ? item.trim() : "")) + .filter((item) => item.length > 0); + if (raw.length === 0) { + return []; + } + + const expanded = raw.flatMap((item) => splitQuestionCandidate(item)); + const deduped: string[] = []; + const seen = new Set(); + for (const item of expanded) { + const normalized = normalizeQuestionChunk(item); + if (!normalized) continue; + if (seen.has(normalized)) continue; + seen.add(normalized); + deduped.push(normalized); + } + return deduped; +} + +function normalizeCaseIds(value: unknown): string[] | undefined { + if (!Array.isArray(value)) { + return undefined; + } + const normalized = value + .map((item) => (typeof item === "string" ? item.trim() : "")) + .filter((item) => item.length > 0); + return normalized.length > 0 ? normalized : undefined; +} + +function buildEvalPayloadFromBody(body: Record): { + normalizeConfig: Omit; + caseIds?: string[]; + useMock: boolean; + mode: EvalRunMode; + caseSetFile?: string; + rawQuestions?: string; + evalTarget: EvalTarget; + compareWithReportFile?: string; +} { + return { + normalizeConfig: (body.normalizeConfig ?? {}) as Omit, + caseIds: normalizeCaseIds(body.caseIds), + useMock: Boolean(body.useMock), + mode: (body.mode as EvalRunMode | undefined) ?? "standard", + caseSetFile: typeof body.caseSetFile === "string" ? body.caseSetFile : undefined, + rawQuestions: typeof body.rawQuestions === "string" ? body.rawQuestions : undefined, + evalTarget: (body.eval_target as EvalTarget | undefined) ?? "normalizer", + compareWithReportFile: + typeof body.compare_with_report_file === "string" + ? body.compare_with_report_file + : typeof body.comparisonBaselineReportFile === "string" + ? body.comparisonBaselineReportFile + : undefined + }; +} + +function resolveReadablePath(inputPath: string): string { + if (path.isAbsolute(inputPath)) { + return inputPath; + } + const candidates = [ + path.resolve(EVAL_CASES_DIR, inputPath), + path.resolve(EVAL_DATASETS_DIR, inputPath), + path.resolve(inputPath) + ]; + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + return candidates[0]; +} + +function readAssistantSuiteCaseSeeds(inputPath: string): Array<{ case_id: string; turns_total: number }> { + const filePath = resolveReadablePath(inputPath); + const raw = fs.readFileSync(filePath, "utf-8").replace(/^\uFEFF/, ""); + const parsed = JSON.parse(raw) as unknown; + const record = toRecord(parsed); + const cases = toArray(record?.cases); + return cases + .map((item) => toRecord(item)) + .filter((item): item is Record => item !== null) + .map((item) => { + const caseId = toStringSafe(item.case_id); + const turns = toArray(item.turns); + if (!caseId || turns.length === 0) { + return null; + } + return { + case_id: caseId, + turns_total: turns.length + }; + }) + .filter((item): item is { case_id: string; turns_total: number } => item !== null); +} + +function writeRuntimeAssistantSuiteFromQuestions(jobId: string, questions: string[]): string { + if (!fs.existsSync(EVAL_CASES_DIR)) { + fs.mkdirSync(EVAL_CASES_DIR, { recursive: true }); + } + const cases = questions.map((question, index) => { + const caseId = `AUTO-${String(index + 1).padStart(3, "0")}`; + return { + case_id: caseId, + scenario_tag: "autogen_runtime", + question_type: "direct", + broadness_level: "medium", + turns: [{ user_message: question }] + }; + }); + const payload = { + suite_id: `assistant_autogen_runtime_${jobId}`, + suite_version: "0.1.0", + schema_version: "assistant_autogen_runtime_v0_1", + scenario_count: cases.length, + case_ids: cases.map((item) => item.case_id), + cases + }; + const fileName = `assistant_autogen_runtime_${jobId}.json`; + fs.writeFileSync(path.resolve(EVAL_CASES_DIR, fileName), JSON.stringify(payload, null, 2), "utf-8"); + return fileName; +} + +function readSessionConversation(runId: string, caseId: string): EvalAsyncCaseInfo["messages"] { + const sessionId = `${runId}-${caseId}`; + const filePath = path.resolve(ASSISTANT_SESSIONS_DIR, `${sessionId}.json`); + if (!fs.existsSync(filePath)) { + return []; + } + try { + const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown; + const record = toRecord(parsed); + const conversation = toArray(record?.conversation) + .map((item) => toRecord(item)) + .filter((item): item is Record => item !== null); + return conversation.map((item, index) => ({ + message_id: toStringSafe(item.message_id), + role: toStringSafe(item.role) ?? "unknown", + text: toStringSafe(item.text) ?? "", + created_at: toStringSafe(item.created_at), + trace_id: toStringSafe(item.trace_id), + reply_type: toStringSafe(item.reply_type), + message_index: index, + case_id: caseId, + case_message_index: index + })); + } catch { + return []; + } +} + +function syncJobWithSessions(job: EvalAsyncJob): void { + if (!job.run_id || !job.eval_target.startsWith("assistant_")) { + return; + } + let completed = 0; + let hasRunning = false; + for (const item of job.cases) { + const messages = readSessionConversation(job.run_id, item.case_id); + item.messages = messages; + const assistantMessages = messages.filter((entry) => entry.role === "assistant").length; + const userMessages = messages.filter((entry) => entry.role === "user").length; + if (assistantMessages >= item.turns_total && item.turns_total > 0) { + item.status = "completed"; + completed += 1; + continue; + } + if (userMessages > 0 || messages.length > 0) { + item.status = "running"; + hasRunning = true; + continue; + } + item.status = "queued"; + } + job.completed_cases = completed; + if (job.status === "running" && !hasRunning && completed === job.total_cases && job.total_cases > 0) { + job.status = "completed"; + } +} + +function trimAsyncJobsStore(): void { + if (ASYNC_JOBS.size <= MAX_ASYNC_JOBS) return; + const sorted = Array.from(ASYNC_JOBS.values()).sort((a, b) => Date.parse(a.updated_at) - Date.parse(b.updated_at)); + for (const item of sorted) { + if (ASYNC_JOBS.size <= MAX_ASYNC_JOBS) break; + ASYNC_JOBS.delete(item.job_id); + } +} + +function snapshotJob(job: EvalAsyncJob): Record { + return { + job_id: job.job_id, + status: job.status, + created_at: job.created_at, + updated_at: job.updated_at, + eval_target: job.eval_target, + run_id: job.run_id, + case_set_file: job.case_set_file, + total_cases: job.total_cases, + completed_cases: job.completed_cases, + error: job.error, + cases: job.cases, + report_summary: job.report + ? { + run_id: toStringSafe(job.report.run_id), + run_timestamp: toStringSafe(job.report.run_timestamp) ?? toStringSafe(job.report.timestamp), + score_index: + typeof job.report.score_index === "number" + ? Number(job.report.score_index) + : toRecord(job.report.metrics) && typeof toRecord(job.report.metrics)?.score_index === "number" + ? Number(toRecord(job.report.metrics)?.score_index) + : null, + cases_total: typeof job.report.cases_total === "number" ? Number(job.report.cases_total) : null + } + : null + }; +} + export function buildEvalRouter(services: AppServices): Router { const router = Router(); router.post("/api/eval/run", async (req, res, next) => { try { const body = (req.body ?? {}) as Record; - const report = await services.evalService.run({ - normalizeConfig: (body.normalizeConfig ?? {}) as Omit, - caseIds: Array.isArray(body.caseIds) ? (body.caseIds as string[]) : undefined, - useMock: Boolean(body.useMock), - mode: (body.mode as EvalRunMode | undefined) ?? "standard", - caseSetFile: typeof body.caseSetFile === "string" ? body.caseSetFile : undefined, - rawQuestions: typeof body.rawQuestions === "string" ? body.rawQuestions : undefined, - evalTarget: (body.eval_target as EvalTarget | undefined) ?? "normalizer", - compareWithReportFile: - typeof body.compare_with_report_file === "string" - ? body.compare_with_report_file - : typeof body.comparisonBaselineReportFile === "string" - ? body.comparisonBaselineReportFile - : undefined - }); + const payload = buildEvalPayloadFromBody(body); + const report = await services.evalService.run(payload); ok(res, { ok: true, report @@ -34,5 +337,115 @@ export function buildEvalRouter(services: AppServices): Router { } }); + router.post("/api/eval/run-async/start", async (req, res, next) => { + try { + const body = (req.body ?? {}) as Record; + const payload = buildEvalPayloadFromBody(body); + if (payload.evalTarget !== "assistant_stage1") { + throw new ApiError("UNSUPPORTED_ASYNC_EVAL_TARGET", "Async eval currently supports assistant_stage1 only.", 400); + } + const questions = normalizeRuntimeQuestions(body.questions); + + const jobId = `job-${nanoid(10)}`; + const runId = `assistant-stage1-${nanoid(10)}`; + const runtimeCaseSetFile = + questions.length > 0 + ? writeRuntimeAssistantSuiteFromQuestions(jobId, questions) + : payload.caseSetFile + ? payload.caseSetFile + : undefined; + + if (!runtimeCaseSetFile) { + throw new ApiError( + "ASYNC_CASESET_REQUIRED", + "Async assistant_stage1 run requires caseSetFile or explicit questions[] payload.", + 400 + ); + } + + const caseSeeds = readAssistantSuiteCaseSeeds(runtimeCaseSetFile); + if (caseSeeds.length === 0) { + throw new ApiError("ASYNC_CASESET_EMPTY", "No runnable cases found in selected case-set.", 400); + } + + const nowIso = new Date().toISOString(); + const job: EvalAsyncJob = { + job_id: jobId, + status: "queued", + created_at: nowIso, + updated_at: nowIso, + eval_target: payload.evalTarget, + run_id: runId, + case_set_file: runtimeCaseSetFile, + total_cases: caseSeeds.length, + completed_cases: 0, + cases: caseSeeds.map((item) => ({ + case_id: item.case_id, + turns_total: item.turns_total, + status: "queued", + messages: [] + })), + error: null, + report: null + }; + ASYNC_JOBS.set(job.job_id, job); + trimAsyncJobsStore(); + + setImmediate(() => { + void (async () => { + const target = ASYNC_JOBS.get(job.job_id); + if (!target) return; + target.status = "running"; + target.updated_at = new Date().toISOString(); + try { + const report = await services.evalService.run({ + ...payload, + caseSetFile: runtimeCaseSetFile, + runId + }); + target.report = report; + syncJobWithSessions(target); + target.completed_cases = target.total_cases; + target.status = "completed"; + target.updated_at = new Date().toISOString(); + } catch (error) { + syncJobWithSessions(target); + target.status = "failed"; + target.error = error instanceof Error ? error.message : String(error); + target.updated_at = new Date().toISOString(); + } + })(); + }); + + ok(res, { + ok: true, + job: snapshotJob(job) + }); + } catch (error) { + next(error); + } + }); + + router.get("/api/eval/run-async/:job_id", (req, res, next) => { + try { + const jobId = String(req.params.job_id ?? "").trim(); + if (!jobId) { + throw new ApiError("INVALID_ASYNC_JOB_ID", "job_id is required.", 400); + } + const job = ASYNC_JOBS.get(jobId); + if (!job) { + throw new ApiError("ASYNC_JOB_NOT_FOUND", `Async eval job not found: ${jobId}`, 404); + } + syncJobWithSessions(job); + job.updated_at = new Date().toISOString(); + ok(res, { + ok: true, + job: snapshotJob(job) + }); + } catch (error) { + next(error); + } + }); + return router; } diff --git a/llm_normalizer/backend/src/server.ts b/llm_normalizer/backend/src/server.ts index 50bab81..42786e6 100644 --- a/llm_normalizer/backend/src/server.ts +++ b/llm_normalizer/backend/src/server.ts @@ -72,7 +72,7 @@ export function createApp(): express.Express { app.use(buildNormalizeRouter(services)); app.use(buildEvalRouter(services)); app.use(buildAssistantRouter(services)); - app.use(buildAutoRunsRouter()); + app.use(buildAutoRunsRouter(openaiClient)); app.use(buildHistoryRouter()); app.use(buildPresetsRouter()); app.use(buildAccountingAgentRouter(services)); diff --git a/llm_normalizer/backend/src/services/assistantService.ts b/llm_normalizer/backend/src/services/assistantService.ts index df5b1d6..3d67eab 100644 --- a/llm_normalizer/backend/src/services/assistantService.ts +++ b/llm_normalizer/backend/src/services/assistantService.ts @@ -1,4 +1,4 @@ -// @ts-nocheck +// @ts-nocheck import * as nanoid_1 from "nanoid"; import * as stage1Contracts_1 from "../types/stage1Contracts"; import * as config_1 from "../config"; @@ -4789,6 +4789,10 @@ export class AssistantService { debug: null }; this.sessions.appendItem(sessionId, userItem); + const sessionAfterUserAppend = this.sessions.getSession(sessionId); + if (sessionAfterUserAppend) { + this.sessionLogger.persistSession(sessionAfterUserAppend); + } const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items); const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => { const safeAddressReply = sanitizeOutgoingAssistantText(addressLane.reply_text); diff --git a/llm_normalizer/backend/src/services/evalService.ts b/llm_normalizer/backend/src/services/evalService.ts index 4c91ea5..9599cd6 100644 --- a/llm_normalizer/backend/src/services/evalService.ts +++ b/llm_normalizer/backend/src/services/evalService.ts @@ -1876,6 +1876,7 @@ export class EvalService { mode: EvalRunMode; caseSetFile?: string; compareWithReportFile?: string; + runId?: string; }): Promise> { if (!FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1) { throw new ApiError( @@ -1887,7 +1888,7 @@ export class EvalService { const suite = parseAssistantSuiteFile(payload.caseSetFile); const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id)); - const runId = `assistant-stage1-${nanoid(10)}`; + const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage1-${nanoid(10)}`; const assistantService = new AssistantService(this.normalizerService, new AssistantSessionStore()); const diagnostics: AssistantCaseDiagnostics[] = []; let requestsTotal = 0; @@ -1905,6 +1906,7 @@ export class EvalService { user_message: turn.user_message, message: turn.user_message, mode: "assistant", + llmProvider: payload.normalizeConfig.llmProvider, apiKey: payload.normalizeConfig.apiKey, model: payload.normalizeConfig.model, baseUrl: payload.normalizeConfig.baseUrl, @@ -2223,6 +2225,7 @@ export class EvalService { mode: EvalRunMode; caseSetFile?: string; compareWithReportFile?: string; + runId?: string; }): Promise> { if (!FEATURE_ASSISTANT_STAGE2_EVAL_V1) { throw new ApiError( @@ -2234,7 +2237,7 @@ export class EvalService { const suite = parseAssistantStage2SuiteFile(payload.caseSetFile); const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id)); - const runId = `assistant-stage2-${nanoid(10)}`; + const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage2-${nanoid(10)}`; const assistantService = new AssistantService(this.normalizerService, new AssistantSessionStore()); const diagnostics: AssistantStage2CaseDiagnostics[] = []; let requestsTotal = 0; @@ -2255,6 +2258,7 @@ export class EvalService { user_message: turn.user_message, message: turn.user_message, mode: "assistant", + llmProvider: payload.normalizeConfig.llmProvider, apiKey: payload.normalizeConfig.apiKey, model: payload.normalizeConfig.model, baseUrl: payload.normalizeConfig.baseUrl, @@ -2548,6 +2552,7 @@ export class EvalService { rawQuestions?: string; evalTarget?: EvalTarget; compareWithReportFile?: string; + runId?: string; }): Promise> { const mode = payload.mode ?? "standard"; const evalTarget = payload.evalTarget ?? "normalizer"; @@ -2559,7 +2564,8 @@ export class EvalService { useMock: payload.useMock, mode, caseSetFile: payload.caseSetFile, - compareWithReportFile: payload.compareWithReportFile + compareWithReportFile: payload.compareWithReportFile, + runId: payload.runId }); } @@ -2570,7 +2576,8 @@ export class EvalService { useMock: payload.useMock, mode, caseSetFile: payload.caseSetFile, - compareWithReportFile: payload.compareWithReportFile + compareWithReportFile: payload.compareWithReportFile, + runId: payload.runId }); } diff --git a/llm_normalizer/data/autorun_annotations/annotations.json b/llm_normalizer/data/autorun_annotations/annotations.json new file mode 100644 index 0000000..dd8b61f --- /dev/null +++ b/llm_normalizer/data/autorun_annotations/annotations.json @@ -0,0 +1,164 @@ +[ + { + "annotation_id": "ann-mnrol1zq-6eiif5d", + "run_id": "assistant-stage1-5513CzxPo1", + "case_id": "AUTO-001", + "session_id": "assistant-stage1-5513CzxPo1-AUTO-001", + "message_index": 1, + "rating": 1, + "comment": "Тут брак не только в формулировке ответа, а в самой логике попадания в запрос:\n\nвопрос про поставщиков с хвостами на конец месяца;\nещё и про признак систематической проблемы, а не разовую задержку;\nв ответе показан один документ списания с расчётного счёта по одному контрагенту;\nне объяснено, почему этот документ вообще релевантен запросу;\nне раскрыто, как он связан с хвостами, концом месяца и системностью;\nнет декомпозиции запроса и нет явного ответа на его основную часть.\n\nТо есть система фактически подменила задачу: вместо поиска паттерна по поставщикам выдала одиночную находку без обоснования.\n\n\n\nОтвет не попал в суть запроса: вопрос был о поставщиках с системными хвостами на конец месяца, а показан одиночный документ списания без объяснения его связи с хвостами, периодом и систематичностью. Нужны декомпозиция запроса, явное обоснование выбора документа и комментарий о степени покрытия контекста.", + "manual_case_decision": "needs_dialog_policy_fix", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-09T16:18:03.734Z", + "updated_at": "2026-04-09T16:18:03.734Z", + "context": { + "message_id": "msg-hHz2ydMJOa", + "trace_id": "address-z5C0-D-mMH", + "reply_type": "factual", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "Кто из поставщиков имеет хвосты с документами на конец месяца, которые уже больше похожи на систематическую проблему, а не на обычную задержку?", + "answer_text": "Найдено документов по контрагенту: 1.\n1. 2020-11-16T16:08:51Z | Списание с расчетного счета 00000000262 от 16.11.2020 16:08:51 | 0 / 0 | 8700 | аналитика: ВИЗАНТИЯ; Счет № 3363 от 16.11.2020 г." + } + }, + { + "annotation_id": "ann-mnrsn1s4-g1t5tks", + "run_id": "assistant-stage1-rDM8XCbg7q", + "case_id": "AUTO-001", + "session_id": "assistant-stage1-rDM8XCbg7q-AUTO-001", + "message_index": 1, + "rating": 3, + "comment": "Так, ну этот вопрос явно вне контура, тут ответ очень технический. Ну как бы юзеру вообще не надо знать ничего про адрес Query, это какая-то техническая хуета, ее нахуй не нужно вываливать. Вот. И просить переформулировать тоже не надо, потому что когда мы просим что-то переформулировать, это значит, мы что-то не поняли. Тут явно вопрос вне контура. Вот. Ну то есть мы как бы точно не будем разлиновку делать под этот запрос.", + "manual_case_decision": "out_of_scope_but_answer_softly", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-09T18:11:35.236Z", + "updated_at": "2026-04-09T18:11:35.236Z", + "context": { + "message_id": "msg-JqLbA9uuLs", + "trace_id": "address-vyNV6r-l6T", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "Какие поставщики у нас уже пару месяцев сдают акты без приходок. Может, их надо проконтролировать отдельно чтоб не засорять бухгалтерию дальше?", + "answer_text": "Этот запрос не подходит под address_query V1.\nПричина: intent пока не поддержан в address V1.\nЧто нужно уточнить: переформулируйте вопрос как адресный lookup по счету/контрагенту/договору." + } + }, + { + "annotation_id": "ann-mnrsrgxc-tstn1f1", + "run_id": "assistant-stage1-rDM8XCbg7q", + "case_id": "AUTO-002", + "session_id": "assistant-stage1-rDM8XCbg7q-AUTO-002", + "message_index": 1, + "rating": 3, + "comment": "Так, ну смотри, короче, ответ очень опять очень технический, не хватает обязательного якоря. Тут как бы мы не должны ни про какие там якоря там обязательно говорить, что есть обязательный якорь. Ну как бы это знаешь, такая очень абстрактная история. Так, дальше ответ. Причина контрагент по указанному имени Алясу не найден. Да только кто-то вообще не уточняет ни про каких конкретных. Он как раз и требует тебе найти какого-то контрагента. Он не знает. Он вот тут вопрос как раз про поиски. Вот, и точно- точно неправильная просьба. Тут надо смотреть на диапазоны, наверное, между заключением договора и там какими-то оплатами. Вот, и предполагать, что это оно как раз. То, что очень длинные окна, там они могут на годы там быть, например, я не знаю. Ну, короче, сыро очень ответчик. Очень сыро ответил и вообще не по теме.", + "manual_case_decision": "covered_but_bad_answer", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-09T18:15:01.488Z", + "updated_at": "2026-04-09T18:15:01.488Z", + "context": { + "message_id": "msg-ONDlFjoxbU", + "trace_id": "address-paNbrPvWpC", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "Где у нас висят покупатели со слишком длинным периодом между отправкой товара и его оплатой, и это уже вызывает тревогу?", + "answer_text": "Для точного адресного поиска не хватает обязательного якоря.\nПричина: контрагент по указанному имени/алиасу не найден в materialized live-строках.\nЧто нужно уточнить: уточните точное имя контрагента или добавьте ИНН." + } + }, + { + "annotation_id": "ann-mnrstmmx-dqr0qoq", + "run_id": "assistant-stage1-rDM8XCbg7q", + "case_id": "AUTO-003", + "session_id": "assistant-stage1-rDM8XCbg7q-AUTO-003", + "message_index": 1, + "rating": 1, + "comment": "Так мы вообще не должны вводить вот эту техническую историю про что сломано. Вот, я, если честно, скорее всего, надо просто ответить, что вопрос вне контекста.", + "manual_case_decision": "out_of_scope_but_answer_softly", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-09T18:16:42.200Z", + "updated_at": "2026-04-09T18:16:42.200Z", + "context": { + "message_id": "msg-ajecodZjyK", + "trace_id": "CQZp9tGQTTowij", + "reply_type": "clarification_required", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?", + "answer_text": "Коротко: Наиболее вероятный механизм проблемы подтвержден частично и требует первичной проверки. Что сломано:\n- Есть признаки проблемы, но без уточнений по периоду и объекту вывод ненадежен. Почему это похоже на проблему:\n- Сигнал проблемы есть, но механизм подтвержден не полностью. На чем это основано:\n- Опора частичная: часть требований покрыта не полностью.\n- Отдельно не подтверждено или покрыто частично: R1. Что проверить первым:\n- Уточните период проверки, чтобы подтвердить проблему без лишнего шума.\n- Проверьте связку документов и проводок по проблемному участку в указанном периоде.\n- Уточните период проверки (например, июль 2020).\n- Уточните счет или группу счетов (например, 19, 60, 62). Ограничения:\n- Ни одно требование не получило подтвержденного покрытия.\n- Нет explainable-сигналов why_included в результатах выборки.\n- Недостаточно покрытия required anchors для claim-bound grounding.\n- Недостаточно допустимого evidence для обоснованного ответа.\n- Targeted evidence acquisition не дал допустимых попаданий по claim target path." + } + }, + { + "annotation_id": "ann-mnrsvcxx-o0igav4", + "run_id": "assistant-stage1-rDM8XCbg7q", + "case_id": "AUTO-004", + "session_id": "assistant-stage1-rDM8XCbg7q-AUTO-004", + "message_index": 1, + "rating": 1, + "comment": "Тут опять очевидно очень прямой и конкретный запрос, и мы его должны вроде, по идее, как отработать. Он у нас уже вроде должен отрабатываться. Если нет, то значит надо его... Так, надо расширить маршрутизацию. То есть глобально надо найти, короче, документы на какой-то период определенный, если мы говорим про срез. Надо найти документы, которые не закрыты. Вот и всё. Как бы это очевидно. Ну, как бы не закрытые доки просто ищем и выводим. Вот. Если таких нету, то, значит, отвечаем, что такая проблема отсутствует.", + "manual_case_decision": "needs_routing_extension", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-09T18:18:02.949Z", + "updated_at": "2026-04-09T18:18:02.949Z", + "context": { + "message_id": "msg-fIfli9GAKl", + "trace_id": "address-0wCj3SAhfK", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "Где у нас есть платежи, но нет документов для закрытия взаиморасчетов? Это уже требует ручной проверки.", + "answer_text": "Для точного адресного поиска не хватает обязательного якоря.\nПричина: контрагент по указанному имени/алиасу не найден в materialized live-строках.\nЧто нужно уточнить: уточните точное имя контрагента или добавьте ИНН." + } + }, + { + "annotation_id": "ann-mnrsx62p-sp9mrfy", + "run_id": "assistant-stage1-rDM8XCbg7q", + "case_id": "AUTO-005", + "session_id": "assistant-stage1-rDM8XCbg7q-AUTO-005", + "message_index": 1, + "rating": 1, + "comment": "Опять точно такая же проблема. Ну как бы очень технический ответ про якоря и про обязательства. Это уже обсуждали. И никакие уточнения, а почему модель просто какие-то уточнения, это противоречит вообще-то изначальному запросу. Запрос максимально простой. Надо найти документы, которые не оплачены. То есть договора мертвые, без оплат. Вот и всё. У нас точно такие есть, и их точно довольно-таки легко маршрутизировать, если они у нас не маршрутизируются.", + "manual_case_decision": "needs_routing_extension", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-09T18:19:27.359Z", + "updated_at": "2026-04-09T20:06:33.494Z", + "context": { + "message_id": "msg-uVQqIiArS7", + "trace_id": "address-2NS8zxTvo5", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "По каким контрагентам документы есть, а оплат нет. Может, стоит взять на карандаш такие ситуации чтоб не тянуть дальше?", + "answer_text": "Для точного адресного поиска не хватает обязательного якоря.\nПричина: контрагент по указанному имени/алиасу не найден в materialized live-строках.\nЧто нужно уточнить: уточните точное имя контрагента или добавьте ИНН." + } + } +] \ No newline at end of file diff --git a/llm_normalizer/data/autorun_generators/history.json b/llm_normalizer/data/autorun_generators/history.json new file mode 100644 index 0000000..05f8fc1 --- /dev/null +++ b/llm_normalizer/data/autorun_generators/history.json @@ -0,0 +1,248 @@ +[ + { + "generation_id": "gen-mnrvs132-1dewq5r", + "created_at": "2026-04-09T19:39:26.463Z", + "mode": "qwen_seed", + "count": 5, + "domain": null, + "questions": [ + "Какие покупатели оставили хвосты по отгрузкам на конец месяца, которые скорее говорят про проблемы с документами, чем просто задержку платежей?", + "Где у нас висят неоплаченные реализации, что могут испортить баланс выручки, если их не проверять заранее?", + "Покажи контрагентов, по которым сальдо явно расходится с тем, что они напишут в сверке, если её запросить сейчас.", + "Есть ли авансы, которые уже давно не закрыты и требуют ручной перепроверки?", + "Какие поставщики оставили хвосты по документам на конец месяца, что указывают скорее на проблемы с оформлением, чем на задержку?" + ], + "generated_by": "manual_reviewer", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260409193926_gen-mnrvs132-1dewq5r.json", + "context": { + "llm_provider": "local", + "model": "Qwen2.5 14B Instruct 1M", + "assistant_prompt_version": "address_query_runtime_v1", + "decomposition_prompt_version": "normalizer_v2_0_2", + "prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and ", + "autogen_personality_id": "general", + "autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл." + } + }, + { + "generation_id": "gen-mnrvqxcg-wa3jsro", + "created_at": "2026-04-09T19:38:34.961Z", + "mode": "qwen_seed", + "count": 5, + "domain": null, + "questions": [ + "Какие у нас поставщики пока вообще никак не проявились в текущем месяце и это уже начинает выглядеть подозрительно?", + "Где у нас есть реализации, которые сидят без закрытий на конец отчетного периода, и они реально могут испортить финансовую картину?", + "Покажи контрагентов, по которым сальдо в 1С явно расходится с тем, что должно быть по данным их последнего акта сверки.", + "Есть ли такие авансы, которые уже давно не используются и их пора или списать, или перепроверить?", + "Где у нас есть оплаты за товары/услуги, но самих документов на эти платежи до сих пор нет в системе?" + ], + "generated_by": "manual_reviewer", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260409193834_gen-mnrvqxcg-wa3jsro.json", + "context": { + "llm_provider": "local", + "model": "Qwen2.5 14B Instruct 1M", + "assistant_prompt_version": "address_query_runtime_v1", + "decomposition_prompt_version": "normalizer_v2_0_2", + "prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and ", + "autogen_personality_id": "general", + "autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл." + } + }, + { + "generation_id": "gen-mnrshzcm-xyiv4gs", + "created_at": "2026-04-09T18:07:38.807Z", + "mode": "qwen_seed", + "count": 10, + "domain": null, + "questions": [ + "Какие поставщики у нас уже пару месяцев сдают акты без приходок. Может, их надо проконтролировать отдельно чтоб не засорять бухгалтерию дальше?", + "Где у нас висят покупатели со слишком длинным периодом между отправкой товара и его оплатой, и это уже вызывает тревогу?", + "Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?", + "Где у нас есть платежи, но нет документов для закрытия взаиморасчетов? Это уже требует ручной проверки.", + "По каким контрагентам документы есть, а оплат нет. Может, стоит взять на карандаш такие ситуации чтоб не тянуть дальше?", + "Есть ли зависшие авансы, которые нужно либо закрыть, либо перепроверить уже давно?", + "Какие реализации на конец периода выглядят так, будто они зависли и могут портить картину по выручке?", + "Где у нас отгрузки с кривыми документами. Это уже требует ручного анализа.", + "Кто из контрагентов давно не подтверждал свои расчеты. Может, стоит напомнить о сверках?", + "Какие покупатели долго задерживают оплату без явных причин. Это тоже требует внимания." + ], + "generated_by": "manual_reviewer", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260409180738_gen-mnrshzcm-xyiv4gs.json", + "context": { + "llm_provider": "local", + "model": "Qwen2.5 14B Instruct 1M", + "assistant_prompt_version": "address_query_runtime_v1", + "decomposition_prompt_version": "normalizer_v2_0_2", + "prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and ", + "autogen_personality_id": "general", + "autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл." + } + }, + { + "generation_id": "gen-mnrrdfbj-mrcxcjg", + "created_at": "2026-04-09T17:36:06.608Z", + "mode": "qwen_seed", + "count": 10, + "domain": null, + "questions": [ + "Какие покупатели на конец месяца держат нас в незакрытых взаиморасчетах уже больше чем обычно, и это начинает напоминать реальные проблемы а не просто задержку?", + "Где у нас с поставщиками видны хвосты по документам, которые явно вышли за рамки обычной задержки?", + "Какие реализации на конец периода еще не закрыты и могут испортить картину при ревизии?", + "Покажи контрагентов у которых сальдо скорее всего не совпадет с их актом сверки, если его запросить прямо сейчас.", + "Где по покупателям есть история отгрузили - денег нет - закрытия нет и это уже требует ручной проверки?", + "Есть ли зависшие авансы которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками?", + "Какие реализации на конец периода выглядят так будто они зависли и будут портить картину по выручке если их не проверить заранее?", + "Где у нас есть оплаты но не хватает документов чтобы закрыть взаиморасчеты?", + "Какие контрагенты показывают документы, но нормального закрытия оплатами нет - это требует ручной проверки?", + "Кто из контрагентов держит нас в незакрытых взаиморасчетах на конец месяца и это начинает напоминать реальные проблемы а не просто задержку?" + ], + "generated_by": "manual_reviewer", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260409173606_gen-mnrrdfbj-mrcxcjg.json", + "context": { + "llm_provider": "local", + "model": "Qwen2.5 14B Instruct 1M", + "assistant_prompt_version": "address_query_runtime_v1", + "decomposition_prompt_version": "normalizer_v2_0_2", + "prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and", + "autogen_personality_id": "general", + "autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл." + } + }, + { + "generation_id": "gen-mnrnrwtc-za8a8o0", + "created_at": "2026-04-09T15:55:24.001Z", + "mode": "qwen_seed", + "count": 20, + "domain": null, + "questions": [ + "Какие поставщики уже пару месяцев держат хвосты, которые выглядят как системная проблема, а не просто задержка с документами?", + "Где у нас покупатели 'отгрузили - денег нет - закрытия нет' и нужна ручная проверка этих контрагентов?", + "Покажи контрагентов, чьё сальдо скорее всего не совпадет с их актом сверки, если его запросить прямо сейчас.", + "Где у нас есть оплаты, но документов для закрытия взаиморасчетов нет совсем?", + "Какие контрагенты имеют документы, но нормального закрытия оплатами не видно?", + "Есть ли зависшие авансы, которые давно требуют перепроверки или окончательного закрытия?", + "Какие реализации на конец периода выглядят так, будто они зависли и портят картину по выручке?", + "По каким отгрузкам видно, что проблема не просто в том, что клиент не оплатил, а в том, что связка документов собрана криво?", + "Какие накладные на складе уже давно не сопровождаются поступлениями или отправками - это может быть подозрительно?", + "Есть ли контрагенты, у которых есть отгрузки без связанных оплат и их нужно проверить на наличие долгов?", + "Какие документы покупок были созданы давно, но не закрыты вводом накладных или актов - это может быть прямой риск?", + "Где у нас зависли авансовые отгрузки с датами старше полугода и их нужно либо списать, либо проверить детали?", + "Какие поставщики уже давно не подтверждали свои счета - это может указывать на проблемы в цепочке взаиморасчетов?", + "Есть ли контрагенты с отгрузками или покупками, где документы есть, а реальных транзакций нет - это может быть фиктивным?", + "Какие реализации уже подтверждены, но их сальдо не соотносится с фактической выручкой за период?", + "Где у нас зависли авансы поставщикам и они требуют ручной проверки на предмет реальных платежей?", + "Есть ли контрагенты, у которых есть отгрузки без связанных документов или платежей - это может быть проблемой для баланса?", + "Какие документы покупок не завершены вводом актов и их сальдо выглядит подозрительно?", + "Где у нас зависли авансовые поступления от клиентов, которые давно требуют проверки или списания?", + "Есть ли контрагенты, у которых есть реализации без связанных оплат - это может быть фиктивным?" + ], + "generated_by": "manual_reviewer", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260409155524_gen-mnrnrwtc-za8a8o0.json", + "context": { + "llm_provider": "local", + "model": "Qwen2.5 14B Instruct 1M", + "assistant_prompt_version": "address_query_runtime_v1", + "decomposition_prompt_version": "normalizer_v2_0_2", + "prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and", + "autogen_personality_id": "general", + "autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл." + } + }, + { + "generation_id": "gen-mnrmoiey-j9akyvu", + "created_at": "2026-04-09T15:24:45.754Z", + "mode": "qwen_seed", + "count": 2, + "domain": "settlements", + "questions": [ + "Кто из поставщиков имеет хвосты с документами на конец месяца, которые уже больше похожи на систематическую проблему, а не на обычную задержку?", + "Где у нас есть реализации, которые могут портить отчетность по выручке, если их не проверить до конца периода?" + ], + "generated_by": "diag", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260409152445_gen-mnrmoiey-j9akyvu.json", + "context": { + "llm_provider": "local", + "model": "qwen2.5-14b-instruct-1m", + "assistant_prompt_version": null, + "decomposition_prompt_version": null, + "prompt_fingerprint": null, + "autogen_personality_id": null, + "autogen_personality_prompt": "????????? ??????? ??????? ?? ????????." + } + }, + { + "generation_id": "gen-mnrmc8bu-wv7c3o0", + "created_at": "2026-04-09T15:15:12.810Z", + "mode": "qwen_seed", + "count": 54, + "domain": null, + "questions": [ + "{\"questions\":[\"По каким покупателям у нас на конец месяца хвосты с отгрузками без денег уже стали нормой и требуют проверки?\",\"Где по контрагентам висит история 'отгрузили - денег нет - закрытия нет', которая точно нуждается в ручной проверке?\",\"Покажи контрагентов, у которых сальдо скорее всего не совпадет с их актуальным актом сверки сейчас.\",\"Где есть оплаты, но отсутствуют документы для закрытия взаиморасчетов?\",\"По каким контрагентам документы есть, а нормального закрытия оплатами нет вовсе?\",\"Есть ли зависшие авансы, которые давно надо было перепроверить или уже списать как неликвидные?\",\"Какие реализации на конец периода выглядят так, будто они зависли и могут испортить картину по выручке без проверки?\",\"По каким отгрузкам видно, что проблема не просто в оплате клиента, а в кривой сборке документов изначально?\",\"Где у нас есть непогашенные задолженности поставщикам, которые уже давно требуют прокрутки и анализа?\",\"Какие счета-фактуры выставлены, но не оплачены на конец месяца - нужно их проверить или отозвать?\",\"Покажи контрагентов, у которых висят отгрузки без документов подтверждения принятия. Это уже проблема или еще нет?\",\"Есть ли такие покупатели, по которым сальдо точно не совпадает с актуальным состоянием их расчетов - нужно проверить и исправить?\",\"Где есть документы, но оплаты не приходят, хотя все сроки давно прошли?\",\"Какие отгрузки на конец периода выглядят так, будто они зависли и будут портить картину по выручке без прокрутки?\",\"По каким контрагентам видно, что проблема в сборе документов изначально, а не просто в задержке оплаты клиентом?\",\"Где у нас есть непогашенные авансы покупателей, которые давно требуют пересмотра и анализа?\",\"Какие счета-фактуры выставлены, но не отражены в документах подтверждения принятия - нужно их проверить или отозвать?\",\"Покажи контрагентов, у которых сальдо точно не совпадает с актуальным состоянием расчетов. Это уже проблема или еще нет?\",\"Где есть оплаты, но документов для закрытия взаиморасчетов все равно не хватает?\",\"По каким контрагентам документы есть, а нормального закрытия оплатами так и не видно - это уже проблема или еще нет?\",\"Есть ли такие зависшие авансы, которые давно надо было перепроверить или списать как неликвидные?\",\"Какие реализации на конец периода выглядят так, будто они зависли и могут испортить картину по выручке без проверки?\",\"По каким отгрузкам видно, что проблема не просто в оплате клиента, а в кривой сборке документов изначально?\",\"Где у нас есть непогашенные задолженности поставщикам, которые уже давно требуют прокрутки и анализа?\",\"Какие счета-фактуры выставлены, но не оплачены на конец месяца - нужно их проверить или отозвать?\",\"Покажи контрагентов, у которых висят отгрузки без документов подтверждения принятия. Это уже проблема или еще нет?\",\"Есть ли такие покупатели, по которым сальдо точно не совпадает с актуальным состоянием их расчетов - нужно проверить", + "йо По каким поставщикам у нас на конец месяца остались хвосты, которые уже не похожи на обычную задержку документов, а выглядят как реальная проблема в цепочке?", + "слушай Где по покупателям у нас висит история \"отгрузили - денег нет - закрытия нет\", и по каким контрагентам это уже требует ручной проверки? без воды", + "подскажи плиз Покажи контрагентов, по которым сальдо у нас, скорее всего, не совпадет с их актом сверки, если его запросить прямо сейчас. по факту", + "короче Где у нас есть оплаты, но не хватает документов, которые должны были закрыть взаиморасчеты? и коротко", + "мож По каким контрагентам, наоборот, документы есть, а нормального закрытия оплатами не видно? прям сейчас", + "а ну-ка Есть ли такие зависшие авансы, которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками? за весь период", + "йо Какие реализации на конец периода выглядят так, будто они зависли и будут портить картину по выручке, если их не проверить заранее?", + "слушай По каким отгрузкам видно, что проблема не просто в том, что клиент не оплатил, а в том, что сама связка документов собрана криво? без воды", + "подскажи плиз Покажи реализации, где хвост выглядит особенно неприятно: сумма не маленькая, возраст хвоста уже заметный, и при этом не видно нормального завершения цепочки. по факту", + "короче Где по 90/62 история похожа на \"вроде все проведено, но если копнуть, закрытие держится на кривой связке\"? и коротко", + "мож Есть ли случаи, где реализация попала в период, а подтверждающие документы или оплата до сих пор живут в какой-то полуразобранной логике? прям сейчас", + "а ну-ка По каким продажам на конец месяца видно, что бухгалтер потом будет долго распутывать, почему все это не сошлось нормально? за весь период", + "йо Какие банковские движения выглядят так, будто выписка есть, а нормального отражения в учете под ней не хватает?", + "слушай Где по банку можно заподозрить, что документ и проводка вроде есть, но логика операции все равно не собрана в нормальную цепочку? без воды", + "подскажи плиз Есть ли движения по счету 51, которые выглядят корректно по сумме, но по смыслу оставляют после себя подозрительный хвост? по факту", + "короче Покажи банковские кейсы, где, скорее всего, проблема не в платеже как таковом, а в том, что он не туда лег или не тем документом закрылся. и коротко", + "мож Где банк и бухгалтерский контур, скорее всего, расходятся не по одной строке, а по паттерну, который уже начинает повторяться? прям сейчас", + "а ну-ка Какие товарные позиции выглядят так, будто их уже продавали, а нормального прихода под них в базе не видно? за весь период", + "йо Где по товарам у нас отрицательные или подозрительные остатки, которые, скорее всего, связаны не с жизнью, а с ошибкой в учете?", + "слушай Есть ли случаи, где приход и реализация вроде есть оба, но даты между ними выглядят так, будто кто-то завел документы задним числом или с ошибкой? без воды", + "подскажи плиз Покажи товарные хвосты, которые сильнее всего искажают картину периода и требуют проверки до закрытия месяца. по факту", + "короче Где по складу и реализации видно, что себестоимость продажи подтверждена слабо или вообще опирается на кривую цепочку? и коротко", + "мож Что сейчас лежит на 10 счете так, будто это уже давно надо было либо списать, либо хотя бы проверить, почему оно до сих пор висит? прям сейчас", + "а ну-ка Есть ли материалы, по которым остаток выглядит нелогично: движения были, хозяйственная логика слабая, а в учете все еще что-то торчит? за весь период", + "йо Покажи позиции по материалам, где возможен эффект \"вроде сумма не огромная, но учетная логика выглядит криво\".", + "слушай Какие записи на 97 счете больше всего похожи на ошибку в датах начала, конца или самом сроке списания? без воды", + "подскажи плиз Есть ли такие расходы будущих периодов, которые заведены, но по ним не видно нормальной ежемесячной жизни, как будто запись повисла сама по себе? по факту", + "короче Покажи кейсы по 97 счету, где срок документа и срок списания визуально противоречат друг другу. и коротко", + "мож Есть ли основные средства, по которым параметры карточки выглядят так, будто амортизацию им задали не по логике объекта, а \"как получилось\"? прям сейчас", + "а ну-ка Покажи объекты ОС, где риск не в сумме, а в том, что карточка и логика начисления выглядят подозрительно и могут аукнуться позже. за весь период", + "йо По каким поставщикам на конец июня не бьются взаиморасчеты, покажи документы, оплаты и хвосты.", + "слушай По каким реализациям 90/62 хвосты не закрылись оплатой, разложи по цепочке документов. без воды", + "подскажи плиз Где в июне не сходится 60/51: разложи по документу, оплате и закрывающему, чем подтверждается каждый шаг. по факту", + "короче Разложи по контрагентам цепочку: отгрузка -> оплата -> закрывающий, чтобы понять где рвется подтверждение. и коротко", + "мож По поставщикам где повисло в цепочке поступление-оплата-закрытие по 60, покажи проблемные связки. прям сейчас", + "а ну-ка Найди где по 62 не собралось: нужен разбор по документам, оплатам и проводкам с причинно-следственной цепочкой. за весь период", + "йо Покажи по июню все случаи когда реализация без оплаты и где в цепочке ошибка подтверждения.", + "слушай Сделай причинный разбор хвостов по 60: документ, оплата, проводка, закрывающий, где пошло криво. без воды", + "подскажи плиз Почему у части покупателей не видно закрытия, разложи цепочку документов и оплат по июню. по факту", + "короче У кого из контрагентов в июне хвосты между 60 и банком, разложи по документам/оплатам/закрывающим. и коротко", + "мож Сделай рейтинг самых рисковых хвостов перед закрытием периода за июнь. прям сейчас", + "а ну-ка Что у нас выглядит самым проблемным перед закрытием июня, если смотреть на компанию в целом? за весь период", + "йо Собери топ-10 риск-зон учета по июню и приоритизируй, куда лезть сначала.", + "слушай Дай обзорный риск-срез перед сдачей отчетности: где максимальная концентрация ошибок. без воды", + "подскажи плиз Сделай приоритизированный обзор ручных проверок по компании за июнь. по факту", + "короче Покажи документ по номеру 000123 и строку проводки, нужен точный source-of-record. и коротко", + "мож Покажи по банку документ №TRX-88 и связанную проводку по 51. прям сейчас", + "а ну-ка Покажи проводку по документу INV-2020-0615, нужна конкретная строка и источник. за весь период", + "йо Дай точечный drilldown по документу №PAY-441 и его проводке по 51.", + "слушай Покажи карточку конкретной операции DOC-7781 и связанную проводку. без воды", + "подскажи плиз По 97 счету проверь, где возможна ошибка дат начала и окончания списания. по факту", + "короче Проверь контрольные правила по ОС: где ошибки в сроках амортизации и учетной группе. и коротко", + "мож По 10 счету проверь где нарушены правила оценки остатков. прям сейчас" + ], + "generated_by": "manual_reviewer", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260409151512_gen-mnrmc8bu-wv7c3o0.json", + "context": { + "llm_provider": "local", + "model": "Qwen2.5 14B Instruct 1M", + "assistant_prompt_version": "address_query_runtime_v1", + "decomposition_prompt_version": "normalizer_v2_0_2", + "prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and", + "autogen_personality_id": "general", + "autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл." + } + } +] \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409151512_gen-mnrmc8bu-wv7c3o0.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409151512_gen-mnrmc8bu-wv7c3o0.json new file mode 100644 index 0000000..0244c60 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409151512_gen-mnrmc8bu-wv7c3o0.json @@ -0,0 +1,878 @@ +{ + "suite_id": "assistant_autogen_gen-mnrmc8bu-wv7c3o0", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-09T15:15:12.810Z", + "generation_id": "gen-mnrmc8bu-wv7c3o0", + "mode": "qwen_seed", + "domain": null, + "scenario_count": 54, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005", + "AUTO-006", + "AUTO-007", + "AUTO-008", + "AUTO-009", + "AUTO-010", + "AUTO-011", + "AUTO-012", + "AUTO-013", + "AUTO-014", + "AUTO-015", + "AUTO-016", + "AUTO-017", + "AUTO-018", + "AUTO-019", + "AUTO-020", + "AUTO-021", + "AUTO-022", + "AUTO-023", + "AUTO-024", + "AUTO-025", + "AUTO-026", + "AUTO-027", + "AUTO-028", + "AUTO-029", + "AUTO-030", + "AUTO-031", + "AUTO-032", + "AUTO-033", + "AUTO-034", + "AUTO-035", + "AUTO-036", + "AUTO-037", + "AUTO-038", + "AUTO-039", + "AUTO-040", + "AUTO-041", + "AUTO-042", + "AUTO-043", + "AUTO-044", + "AUTO-045", + "AUTO-046", + "AUTO-047", + "AUTO-048", + "AUTO-049", + "AUTO-050", + "AUTO-051", + "AUTO-052", + "AUTO-053", + "AUTO-054" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "{\"questions\":[\"По каким покупателям у нас на конец месяца хвосты с отгрузками без денег уже стали нормой и требуют проверки?\",\"Где по контрагентам висит история 'отгрузили - денег нет - закрытия нет', которая точно нуждается в ручной проверке?\",\"Покажи контрагентов, у которых сальдо скорее всего не совпадет с их актуальным актом сверки сейчас.\",\"Где есть оплаты, но отсутствуют документы для закрытия взаиморасчетов?\",\"По каким контрагентам документы есть, а нормального закрытия оплатами нет вовсе?\",\"Есть ли зависшие авансы, которые давно надо было перепроверить или уже списать как неликвидные?\",\"Какие реализации на конец периода выглядят так, будто они зависли и могут испортить картину по выручке без проверки?\",\"По каким отгрузкам видно, что проблема не просто в оплате клиента, а в кривой сборке документов изначально?\",\"Где у нас есть непогашенные задолженности поставщикам, которые уже давно требуют прокрутки и анализа?\",\"Какие счета-фактуры выставлены, но не оплачены на конец месяца - нужно их проверить или отозвать?\",\"Покажи контрагентов, у которых висят отгрузки без документов подтверждения принятия. Это уже проблема или еще нет?\",\"Есть ли такие покупатели, по которым сальдо точно не совпадает с актуальным состоянием их расчетов - нужно проверить и исправить?\",\"Где есть документы, но оплаты не приходят, хотя все сроки давно прошли?\",\"Какие отгрузки на конец периода выглядят так, будто они зависли и будут портить картину по выручке без прокрутки?\",\"По каким контрагентам видно, что проблема в сборе документов изначально, а не просто в задержке оплаты клиентом?\",\"Где у нас есть непогашенные авансы покупателей, которые давно требуют пересмотра и анализа?\",\"Какие счета-фактуры выставлены, но не отражены в документах подтверждения принятия - нужно их проверить или отозвать?\",\"Покажи контрагентов, у которых сальдо точно не совпадает с актуальным состоянием расчетов. Это уже проблема или еще нет?\",\"Где есть оплаты, но документов для закрытия взаиморасчетов все равно не хватает?\",\"По каким контрагентам документы есть, а нормального закрытия оплатами так и не видно - это уже проблема или еще нет?\",\"Есть ли такие зависшие авансы, которые давно надо было перепроверить или списать как неликвидные?\",\"Какие реализации на конец периода выглядят так, будто они зависли и могут испортить картину по выручке без проверки?\",\"По каким отгрузкам видно, что проблема не просто в оплате клиента, а в кривой сборке документов изначально?\",\"Где у нас есть непогашенные задолженности поставщикам, которые уже давно требуют прокрутки и анализа?\",\"Какие счета-фактуры выставлены, но не оплачены на конец месяца - нужно их проверить или отозвать?\",\"Покажи контрагентов, у которых висят отгрузки без документов подтверждения принятия. Это уже проблема или еще нет?\",\"Есть ли такие покупатели, по которым сальдо точно не совпадает с актуальным состоянием их расчетов - нужно проверить" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо По каким поставщикам у нас на конец месяца остались хвосты, которые уже не похожи на обычную задержку документов, а выглядят как реальная проблема в цепочке?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-003", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай Где по покупателям у нас висит история \"отгрузили - денег нет - закрытия нет\", и по каким контрагентам это уже требует ручной проверки? без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-004", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз Покажи контрагентов, по которым сальдо у нас, скорее всего, не совпадет с их актом сверки, если его запросить прямо сейчас. по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-005", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче Где у нас есть оплаты, но не хватает документов, которые должны были закрыть взаиморасчеты? и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-006", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож По каким контрагентам, наоборот, документы есть, а нормального закрытия оплатами не видно? прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-007", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "а ну-ка Есть ли такие зависшие авансы, которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками? за весь период" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-008", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо Какие реализации на конец периода выглядят так, будто они зависли и будут портить картину по выручке, если их не проверить заранее?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-009", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай По каким отгрузкам видно, что проблема не просто в том, что клиент не оплатил, а в том, что сама связка документов собрана криво? без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-010", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз Покажи реализации, где хвост выглядит особенно неприятно: сумма не маленькая, возраст хвоста уже заметный, и при этом не видно нормального завершения цепочки. по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-011", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче Где по 90/62 история похожа на \"вроде все проведено, но если копнуть, закрытие держится на кривой связке\"? и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-012", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож Есть ли случаи, где реализация попала в период, а подтверждающие документы или оплата до сих пор живут в какой-то полуразобранной логике? прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-013", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "а ну-ка По каким продажам на конец месяца видно, что бухгалтер потом будет долго распутывать, почему все это не сошлось нормально? за весь период" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-014", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо Какие банковские движения выглядят так, будто выписка есть, а нормального отражения в учете под ней не хватает?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-015", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай Где по банку можно заподозрить, что документ и проводка вроде есть, но логика операции все равно не собрана в нормальную цепочку? без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-016", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз Есть ли движения по счету 51, которые выглядят корректно по сумме, но по смыслу оставляют после себя подозрительный хвост? по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-017", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче Покажи банковские кейсы, где, скорее всего, проблема не в платеже как таковом, а в том, что он не туда лег или не тем документом закрылся. и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-018", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож Где банк и бухгалтерский контур, скорее всего, расходятся не по одной строке, а по паттерну, который уже начинает повторяться? прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-019", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "а ну-ка Какие товарные позиции выглядят так, будто их уже продавали, а нормального прихода под них в базе не видно? за весь период" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-020", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо Где по товарам у нас отрицательные или подозрительные остатки, которые, скорее всего, связаны не с жизнью, а с ошибкой в учете?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-021", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай Есть ли случаи, где приход и реализация вроде есть оба, но даты между ними выглядят так, будто кто-то завел документы задним числом или с ошибкой? без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-022", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз Покажи товарные хвосты, которые сильнее всего искажают картину периода и требуют проверки до закрытия месяца. по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-023", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче Где по складу и реализации видно, что себестоимость продажи подтверждена слабо или вообще опирается на кривую цепочку? и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-024", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож Что сейчас лежит на 10 счете так, будто это уже давно надо было либо списать, либо хотя бы проверить, почему оно до сих пор висит? прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-025", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "а ну-ка Есть ли материалы, по которым остаток выглядит нелогично: движения были, хозяйственная логика слабая, а в учете все еще что-то торчит? за весь период" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-026", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо Покажи позиции по материалам, где возможен эффект \"вроде сумма не огромная, но учетная логика выглядит криво\"." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-027", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай Какие записи на 97 счете больше всего похожи на ошибку в датах начала, конца или самом сроке списания? без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-028", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз Есть ли такие расходы будущих периодов, которые заведены, но по ним не видно нормальной ежемесячной жизни, как будто запись повисла сама по себе? по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-029", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче Покажи кейсы по 97 счету, где срок документа и срок списания визуально противоречат друг другу. и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-030", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож Есть ли основные средства, по которым параметры карточки выглядят так, будто амортизацию им задали не по логике объекта, а \"как получилось\"? прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-031", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "а ну-ка Покажи объекты ОС, где риск не в сумме, а в том, что карточка и логика начисления выглядят подозрительно и могут аукнуться позже. за весь период" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-032", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо По каким поставщикам на конец июня не бьются взаиморасчеты, покажи документы, оплаты и хвосты." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-033", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай По каким реализациям 90/62 хвосты не закрылись оплатой, разложи по цепочке документов. без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-034", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз Где в июне не сходится 60/51: разложи по документу, оплате и закрывающему, чем подтверждается каждый шаг. по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-035", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче Разложи по контрагентам цепочку: отгрузка -> оплата -> закрывающий, чтобы понять где рвется подтверждение. и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-036", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож По поставщикам где повисло в цепочке поступление-оплата-закрытие по 60, покажи проблемные связки. прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-037", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "а ну-ка Найди где по 62 не собралось: нужен разбор по документам, оплатам и проводкам с причинно-следственной цепочкой. за весь период" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-038", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо Покажи по июню все случаи когда реализация без оплаты и где в цепочке ошибка подтверждения." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-039", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай Сделай причинный разбор хвостов по 60: документ, оплата, проводка, закрывающий, где пошло криво. без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-040", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз Почему у части покупателей не видно закрытия, разложи цепочку документов и оплат по июню. по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-041", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче У кого из контрагентов в июне хвосты между 60 и банком, разложи по документам/оплатам/закрывающим. и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-042", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож Сделай рейтинг самых рисковых хвостов перед закрытием периода за июнь. прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-043", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "а ну-ка Что у нас выглядит самым проблемным перед закрытием июня, если смотреть на компанию в целом? за весь период" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-044", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо Собери топ-10 риск-зон учета по июню и приоритизируй, куда лезть сначала." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-045", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай Дай обзорный риск-срез перед сдачей отчетности: где максимальная концентрация ошибок. без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-046", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз Сделай приоритизированный обзор ручных проверок по компании за июнь. по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-047", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче Покажи документ по номеру 000123 и строку проводки, нужен точный source-of-record. и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-048", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож Покажи по банку документ №TRX-88 и связанную проводку по 51. прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-049", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "а ну-ка Покажи проводку по документу INV-2020-0615, нужна конкретная строка и источник. за весь период" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-050", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо Дай точечный drilldown по документу №PAY-441 и его проводке по 51." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-051", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "слушай Покажи карточку конкретной операции DOC-7781 и связанную проводку. без воды" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-052", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "подскажи плиз По 97 счету проверь, где возможна ошибка дат начала и окончания списания. по факту" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-053", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "короче Проверь контрольные правила по ОС: где ошибки в сроках амортизации и учетной группе. и коротко" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-054", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "мож По 10 счету проверь где нарушены правила оценки остатков. прям сейчас" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409152445_gen-mnrmoiey-j9akyvu.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409152445_gen-mnrmoiey-j9akyvu.json new file mode 100644 index 0000000..4d71c03 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409152445_gen-mnrmoiey-j9akyvu.json @@ -0,0 +1,46 @@ +{ + "suite_id": "assistant_autogen_gen-mnrmoiey-j9akyvu", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-09T15:24:45.754Z", + "generation_id": "gen-mnrmoiey-j9akyvu", + "mode": "qwen_seed", + "domain": "settlements", + "scenario_count": 2, + "case_ids": [ + "AUTO-001", + "AUTO-002" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_settlements", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Кто из поставщиков имеет хвосты с документами на конец месяца, которые уже больше похожи на систематическую проблему, а не на обычную задержку?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_settlements", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас есть реализации, которые могут портить отчетность по выручке, если их не проверить до конца периода?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409155524_gen-mnrnrwtc-za8a8o0.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409155524_gen-mnrnrwtc-za8a8o0.json new file mode 100644 index 0000000..c8bf2cb --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409155524_gen-mnrnrwtc-za8a8o0.json @@ -0,0 +1,334 @@ +{ + "suite_id": "assistant_autogen_gen-mnrnrwtc-za8a8o0", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-09T15:55:24.001Z", + "generation_id": "gen-mnrnrwtc-za8a8o0", + "mode": "qwen_seed", + "domain": null, + "scenario_count": 20, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005", + "AUTO-006", + "AUTO-007", + "AUTO-008", + "AUTO-009", + "AUTO-010", + "AUTO-011", + "AUTO-012", + "AUTO-013", + "AUTO-014", + "AUTO-015", + "AUTO-016", + "AUTO-017", + "AUTO-018", + "AUTO-019", + "AUTO-020" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие поставщики уже пару месяцев держат хвосты, которые выглядят как системная проблема, а не просто задержка с документами?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас покупатели 'отгрузили - денег нет - закрытия нет' и нужна ручная проверка этих контрагентов?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-003", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Покажи контрагентов, чьё сальдо скорее всего не совпадет с их актом сверки, если его запросить прямо сейчас." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-004", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас есть оплаты, но документов для закрытия взаиморасчетов нет совсем?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-005", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие контрагенты имеют документы, но нормального закрытия оплатами не видно?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-006", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли зависшие авансы, которые давно требуют перепроверки или окончательного закрытия?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-007", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие реализации на конец периода выглядят так, будто они зависли и портят картину по выручке?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-008", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "По каким отгрузкам видно, что проблема не просто в том, что клиент не оплатил, а в том, что связка документов собрана криво?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-009", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие накладные на складе уже давно не сопровождаются поступлениями или отправками - это может быть подозрительно?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-010", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли контрагенты, у которых есть отгрузки без связанных оплат и их нужно проверить на наличие долгов?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-011", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие документы покупок были созданы давно, но не закрыты вводом накладных или актов - это может быть прямой риск?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-012", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас зависли авансовые отгрузки с датами старше полугода и их нужно либо списать, либо проверить детали?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-013", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие поставщики уже давно не подтверждали свои счета - это может указывать на проблемы в цепочке взаиморасчетов?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-014", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли контрагенты с отгрузками или покупками, где документы есть, а реальных транзакций нет - это может быть фиктивным?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-015", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие реализации уже подтверждены, но их сальдо не соотносится с фактической выручкой за период?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-016", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас зависли авансы поставщикам и они требуют ручной проверки на предмет реальных платежей?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-017", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли контрагенты, у которых есть отгрузки без связанных документов или платежей - это может быть проблемой для баланса?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-018", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие документы покупок не завершены вводом актов и их сальдо выглядит подозрительно?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-019", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас зависли авансовые поступления от клиентов, которые давно требуют проверки или списания?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-020", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли контрагенты, у которых есть реализации без связанных оплат - это может быть фиктивным?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409173606_gen-mnrrdfbj-mrcxcjg.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409173606_gen-mnrrdfbj-mrcxcjg.json new file mode 100644 index 0000000..2c5d5ff --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409173606_gen-mnrrdfbj-mrcxcjg.json @@ -0,0 +1,174 @@ +{ + "suite_id": "assistant_autogen_gen-mnrrdfbj-mrcxcjg", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-09T17:36:06.607Z", + "generation_id": "gen-mnrrdfbj-mrcxcjg", + "mode": "qwen_seed", + "domain": null, + "scenario_count": 10, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005", + "AUTO-006", + "AUTO-007", + "AUTO-008", + "AUTO-009", + "AUTO-010" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие покупатели на конец месяца держат нас в незакрытых взаиморасчетах уже больше чем обычно, и это начинает напоминать реальные проблемы а не просто задержку?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас с поставщиками видны хвосты по документам, которые явно вышли за рамки обычной задержки?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-003", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие реализации на конец периода еще не закрыты и могут испортить картину при ревизии?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-004", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Покажи контрагентов у которых сальдо скорее всего не совпадет с их актом сверки, если его запросить прямо сейчас." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-005", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где по покупателям есть история отгрузили - денег нет - закрытия нет и это уже требует ручной проверки?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-006", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли зависшие авансы которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-007", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие реализации на конец периода выглядят так будто они зависли и будут портить картину по выручке если их не проверить заранее?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-008", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас есть оплаты но не хватает документов чтобы закрыть взаиморасчеты?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-009", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие контрагенты показывают документы, но нормального закрытия оплатами нет - это требует ручной проверки?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-010", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Кто из контрагентов держит нас в незакрытых взаиморасчетах на конец месяца и это начинает напоминать реальные проблемы а не просто задержку?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409180738_gen-mnrshzcm-xyiv4gs.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409180738_gen-mnrshzcm-xyiv4gs.json new file mode 100644 index 0000000..e6fa536 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409180738_gen-mnrshzcm-xyiv4gs.json @@ -0,0 +1,174 @@ +{ + "suite_id": "assistant_autogen_gen-mnrshzcm-xyiv4gs", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-09T18:07:38.806Z", + "generation_id": "gen-mnrshzcm-xyiv4gs", + "mode": "qwen_seed", + "domain": null, + "scenario_count": 10, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005", + "AUTO-006", + "AUTO-007", + "AUTO-008", + "AUTO-009", + "AUTO-010" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие поставщики у нас уже пару месяцев сдают акты без приходок. Может, их надо проконтролировать отдельно чтоб не засорять бухгалтерию дальше?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас висят покупатели со слишком длинным периодом между отправкой товара и его оплатой, и это уже вызывает тревогу?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-003", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-004", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас есть платежи, но нет документов для закрытия взаиморасчетов? Это уже требует ручной проверки." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-005", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "По каким контрагентам документы есть, а оплат нет. Может, стоит взять на карандаш такие ситуации чтоб не тянуть дальше?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-006", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли зависшие авансы, которые нужно либо закрыть, либо перепроверить уже давно?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-007", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие реализации на конец периода выглядят так, будто они зависли и могут портить картину по выручке?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-008", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас отгрузки с кривыми документами. Это уже требует ручного анализа." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-009", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Кто из контрагентов давно не подтверждал свои расчеты. Может, стоит напомнить о сверках?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-010", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие покупатели долго задерживают оплату без явных причин. Это тоже требует внимания." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409193834_gen-mnrvqxcg-wa3jsro.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409193834_gen-mnrvqxcg-wa3jsro.json new file mode 100644 index 0000000..11538d5 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409193834_gen-mnrvqxcg-wa3jsro.json @@ -0,0 +1,94 @@ +{ + "suite_id": "assistant_autogen_gen-mnrvqxcg-wa3jsro", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-09T19:38:34.960Z", + "generation_id": "gen-mnrvqxcg-wa3jsro", + "mode": "qwen_seed", + "domain": null, + "scenario_count": 5, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие у нас поставщики пока вообще никак не проявились в текущем месяце и это уже начинает выглядеть подозрительно?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас есть реализации, которые сидят без закрытий на конец отчетного периода, и они реально могут испортить финансовую картину?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-003", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Покажи контрагентов, по которым сальдо в 1С явно расходится с тем, что должно быть по данным их последнего акта сверки." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-004", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли такие авансы, которые уже давно не используются и их пора или списать, или перепроверить?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-005", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас есть оплаты за товары/услуги, но самих документов на эти платежи до сих пор нет в системе?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409193926_gen-mnrvs132-1dewq5r.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409193926_gen-mnrvs132-1dewq5r.json new file mode 100644 index 0000000..7b6a4d0 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260409193926_gen-mnrvs132-1dewq5r.json @@ -0,0 +1,94 @@ +{ + "suite_id": "assistant_autogen_gen-mnrvs132-1dewq5r", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-09T19:39:26.463Z", + "generation_id": "gen-mnrvs132-1dewq5r", + "mode": "qwen_seed", + "domain": null, + "scenario_count": 5, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие покупатели оставили хвосты по отгрузкам на конец месяца, которые скорее говорят про проблемы с документами, чем просто задержку платежей?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас висят неоплаченные реализации, что могут испортить баланс выручки, если их не проверять заранее?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-003", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Покажи контрагентов, по которым сальдо явно расходится с тем, что они напишут в сверке, если её запросить сейчас." + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-004", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли авансы, которые уже давно не закрыты и требуют ручной перепроверки?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-005", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие поставщики оставили хвосты по документам на конец месяца, что указывают скорее на проблемы с оформлением, чем на задержку?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-O2V_MDy1qP.json b/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-O2V_MDy1qP.json new file mode 100644 index 0000000..875b2d5 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-O2V_MDy1qP.json @@ -0,0 +1,70 @@ +{ + "suite_id": "assistant_autogen_runtime_job-O2V_MDy1qP", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_runtime_v0_1", + "scenario_count": 5, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие покупатели оставили хвосты по отгрузкам на конец месяца, которые скорее говорят про проблемы с документами, чем просто задержку платежей?" + } + ] + }, + { + "case_id": "AUTO-002", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Где у нас висят неоплаченные реализации, что могут испортить баланс выручки, если их не проверять заранее?" + } + ] + }, + { + "case_id": "AUTO-003", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Покажи контрагентов, по которым сальдо явно расходится с тем, что они напишут в сверке, если её запросить сейчас." + } + ] + }, + { + "case_id": "AUTO-004", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Есть ли авансы, которые уже давно не закрыты и требуют ручной перепроверки?" + } + ] + }, + { + "case_id": "AUTO-005", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "Какие поставщики оставили хвосты по документам на конец месяца, что указывают скорее на проблемы с оформлением, чем на задержку?" + } + ] + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/eval-WJUoAGRQua.report.json b/llm_normalizer/data/eval_cases/eval-WJUoAGRQua.report.json new file mode 100644 index 0000000..04c7a1b --- /dev/null +++ b/llm_normalizer/data/eval_cases/eval-WJUoAGRQua.report.json @@ -0,0 +1,112 @@ +{ + "run_id": "eval-WJUoAGRQua", + "timestamp": "2026-04-09T18:01:58.497Z", + "mode": "single-pass-strict", + "use_mock": true, + "prompt_version": "normalizer_v2_0_2", + "schema_version": "v2_0_2", + "dataset": { + "source": "inline_raw_questions", + "file": null, + "raw_questions_count": 2 + }, + "cases_total": 2, + "metrics": { + "schema_validation_pass_rate": 100, + "scope_detection_accuracy": null, + "scope_in_scope_rate": 100, + "multi_intent_detected_rate": 0, + "clarification_required_rate": 0, + "avg_fragments_per_message": 1, + "out_of_scope_fragment_rate": 0, + "routed_fragment_rate": 100, + "no_route_fragment_rate": 0, + "route_resolution_accuracy": null, + "no_route_precision": null, + "false_no_route_rate": null, + "execution_state_consistency_rate": 100, + "executable_with_soft_assumptions_rate": 100, + "soft_assumption_used_fragment_rate": 100, + "clarification_precision": null, + "clarification_recall": null, + "false_clarification_rate": null + }, + "budget": { + "requests_total": 0, + "retries_used": 0 + }, + "clarification_eval": { + "labeled_cases": 0, + "true_positive": 0, + "false_positive": 0, + "false_negative": 0 + }, + "route_eval": { + "labeled_cases": 0, + "correct_cases": 0, + "expected_routed_cases": 0, + "no_route_true_positive": 0, + "no_route_false_positive": 0 + }, + "scope_eval": { + "labeled_cases": 0, + "correct_cases": 0 + }, + "execution_state_eval": { + "checks_total": 2, + "checks_passed": 2 + }, + "route_distribution": { + "store_feature_risk": 1, + "hybrid_store_plus_live": 1 + }, + "fallback_distribution": { + "none": 2 + }, + "results": [ + { + "case_id": "BQ-001", + "raw_question": "Проверь счет 60 за июнь 2020", + "validation_passed": true, + "message_in_scope": true, + "scope_confidence": "high", + "contains_multiple_tasks": false, + "fragments_total": 1, + "in_scope_fragments": 1, + "out_of_scope_fragments": 0, + "unclear_fragments": 0, + "fallback_type": "none", + "predicted_route_status": "routed", + "expected_route_status": null, + "predicted_no_route_reason": null, + "expected_no_route_reason": null, + "predicted_clarification_required": false, + "expected_clarification_required": null, + "executable_with_soft_assumptions_fragments": 1, + "trace_id": "JDGrTt0tveFgfh", + "request_count_for_case": 0 + }, + { + "case_id": "BQ-002", + "raw_question": "Покажи риски по НДС и по закрытию", + "validation_passed": true, + "message_in_scope": true, + "scope_confidence": "high", + "contains_multiple_tasks": false, + "fragments_total": 1, + "in_scope_fragments": 1, + "out_of_scope_fragments": 0, + "unclear_fragments": 0, + "fallback_type": "none", + "predicted_route_status": "routed", + "expected_route_status": null, + "predicted_no_route_reason": null, + "expected_no_route_reason": null, + "predicted_clarification_required": false, + "expected_clarification_required": null, + "executable_with_soft_assumptions_fragments": 1, + "trace_id": "qsdm7lX8NXx1-C", + "request_count_for_case": 0 + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/eval-YIS0RgjMsg.report.json b/llm_normalizer/data/eval_cases/eval-YIS0RgjMsg.report.json new file mode 100644 index 0000000..92f8905 --- /dev/null +++ b/llm_normalizer/data/eval_cases/eval-YIS0RgjMsg.report.json @@ -0,0 +1,112 @@ +{ + "run_id": "eval-YIS0RgjMsg", + "timestamp": "2026-04-09T18:01:58.499Z", + "mode": "single-pass-strict", + "use_mock": true, + "prompt_version": "normalizer_v2_0_2", + "schema_version": "v2_0_2", + "dataset": { + "source": "inline_raw_questions", + "file": null, + "raw_questions_count": 2 + }, + "cases_total": 2, + "metrics": { + "schema_validation_pass_rate": 100, + "scope_detection_accuracy": null, + "scope_in_scope_rate": 100, + "multi_intent_detected_rate": 0, + "clarification_required_rate": 0, + "avg_fragments_per_message": 1, + "out_of_scope_fragment_rate": 0, + "routed_fragment_rate": 100, + "no_route_fragment_rate": 0, + "route_resolution_accuracy": null, + "no_route_precision": null, + "false_no_route_rate": null, + "execution_state_consistency_rate": 100, + "executable_with_soft_assumptions_rate": 100, + "soft_assumption_used_fragment_rate": 100, + "clarification_precision": null, + "clarification_recall": null, + "false_clarification_rate": null + }, + "budget": { + "requests_total": 0, + "retries_used": 0 + }, + "clarification_eval": { + "labeled_cases": 0, + "true_positive": 0, + "false_positive": 0, + "false_negative": 0 + }, + "route_eval": { + "labeled_cases": 0, + "correct_cases": 0, + "expected_routed_cases": 0, + "no_route_true_positive": 0, + "no_route_false_positive": 0 + }, + "scope_eval": { + "labeled_cases": 0, + "correct_cases": 0 + }, + "execution_state_eval": { + "checks_total": 2, + "checks_passed": 2 + }, + "route_distribution": { + "store_feature_risk": 1, + "hybrid_store_plus_live": 1 + }, + "fallback_distribution": { + "none": 2 + }, + "results": [ + { + "case_id": "BQ-001", + "raw_question": "Проверь счет 60 за июнь 2020", + "validation_passed": true, + "message_in_scope": true, + "scope_confidence": "high", + "contains_multiple_tasks": false, + "fragments_total": 1, + "in_scope_fragments": 1, + "out_of_scope_fragments": 0, + "unclear_fragments": 0, + "fallback_type": "none", + "predicted_route_status": "routed", + "expected_route_status": null, + "predicted_no_route_reason": null, + "expected_no_route_reason": null, + "predicted_clarification_required": false, + "expected_clarification_required": null, + "executable_with_soft_assumptions_fragments": 1, + "trace_id": "nl9np-2vh2tA1J", + "request_count_for_case": 0 + }, + { + "case_id": "BQ-002", + "raw_question": "Покажи риски по счету 97", + "validation_passed": true, + "message_in_scope": true, + "scope_confidence": "high", + "contains_multiple_tasks": false, + "fragments_total": 1, + "in_scope_fragments": 1, + "out_of_scope_fragments": 0, + "unclear_fragments": 0, + "fallback_type": "none", + "predicted_route_status": "routed", + "expected_route_status": null, + "predicted_no_route_reason": null, + "expected_no_route_reason": null, + "predicted_clarification_required": false, + "expected_clarification_required": null, + "executable_with_soft_assumptions_fragments": 1, + "trace_id": "e6AdKc4cEUNvoK", + "request_count_for_case": 0 + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/eval-snt_txRT-J.report.json b/llm_normalizer/data/eval_cases/eval-snt_txRT-J.report.json new file mode 100644 index 0000000..8e9c931 --- /dev/null +++ b/llm_normalizer/data/eval_cases/eval-snt_txRT-J.report.json @@ -0,0 +1,137 @@ +{ + "run_id": "eval-snt_txRT-J", + "timestamp": "2026-04-09T18:01:39.193Z", + "mode": "single-pass-strict", + "use_mock": true, + "prompt_version": "normalizer_v2_0_2", + "schema_version": "v2_0_2", + "dataset": { + "source": "inline_raw_questions", + "file": null, + "raw_questions_count": 3 + }, + "cases_total": 3, + "metrics": { + "schema_validation_pass_rate": 100, + "scope_detection_accuracy": null, + "scope_in_scope_rate": 33.33, + "multi_intent_detected_rate": 0, + "clarification_required_rate": 0, + "avg_fragments_per_message": 1, + "out_of_scope_fragment_rate": 33.33, + "routed_fragment_rate": 66.67, + "no_route_fragment_rate": 33.33, + "route_resolution_accuracy": null, + "no_route_precision": null, + "false_no_route_rate": null, + "execution_state_consistency_rate": 66.67, + "executable_with_soft_assumptions_rate": 100, + "soft_assumption_used_fragment_rate": 100, + "clarification_precision": null, + "clarification_recall": null, + "false_clarification_rate": null + }, + "budget": { + "requests_total": 0, + "retries_used": 0 + }, + "clarification_eval": { + "labeled_cases": 0, + "true_positive": 0, + "false_positive": 0, + "false_negative": 0 + }, + "route_eval": { + "labeled_cases": 0, + "correct_cases": 0, + "expected_routed_cases": 0, + "no_route_true_positive": 0, + "no_route_false_positive": 0 + }, + "scope_eval": { + "labeled_cases": 0, + "correct_cases": 0 + }, + "execution_state_eval": { + "checks_total": 3, + "checks_passed": 2 + }, + "route_distribution": { + "hybrid_store_plus_live": 1, + "no_route": 1, + "batch_refresh_then_store": 1 + }, + "fallback_distribution": { + "none": 1, + "out_of_scope": 1, + "clarification": 1 + }, + "results": [ + { + "case_id": "BQ-001", + "raw_question": "Проверь хвосты по поставщикам и разложи цепочку", + "validation_passed": true, + "message_in_scope": true, + "scope_confidence": "high", + "contains_multiple_tasks": false, + "fragments_total": 1, + "in_scope_fragments": 1, + "out_of_scope_fragments": 0, + "unclear_fragments": 0, + "fallback_type": "none", + "predicted_route_status": "routed", + "expected_route_status": null, + "predicted_no_route_reason": null, + "expected_no_route_reason": null, + "predicted_clarification_required": false, + "expected_clarification_required": null, + "executable_with_soft_assumptions_fragments": 1, + "trace_id": "LcMJHaAgya9hgV", + "request_count_for_case": 0 + }, + { + "case_id": "BQ-002", + "raw_question": "Как вообще по ФСБУ", + "validation_passed": true, + "message_in_scope": false, + "scope_confidence": "low", + "contains_multiple_tasks": false, + "fragments_total": 1, + "in_scope_fragments": 0, + "out_of_scope_fragments": 1, + "unclear_fragments": 0, + "fallback_type": "out_of_scope", + "predicted_route_status": "no_route", + "expected_route_status": null, + "predicted_no_route_reason": "out_of_scope", + "expected_no_route_reason": null, + "predicted_clarification_required": false, + "expected_clarification_required": null, + "executable_with_soft_assumptions_fragments": 0, + "trace_id": "DB2D2zvB92-2b9", + "request_count_for_case": 0 + }, + { + "case_id": "BQ-003", + "raw_question": "Покажи топ рисков за июнь 2020", + "validation_passed": true, + "message_in_scope": false, + "scope_confidence": "low", + "contains_multiple_tasks": false, + "fragments_total": 1, + "in_scope_fragments": 0, + "out_of_scope_fragments": 0, + "unclear_fragments": 1, + "fallback_type": "clarification", + "predicted_route_status": "routed", + "expected_route_status": null, + "predicted_no_route_reason": null, + "expected_no_route_reason": null, + "predicted_clarification_required": false, + "expected_clarification_required": null, + "executable_with_soft_assumptions_fragments": 0, + "trace_id": "2kWFwub_GELA7S", + "request_count_for_case": 0 + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/frontend/dist/assets/index-Cbn_mHUl.js b/llm_normalizer/frontend/dist/assets/index-Cbn_mHUl.js deleted file mode 100644 index 8b54bb7..0000000 --- a/llm_normalizer/frontend/dist/assets/index-Cbn_mHUl.js +++ /dev/null @@ -1,13 +0,0 @@ -(function(){const m=document.createElement("link").relList;if(m&&m.supports&&m.supports("modulepreload"))return;for(const P of document.querySelectorAll('link[rel="modulepreload"]'))O(P);new MutationObserver(P=>{for(const F of P)if(F.type==="childList")for(const Q of F.addedNodes)Q.tagName==="LINK"&&Q.rel==="modulepreload"&&O(Q)}).observe(document,{childList:!0,subtree:!0});function p(P){const F={};return P.integrity&&(F.integrity=P.integrity),P.referrerPolicy&&(F.referrerPolicy=P.referrerPolicy),P.crossOrigin==="use-credentials"?F.credentials="include":P.crossOrigin==="anonymous"?F.credentials="omit":F.credentials="same-origin",F}function O(P){if(P.ep)return;P.ep=!0;const F=p(P);fetch(P.href,F)}})();function gc(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}var gi={exports:{}},xl={},yi={exports:{}},oe={};var Yu;function nf(){if(Yu)return oe;Yu=1;var a=Symbol.for("react.element"),m=Symbol.for("react.portal"),p=Symbol.for("react.fragment"),O=Symbol.for("react.strict_mode"),P=Symbol.for("react.profiler"),F=Symbol.for("react.provider"),Q=Symbol.for("react.context"),Z=Symbol.for("react.forward_ref"),U=Symbol.for("react.suspense"),z=Symbol.for("react.memo"),ee=Symbol.for("react.lazy"),C=Symbol.iterator;function H(h){return h===null||typeof h!="object"?null:(h=C&&h[C]||h["@@iterator"],typeof h=="function"?h:null)}var pe={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Te=Object.assign,te={};function re(h,S,q){this.props=h,this.context=S,this.refs=te,this.updater=q||pe}re.prototype.isReactComponent={},re.prototype.setState=function(h,S){if(typeof h!="object"&&typeof h!="function"&&h!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,h,S,"setState")},re.prototype.forceUpdate=function(h){this.updater.enqueueForceUpdate(this,h,"forceUpdate")};function Se(){}Se.prototype=re.prototype;function me(h,S,q){this.props=h,this.context=S,this.refs=te,this.updater=q||pe}var Me=me.prototype=new Se;Me.constructor=me,Te(Me,re.prototype),Me.isPureReactComponent=!0;var Ne=Array.isArray,Ae=Object.prototype.hasOwnProperty,Fe={current:null},Ue={key:!0,ref:!0,__self:!0,__source:!0};function Ke(h,S,q){var b,ne={},le=null,de=null;if(S!=null)for(b in S.ref!==void 0&&(de=S.ref),S.key!==void 0&&(le=""+S.key),S)Ae.call(S,b)&&!Ue.hasOwnProperty(b)&&(ne[b]=S[b]);var ue=arguments.length-2;if(ue===1)ne.children=q;else if(1>>1,S=T[h];if(0>>1;hP(ne,M))leP(de,ne)?(T[h]=de,T[le]=M,h=le):(T[h]=ne,T[b]=M,h=b);else if(leP(de,M))T[h]=de,T[le]=M,h=le;else break e}}return W}function P(T,W){var M=T.sortIndex-W.sortIndex;return M!==0?M:T.id-W.id}if(typeof performance=="object"&&typeof performance.now=="function"){var F=performance;a.unstable_now=function(){return F.now()}}else{var Q=Date,Z=Q.now();a.unstable_now=function(){return Q.now()-Z}}var U=[],z=[],ee=1,C=null,H=3,pe=!1,Te=!1,te=!1,re=typeof setTimeout=="function"?setTimeout:null,Se=typeof clearTimeout=="function"?clearTimeout:null,me=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function Me(T){for(var W=p(z);W!==null;){if(W.callback===null)O(z);else if(W.startTime<=T)O(z),W.sortIndex=W.expirationTime,m(U,W);else break;W=p(z)}}function Ne(T){if(te=!1,Me(T),!Te)if(p(U)!==null)Te=!0,he(Ae);else{var W=p(z);W!==null&&ye(Ne,W.startTime-T)}}function Ae(T,W){Te=!1,te&&(te=!1,Se(Ke),Ke=-1),pe=!0;var M=H;try{for(Me(W),C=p(U);C!==null&&(!(C.expirationTime>W)||T&&!xe());){var h=C.callback;if(typeof h=="function"){C.callback=null,H=C.priorityLevel;var S=h(C.expirationTime<=W);W=a.unstable_now(),typeof S=="function"?C.callback=S:C===p(U)&&O(U),Me(W)}else O(U);C=p(U)}if(C!==null)var q=!0;else{var b=p(z);b!==null&&ye(Ne,b.startTime-W),q=!1}return q}finally{C=null,H=M,pe=!1}}var Fe=!1,Ue=null,Ke=-1,st=5,be=-1;function xe(){return!(a.unstable_now()-beT||125h?(T.sortIndex=M,m(z,T),p(U)===null&&T===p(z)&&(te?(Se(Ke),Ke=-1):te=!0,ye(Ne,M-h))):(T.sortIndex=S,m(U,T),Te||pe||(Te=!0,he(Ae))),T},a.unstable_shouldYield=xe,a.unstable_wrapCallback=function(T){var W=H;return function(){var M=H;H=W;try{return T.apply(this,arguments)}finally{H=M}}}})(Si)),Si}var ec;function af(){return ec||(ec=1,_i.exports=sf()),_i.exports}var tc;function uf(){if(tc)return _t;tc=1;var a=Ci(),m=af();function p(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),U=Object.prototype.hasOwnProperty,z=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,ee={},C={};function H(e){return U.call(C,e)?!0:U.call(ee,e)?!1:z.test(e)?C[e]=!0:(ee[e]=!0,!1)}function pe(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Te(e,t,n,r){if(t===null||typeof t>"u"||pe(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function te(e,t,n,r,l,o,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=o,this.removeEmptyString=i}var re={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){re[e]=new te(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];re[t]=new te(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){re[e]=new te(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){re[e]=new te(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){re[e]=new te(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){re[e]=new te(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){re[e]=new te(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){re[e]=new te(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){re[e]=new te(e,5,!1,e.toLowerCase(),null,!1,!1)});var Se=/[\-:]([a-z])/g;function me(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Se,me);re[t]=new te(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Se,me);re[t]=new te(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Se,me);re[t]=new te(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){re[e]=new te(e,1,!1,e.toLowerCase(),null,!1,!1)}),re.xlinkHref=new te("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){re[e]=new te(e,1,!1,e.toLowerCase(),null,!0,!0)});function Me(e,t,n,r){var l=re.hasOwnProperty(t)?re[t]:null;(l!==null?l.type!==0:r||!(2c||l[i]!==o[c]){var d=` -`+l[i].replace(" at new "," at ");return e.displayName&&d.includes("")&&(d=d.replace("",e.displayName)),d}while(1<=i&&0<=c);break}}}finally{q=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?S(e):""}function ne(e){switch(e.tag){case 5:return S(e.type);case 16:return S("Lazy");case 13:return S("Suspense");case 19:return S("SuspenseList");case 0:case 2:case 15:return e=b(e.type,!1),e;case 11:return e=b(e.type.render,!1),e;case 1:return e=b(e.type,!0),e;default:return""}}function le(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ue:return"Fragment";case Fe:return"Portal";case st:return"Profiler";case Ke:return"StrictMode";case Ee:return"Suspense";case Ve:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case xe:return(e.displayName||"Context")+".Consumer";case be:return(e._context.displayName||"Context")+".Provider";case it:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case X:return t=e.displayName||null,t!==null?t:le(e.type)||"Memo";case he:t=e._payload,e=e._init;try{return le(e(t))}catch{}}return null}function de(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return le(t);case 8:return t===Ke?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function ue(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function se(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function ve(e){var t=se(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(i){r=""+i,o.call(this,i)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(i){r=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Et(e){e._valueTracker||(e._valueTracker=ve(e))}function on(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=se(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function St(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Kt(e,t){var n=t.checked;return M({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function xn(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=ue(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function at(e,t){t=t.checked,t!=null&&Me(e,"checked",t,!1)}function ae(e,t){at(e,t);var n=ue(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Gt(e,t.type,n):t.hasOwnProperty("defaultValue")&&Gt(e,t.type,ue(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function et(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Gt(e,t,n){(t!=="number"||St(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var ut=Array.isArray;function Pt(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=dt.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Ge(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Yt={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},At=["Webkit","ms","Moz","O"];Object.keys(Yt).forEach(function(e){At.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Yt[t]=Yt[e]})});function mt(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Yt.hasOwnProperty(e)&&Yt[e]?(""+t).trim():t+"px"}function It(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=mt(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var nr=M({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function wt(e,t){if(t){if(nr[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(p(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(p(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(p(61))}if(t.style!=null&&typeof t.style!="object")throw Error(p(62))}}function Sn(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Jt=null;function an(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var u=null,_=null,V=null;function je(e){if(e=ll(e)){if(typeof u!="function")throw Error(p(280));var t=e.stateNode;t&&(t=Kl(t),u(e.stateNode,e.type,t))}}function Ft(e){_?V?V.push(e):V=[e]:_=e}function Ut(){if(_){var e=_,t=V;if(V=_=null,je(e),t)for(e=0;e>>=0,e===0?32:31-(yc(e)/xc|0)|0}var Pl=64,Rl=4194304;function $r(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Tl(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,o=e.pingedLanes,i=n&268435455;if(i!==0){var c=i&~l;c!==0?r=$r(c):(o&=i,o!==0&&(r=$r(o)))}else i=n&~l,i!==0?r=$r(i):o!==0&&(r=$r(o));if(r===0)return 0;if(t!==0&&t!==r&&(t&l)===0&&(l=r&-r,o=t&-t,l>=o||l===16&&(o&4194240)!==0))return t;if((r&4)!==0&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Br(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-ie(t),e[t]=n}function jc(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Yr),Hi=" ",Vi=!1;function Qi(e,t){switch(e){case"keyup":return Xc.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Wi(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var cr=!1;function bc(e,t){switch(e){case"compositionend":return Wi(t);case"keypress":return t.which!==32?null:(Vi=!0,Hi);case"textInput":return e=t.data,e===Hi&&Vi?null:e;default:return null}}function ed(e,t){if(cr)return e==="compositionend"||!Xo&&Qi(e,t)?(e=Ai(),Ol=Wo=Pn=null,cr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Zi(n)}}function ea(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?ea(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ta(){for(var e=window,t=St();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=St(e.document)}return t}function es(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function ud(e){var t=ta(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&ea(n.ownerDocument.documentElement,n)){if(r!==null&&es(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,o=Math.min(r.start,l);r=r.end===void 0?o:Math.min(r.end,l),!e.extend&&o>r&&(l=r,r=o,o=l),l=bi(n,o);var i=bi(n,r);l&&i&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==i.node||e.focusOffset!==i.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),o>r?(e.addRange(t),e.extend(i.node,i.offset)):(t.setEnd(i.node,i.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,dr=null,ts=null,br=null,ns=!1;function na(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ns||dr==null||dr!==St(r)||(r=dr,"selectionStart"in r&&es(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),br&&Zr(br,r)||(br=r,r=Vl(ts,"onSelect"),0vr||(e.current=ms[vr],ms[vr]=null,vr--)}function we(e,t){vr++,ms[vr]=e.current,e.current=t}var Ln={},tt=Mn(Ln),ht=Mn(!1),Wn=Ln;function gr(e,t){var n=e.type.contextTypes;if(!n)return Ln;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},o;for(o in n)l[o]=t[o];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function vt(e){return e=e.childContextTypes,e!=null}function Gl(){Ce(ht),Ce(tt)}function ga(e,t,n){if(tt.current!==Ln)throw Error(p(168));we(tt,t),we(ht,n)}function ya(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(p(108,de(e)||"Unknown",l));return M({},n,r)}function ql(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Ln,Wn=tt.current,we(tt,e),we(ht,ht.current),!0}function xa(e,t,n){var r=e.stateNode;if(!r)throw Error(p(169));n?(e=ya(e,t,Wn),r.__reactInternalMemoizedMergedChildContext=e,Ce(ht),Ce(tt),we(tt,e)):Ce(ht),we(ht,n)}var dn=null,Yl=!1,hs=!1;function _a(e){dn===null?dn=[e]:dn.push(e)}function Sd(e){Yl=!0,_a(e)}function zn(){if(!hs&&dn!==null){hs=!0;var e=0,t=ge;try{var n=dn;for(ge=1;e>=i,l-=i,fn=1<<32-ie(t)+l|n<J?(Je=G,G=null):Je=G.sibling;var fe=w(v,G,g[J],R);if(fe===null){G===null&&(G=Je);break}e&&G&&fe.alternate===null&&t(v,G),f=o(fe,f,J),K===null?B=fe:K.sibling=fe,K=fe,G=Je}if(J===g.length)return n(v,G),Re&&Gn(v,J),B;if(G===null){for(;JJ?(Je=G,G=null):Je=G.sibling;var Hn=w(v,G,fe.value,R);if(Hn===null){G===null&&(G=Je);break}e&&G&&Hn.alternate===null&&t(v,G),f=o(Hn,f,J),K===null?B=Hn:K.sibling=Hn,K=Hn,G=Je}if(fe.done)return n(v,G),Re&&Gn(v,J),B;if(G===null){for(;!fe.done;J++,fe=g.next())fe=N(v,fe.value,R),fe!==null&&(f=o(fe,f,J),K===null?B=fe:K.sibling=fe,K=fe);return Re&&Gn(v,J),B}for(G=r(v,G);!fe.done;J++,fe=g.next())fe=D(G,v,J,fe.value,R),fe!==null&&(e&&fe.alternate!==null&&G.delete(fe.key===null?J:fe.key),f=o(fe,f,J),K===null?B=fe:K.sibling=fe,K=fe);return e&&G.forEach(function(tf){return t(v,tf)}),Re&&Gn(v,J),B}function $e(v,f,g,R){if(typeof g=="object"&&g!==null&&g.type===Ue&&g.key===null&&(g=g.props.children),typeof g=="object"&&g!==null){switch(g.$$typeof){case Ae:e:{for(var B=g.key,K=f;K!==null;){if(K.key===B){if(B=g.type,B===Ue){if(K.tag===7){n(v,K.sibling),f=l(K,g.props.children),f.return=v,v=f;break e}}else if(K.elementType===B||typeof B=="object"&&B!==null&&B.$$typeof===he&&Na(B)===K.type){n(v,K.sibling),f=l(K,g.props),f.ref=ol(v,K,g),f.return=v,v=f;break e}n(v,K);break}else t(v,K);K=K.sibling}g.type===Ue?(f=tr(g.props.children,v.mode,R,g.key),f.return=v,v=f):(R=ko(g.type,g.key,g.props,null,v.mode,R),R.ref=ol(v,f,g),R.return=v,v=R)}return i(v);case Fe:e:{for(K=g.key;f!==null;){if(f.key===K)if(f.tag===4&&f.stateNode.containerInfo===g.containerInfo&&f.stateNode.implementation===g.implementation){n(v,f.sibling),f=l(f,g.children||[]),f.return=v,v=f;break e}else{n(v,f);break}else t(v,f);f=f.sibling}f=fi(g,v.mode,R),f.return=v,v=f}return i(v);case he:return K=g._init,$e(v,f,K(g._payload),R)}if(ut(g))return I(v,f,g,R);if(W(g))return $(v,f,g,R);bl(v,g)}return typeof g=="string"&&g!==""||typeof g=="number"?(g=""+g,f!==null&&f.tag===6?(n(v,f.sibling),f=l(f,g),f.return=v,v=f):(n(v,f),f=di(g,v.mode,R),f.return=v,v=f),i(v)):n(v,f)}return $e}var Sr=Ea(!0),Pa=Ea(!1),eo=Mn(null),to=null,wr=null,Ss=null;function ws(){Ss=wr=to=null}function js(e){var t=eo.current;Ce(eo),e._currentValue=t}function ks(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function jr(e,t){to=e,Ss=wr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(gt=!0),e.firstContext=null)}function Mt(e){var t=e._currentValue;if(Ss!==e)if(e={context:e,memoizedValue:t,next:null},wr===null){if(to===null)throw Error(p(308));wr=e,to.dependencies={lanes:0,firstContext:e}}else wr=wr.next=e;return t}var qn=null;function Cs(e){qn===null?qn=[e]:qn.push(e)}function Ra(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Cs(t)):(n.next=l.next,l.next=n),t.interleaved=n,mn(e,r)}function mn(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Dn=!1;function Ns(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ta(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function hn(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function On(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,(ce&2)!==0){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,mn(e,n)}return l=r.interleaved,l===null?(t.next=t,Cs(r)):(t.next=l.next,l.next=t),r.interleaved=t,mn(e,n)}function no(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$o(e,n)}}function Ma(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,o=null;if(n=n.firstBaseUpdate,n!==null){do{var i={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};o===null?l=o=i:o=o.next=i,n=n.next}while(n!==null);o===null?l=o=t:o=o.next=t}else l=o=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:o,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ro(e,t,n,r){var l=e.updateQueue;Dn=!1;var o=l.firstBaseUpdate,i=l.lastBaseUpdate,c=l.shared.pending;if(c!==null){l.shared.pending=null;var d=c,x=d.next;d.next=null,i===null?o=x:i.next=x,i=d;var k=e.alternate;k!==null&&(k=k.updateQueue,c=k.lastBaseUpdate,c!==i&&(c===null?k.firstBaseUpdate=x:c.next=x,k.lastBaseUpdate=d))}if(o!==null){var N=l.baseState;i=0,k=x=d=null,c=o;do{var w=c.lane,D=c.eventTime;if((r&w)===w){k!==null&&(k=k.next={eventTime:D,lane:0,tag:c.tag,payload:c.payload,callback:c.callback,next:null});e:{var I=e,$=c;switch(w=t,D=n,$.tag){case 1:if(I=$.payload,typeof I=="function"){N=I.call(D,N,w);break e}N=I;break e;case 3:I.flags=I.flags&-65537|128;case 0:if(I=$.payload,w=typeof I=="function"?I.call(D,N,w):I,w==null)break e;N=M({},N,w);break e;case 2:Dn=!0}}c.callback!==null&&c.lane!==0&&(e.flags|=64,w=l.effects,w===null?l.effects=[c]:w.push(c))}else D={eventTime:D,lane:w,tag:c.tag,payload:c.payload,callback:c.callback,next:null},k===null?(x=k=D,d=N):k=k.next=D,i|=w;if(c=c.next,c===null){if(c=l.shared.pending,c===null)break;w=c,c=w.next,w.next=null,l.lastBaseUpdate=w,l.shared.pending=null}}while(!0);if(k===null&&(d=N),l.baseState=d,l.firstBaseUpdate=x,l.lastBaseUpdate=k,t=l.shared.interleaved,t!==null){l=t;do i|=l.lane,l=l.next;while(l!==t)}else o===null&&(l.shared.lanes=0);Xn|=i,e.lanes=i,e.memoizedState=N}}function La(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Ms.transition;Ms.transition={};try{e(!1),t()}finally{ge=n,Ms.transition=r}}function Xa(){return Lt().memoizedState}function Cd(e,t,n){var r=Un(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},Za(e))ba(t,n);else if(n=Ra(e,t,n,r),n!==null){var l=pt();Wt(n,e,r,l),eu(n,t,r)}}function Nd(e,t,n){var r=Un(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(Za(e))ba(t,l);else{var o=e.alternate;if(e.lanes===0&&(o===null||o.lanes===0)&&(o=t.lastRenderedReducer,o!==null))try{var i=t.lastRenderedState,c=o(i,n);if(l.hasEagerState=!0,l.eagerState=c,$t(c,i)){var d=t.interleaved;d===null?(l.next=l,Cs(t)):(l.next=d.next,d.next=l),t.interleaved=l;return}}catch{}n=Ra(e,t,l,r),n!==null&&(l=pt(),Wt(n,e,r,l),eu(n,t,r))}}function Za(e){var t=e.alternate;return e===De||t!==null&&t===De}function ba(e,t){ul=so=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function eu(e,t,n){if((n&4194240)!==0){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,$o(e,n)}}var uo={readContext:Mt,useCallback:nt,useContext:nt,useEffect:nt,useImperativeHandle:nt,useInsertionEffect:nt,useLayoutEffect:nt,useMemo:nt,useReducer:nt,useRef:nt,useState:nt,useDebugValue:nt,useDeferredValue:nt,useTransition:nt,useMutableSource:nt,useSyncExternalStore:nt,useId:nt,unstable_isNewReconciler:!1},Ed={readContext:Mt,useCallback:function(e,t){return nn().memoizedState=[e,t===void 0?null:t],e},useContext:Mt,useEffect:Va,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,io(4194308,4,Ka.bind(null,t,e),n)},useLayoutEffect:function(e,t){return io(4194308,4,e,t)},useInsertionEffect:function(e,t){return io(4,2,e,t)},useMemo:function(e,t){var n=nn();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=nn();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Cd.bind(null,De,e),[r.memoizedState,e]},useRef:function(e){var t=nn();return e={current:e},t.memoizedState=e},useState:Ba,useDebugValue:Fs,useDeferredValue:function(e){return nn().memoizedState=e},useTransition:function(){var e=Ba(!1),t=e[0];return e=kd.bind(null,e[1]),nn().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=De,l=nn();if(Re){if(n===void 0)throw Error(p(407));n=n()}else{if(n=t(),Ye===null)throw Error(p(349));(Jn&30)!==0||Aa(r,t,n)}l.memoizedState=n;var o={value:n,getSnapshot:t};return l.queue=o,Va(Fa.bind(null,r,o,e),[e]),r.flags|=2048,fl(9,Ia.bind(null,r,o,n,t),void 0,null),n},useId:function(){var e=nn(),t=Ye.identifierPrefix;if(Re){var n=pn,r=fn;n=(r&~(1<<32-ie(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=cl++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=i.createElement(n,{is:r.is}):(e=i.createElement(n),n==="select"&&(i=e,r.multiple?i.multiple=!0:r.size&&(i.size=r.size))):e=i.createElementNS(e,n),e[en]=t,e[rl]=r,_u(e,t,!1,!1),t.stateNode=e;e:{switch(i=Sn(n,r),n){case"dialog":ke("cancel",e),ke("close",e),l=r;break;case"iframe":case"object":case"embed":ke("load",e),l=r;break;case"video":case"audio":for(l=0;lPr&&(t.flags|=128,r=!0,pl(o,!1),t.lanes=4194304)}else{if(!r)if(e=lo(i),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),pl(o,!0),o.tail===null&&o.tailMode==="hidden"&&!i.alternate&&!Re)return rt(t),null}else 2*Le()-o.renderingStartTime>Pr&&n!==1073741824&&(t.flags|=128,r=!0,pl(o,!1),t.lanes=4194304);o.isBackwards?(i.sibling=t.child,t.child=i):(n=o.last,n!==null?n.sibling=i:t.child=i,o.last=i)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=Le(),t.sibling=null,n=ze.current,we(ze,r?n&1|2:n&1),t):(rt(t),null);case 22:case 23:return ai(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&(t.mode&1)!==0?(Nt&1073741824)!==0&&(rt(t),t.subtreeFlags&6&&(t.flags|=8192)):rt(t),null;case 24:return null;case 25:return null}throw Error(p(156,t.tag))}function Od(e,t){switch(gs(t),t.tag){case 1:return vt(t.type)&&Gl(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return kr(),Ce(ht),Ce(tt),Ts(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 5:return Ps(t),null;case 13:if(Ce(ze),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(p(340));_r()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Ce(ze),null;case 4:return kr(),null;case 10:return js(t.type._context),null;case 22:case 23:return ai(),null;case 24:return null;default:return null}}var mo=!1,lt=!1,Ad=typeof WeakSet=="function"?WeakSet:Set,A=null;function Nr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Ie(e,t,r)}else n.current=null}function Js(e,t,n){try{n()}catch(r){Ie(e,t,r)}}var ju=!1;function Id(e,t){if(as=zl,e=ta(),es(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var i=0,c=-1,d=-1,x=0,k=0,N=e,w=null;t:for(;;){for(var D;N!==n||l!==0&&N.nodeType!==3||(c=i+l),N!==o||r!==0&&N.nodeType!==3||(d=i+r),N.nodeType===3&&(i+=N.nodeValue.length),(D=N.firstChild)!==null;)w=N,N=D;for(;;){if(N===e)break t;if(w===n&&++x===l&&(c=i),w===o&&++k===r&&(d=i),(D=N.nextSibling)!==null)break;N=w,w=N.parentNode}N=D}n=c===-1||d===-1?null:{start:c,end:d}}else n=null}n=n||{start:0,end:0}}else n=null;for(us={focusedElem:e,selectionRange:n},zl=!1,A=t;A!==null;)if(t=A,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,A=e;else for(;A!==null;){t=A;try{var I=t.alternate;if((t.flags&1024)!==0)switch(t.tag){case 0:case 11:case 15:break;case 1:if(I!==null){var $=I.memoizedProps,$e=I.memoizedState,v=t.stateNode,f=v.getSnapshotBeforeUpdate(t.elementType===t.type?$:Ht(t.type,$),$e);v.__reactInternalSnapshotBeforeUpdate=f}break;case 3:var g=t.stateNode.containerInfo;g.nodeType===1?g.textContent="":g.nodeType===9&&g.documentElement&&g.removeChild(g.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(p(163))}}catch(R){Ie(t,t.return,R)}if(e=t.sibling,e!==null){e.return=t.return,A=e;break}A=t.return}return I=ju,ju=!1,I}function ml(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Js(t,n,o)}l=l.next}while(l!==r)}}function ho(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function Xs(e){var t=e.ref;if(t!==null){var n=e.stateNode;e.tag,e=n,typeof t=="function"?t(e):t.current=e}}function ku(e){var t=e.alternate;t!==null&&(e.alternate=null,ku(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[en],delete t[rl],delete t[ps],delete t[xd],delete t[_d])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Cu(e){return e.tag===5||e.tag===3||e.tag===4}function Nu(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Cu(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Zs(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Wl));else if(r!==4&&(e=e.child,e!==null))for(Zs(e,t,n),e=e.sibling;e!==null;)Zs(e,t,n),e=e.sibling}function bs(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(bs(e,t,n),e=e.sibling;e!==null;)bs(e,t,n),e=e.sibling}var Xe=null,Vt=!1;function An(e,t,n){for(n=n.child;n!==null;)Eu(e,t,n),n=n.sibling}function Eu(e,t,n){if(L&&typeof L.onCommitFiberUnmount=="function")try{L.onCommitFiberUnmount(y,n)}catch{}switch(n.tag){case 5:lt||Nr(n,t);case 6:var r=Xe,l=Vt;Xe=null,An(e,t,n),Xe=r,Vt=l,Xe!==null&&(Vt?(e=Xe,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Xe.removeChild(n.stateNode));break;case 18:Xe!==null&&(Vt?(e=Xe,n=n.stateNode,e.nodeType===8?fs(e.parentNode,n):e.nodeType===1&&fs(e,n),Kr(e)):fs(Xe,n.stateNode));break;case 4:r=Xe,l=Vt,Xe=n.stateNode.containerInfo,Vt=!0,An(e,t,n),Xe=r,Vt=l;break;case 0:case 11:case 14:case 15:if(!lt&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,i=o.destroy;o=o.tag,i!==void 0&&((o&2)!==0||(o&4)!==0)&&Js(n,t,i),l=l.next}while(l!==r)}An(e,t,n);break;case 1:if(!lt&&(Nr(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(c){Ie(n,t,c)}An(e,t,n);break;case 21:An(e,t,n);break;case 22:n.mode&1?(lt=(r=lt)||n.memoizedState!==null,An(e,t,n),lt=r):An(e,t,n);break;default:An(e,t,n)}}function Pu(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Ad),t.forEach(function(r){var l=Kd.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Qt(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=i),r&=~o}if(r=l,r=Le()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Ud(r/1960))-r,10e?16:e,Fn===null)var r=!1;else{if(e=Fn,Fn=null,_o=0,(ce&6)!==0)throw Error(p(331));var l=ce;for(ce|=4,A=e.current;A!==null;){var o=A,i=o.child;if((A.flags&16)!==0){var c=o.deletions;if(c!==null){for(var d=0;dLe()-ni?bn(e,0):ti|=n),xt(e,t)}function Bu(e,t){t===0&&((e.mode&1)===0?t=1:(t=Rl,Rl<<=1,(Rl&130023424)===0&&(Rl=4194304)));var n=pt();e=mn(e,t),e!==null&&(Br(e,t,n),xt(e,n))}function Wd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Bu(e,n)}function Kd(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(p(314))}r!==null&&r.delete(t),Bu(e,n)}var Hu;Hu=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ht.current)gt=!0;else{if((e.lanes&n)===0&&(t.flags&128)===0)return gt=!1,zd(e,t,n);gt=(e.flags&131072)!==0}else gt=!1,Re&&(t.flags&1048576)!==0&&Sa(t,Xl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;po(e,t),e=t.pendingProps;var l=gr(t,tt.current);jr(t,n),l=zs(null,t,r,e,l,n);var o=Ds();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,vt(r)?(o=!0,ql(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ns(t),l.updater=co,t.stateNode=l,l._reactInternals=t,$s(t,r,e,n),t=Qs(null,t,r,!0,o,n)):(t.tag=0,Re&&o&&vs(t),ft(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(po(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=qd(r),e=Ht(r,e),l){case 0:t=Vs(null,t,r,e,n);break e;case 1:t=mu(null,t,r,e,n);break e;case 11:t=uu(null,t,r,e,n);break e;case 14:t=cu(null,t,r,Ht(r.type,e),n);break e}throw Error(p(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ht(r,l),Vs(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ht(r,l),mu(e,t,r,l,n);case 3:e:{if(hu(t),e===null)throw Error(p(387));r=t.pendingProps,o=t.memoizedState,l=o.element,Ta(e,t),ro(t,r,null,n);var i=t.memoizedState;if(r=i.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:i.cache,pendingSuspenseBoundaries:i.pendingSuspenseBoundaries,transitions:i.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=Cr(Error(p(423)),t),t=vu(e,t,r,n,l);break e}else if(r!==l){l=Cr(Error(p(424)),t),t=vu(e,t,r,n,l);break e}else for(Ct=Tn(t.stateNode.containerInfo.firstChild),kt=t,Re=!0,Bt=null,n=Pa(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(_r(),r===l){t=vn(e,t,n);break e}ft(e,t,r,n)}t=t.child}return t;case 5:return za(t),e===null&&xs(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,i=l.children,cs(r,l)?i=null:o!==null&&cs(r,o)&&(t.flags|=32),pu(e,t),ft(e,t,i,n),t.child;case 6:return e===null&&xs(t),null;case 13:return gu(e,t,n);case 4:return Es(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Sr(t,null,r,n):ft(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ht(r,l),uu(e,t,r,l,n);case 7:return ft(e,t,t.pendingProps,n),t.child;case 8:return ft(e,t,t.pendingProps.children,n),t.child;case 12:return ft(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,i=l.value,we(eo,r._currentValue),r._currentValue=i,o!==null)if($t(o.value,i)){if(o.children===l.children&&!ht.current){t=vn(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var c=o.dependencies;if(c!==null){i=o.child;for(var d=c.firstContext;d!==null;){if(d.context===r){if(o.tag===1){d=hn(-1,n&-n),d.tag=2;var x=o.updateQueue;if(x!==null){x=x.shared;var k=x.pending;k===null?d.next=d:(d.next=k.next,k.next=d),x.pending=d}}o.lanes|=n,d=o.alternate,d!==null&&(d.lanes|=n),ks(o.return,n,t),c.lanes|=n;break}d=d.next}}else if(o.tag===10)i=o.type===t.type?null:o.child;else if(o.tag===18){if(i=o.return,i===null)throw Error(p(341));i.lanes|=n,c=i.alternate,c!==null&&(c.lanes|=n),ks(i,n,t),i=o.sibling}else i=o.child;if(i!==null)i.return=o;else for(i=o;i!==null;){if(i===t){i=null;break}if(o=i.sibling,o!==null){o.return=i.return,i=o;break}i=i.return}o=i}ft(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,jr(t,n),l=Mt(l),r=r(l),t.flags|=1,ft(e,t,r,n),t.child;case 14:return r=t.type,l=Ht(r,t.pendingProps),l=Ht(r.type,l),cu(e,t,r,l,n);case 15:return du(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ht(r,l),po(e,t),t.tag=1,vt(r)?(e=!0,ql(t)):e=!1,jr(t,n),nu(t,r,l),$s(t,r,l,n),Qs(null,t,r,!0,e,n);case 19:return xu(e,t,n);case 22:return fu(e,t,n)}throw Error(p(156,t.tag))};function Vu(e,t){return Ir(e,t)}function Gd(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Dt(e,t,n,r){return new Gd(e,t,n,r)}function ci(e){return e=e.prototype,!(!e||!e.isReactComponent)}function qd(e){if(typeof e=="function")return ci(e)?1:0;if(e!=null){if(e=e.$$typeof,e===it)return 11;if(e===X)return 14}return 2}function Bn(e,t){var n=e.alternate;return n===null?(n=Dt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function ko(e,t,n,r,l,o){var i=2;if(r=e,typeof e=="function")ci(e)&&(i=1);else if(typeof e=="string")i=5;else e:switch(e){case Ue:return tr(n.children,l,o,t);case Ke:i=8,l|=8;break;case st:return e=Dt(12,n,t,l|2),e.elementType=st,e.lanes=o,e;case Ee:return e=Dt(13,n,t,l),e.elementType=Ee,e.lanes=o,e;case Ve:return e=Dt(19,n,t,l),e.elementType=Ve,e.lanes=o,e;case ye:return Co(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case be:i=10;break e;case xe:i=9;break e;case it:i=11;break e;case X:i=14;break e;case he:i=16,r=null;break e}throw Error(p(130,e==null?e:typeof e,""))}return t=Dt(i,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function tr(e,t,n,r){return e=Dt(7,e,r,t),e.lanes=n,e}function Co(e,t,n,r){return e=Dt(22,e,r,t),e.elementType=ye,e.lanes=n,e.stateNode={isHidden:!1},e}function di(e,t,n){return e=Dt(6,e,null,t),e.lanes=n,e}function fi(e,t,n){return t=Dt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Yd(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Uo(0),this.expirationTimes=Uo(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Uo(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function pi(e,t,n,r,l,o,i,c,d){return e=new Yd(e,t,n,c,d),t===1?(t=1,o===!0&&(t|=8)):t=0,o=Dt(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ns(o),e}function Jd(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(a)}catch(m){console.error(m)}}return a(),xi.exports=uf(),xi.exports}var rc;function df(){if(rc)return Lo;rc=1;var a=cf();return Lo.createRoot=a.createRoot,Lo.hydrateRoot=a.hydrateRoot,Lo}var ff=df();const pf=gc(ff),mf="/api";async function Oe(a,m){const p=await fetch(`${mf}${a}`,{...m,headers:{"Content-Type":"application/json",...m?.headers??{}}}),O=await p.json();if(!p.ok){const P=O.error?.message??"Ошибка запроса";throw new Error(P)}return O}const Be={async listModels(a){return Oe("/llm/models",{method:"POST",body:JSON.stringify({llmProvider:a.llmProvider,apiKey:a.apiKey,model:a.model,baseUrl:a.baseUrl})})},async testConnection(a){return Oe("/llm/test-connection",{method:"POST",body:JSON.stringify({llmProvider:a.llmProvider,apiKey:a.apiKey,model:a.model,baseUrl:a.baseUrl})})},async normalize(a){return Oe("/normalize",{method:"POST",body:JSON.stringify({llmProvider:a.connection.llmProvider,apiKey:a.connection.apiKey,model:a.connection.model,baseUrl:a.connection.baseUrl,temperature:a.connection.temperature,maxOutputTokens:a.connection.maxOutputTokens,promptVersion:a.promptVersion,systemPrompt:a.prompts.systemPrompt,developerPrompt:a.prompts.developerPrompt,domainPrompt:a.prompts.domainPrompt,fewShotExamples:a.prompts.fewShotExamples,userQuestion:a.query.userQuestion,context:{period_hint:a.query.periodHint??"",business_context:a.query.businessContext??"",expected_route:a.query.expectedRoute??""},saveAsTestCase:!!a.saveAsTestCase,useMock:!!a.useMock})})},async loadHistory(){return Oe("/history")},async loadTrace(a){return Oe(`/history/${a}`)},async loadPresets(){return Oe("/presets")},async savePreset(a){return Oe("/presets/save",{method:"POST",body:JSON.stringify(a)})},async runEval(a){return Oe("/eval/run",{method:"POST",body:JSON.stringify({normalizeConfig:{llmProvider:a.connection.llmProvider,apiKey:a.connection.apiKey,model:a.connection.model,baseUrl:a.connection.baseUrl,temperature:a.connection.temperature,maxOutputTokens:a.connection.maxOutputTokens,promptVersion:a.promptVersion,systemPrompt:a.prompts.systemPrompt,developerPrompt:a.prompts.developerPrompt,domainPrompt:a.prompts.domainPrompt,fewShotExamples:a.prompts.fewShotExamples},caseIds:a.caseIds,useMock:!!a.useMock,mode:a.mode??"standard",caseSetFile:a.caseSetFile,rawQuestions:a.rawQuestions})})},async startRun(){return Oe("/accounting-agent/v1/runs/start",{method:"POST",body:JSON.stringify({initiator:"ndc_operator",source:"gui"})})},async finishRun(a){return Oe("/accounting-agent/v1/runs/finish",{method:"POST",body:JSON.stringify({runId:a,status:"DONE",source:"gui",reason:"Остановлено оператором из GUI"})})},async listRuns(){return Oe("/accounting-agent/v1/runs")},async listResults(){return Oe("/accounting-agent/v1/results")},async runTrace(a){return Oe(`/accounting-agent/v1/trace/run/${a}`)},async sendAssistantMessage(a){return Oe("/assistant/message",{method:"POST",body:JSON.stringify({session_id:a.sessionId??"",mode:"assistant",message:a.userMessage,user_message:a.userMessage,llmProvider:a.connection.llmProvider,apiKey:a.connection.apiKey,model:a.connection.model,baseUrl:a.connection.baseUrl,temperature:a.connection.temperature,maxOutputTokens:a.connection.maxOutputTokens,promptVersion:a.promptVersion??"address_query_runtime_v1",systemPrompt:a.prompts.systemPrompt,developerPrompt:a.prompts.developerPrompt,domainPrompt:a.prompts.domainPrompt,fewShotExamples:a.prompts.fewShotExamples,context:{period_hint:a.context?.periodHint??"",business_context:a.context?.businessContext??""},useMock:!!a.useMock})})},async loadAssistantSession(a){return Oe(`/assistant/session/${a}`)},async loadAutoRunsHistory(a){const m=new URLSearchParams;a?.from&&m.set("from",a.from),a?.to&&m.set("to",a.to),a?.target&&m.set("target",a.target),a?.mode&&m.set("mode",a.mode),a?.use_mock&&m.set("use_mock",a.use_mock),a?.prompt_contains&&m.set("prompt_contains",a.prompt_contains),typeof a?.limit=="number"&&m.set("limit",String(a.limit)),typeof a?.scan_limit=="number"&&m.set("scan_limit",String(a.scan_limit));const p=m.toString();return Oe(`/autoruns/history${p?`?${p}`:""}`)},async loadAutoRunDetail(a){return Oe(`/autoruns/history/${encodeURIComponent(a)}`)},async loadAutoRunCaseDialog(a,m){return Oe(`/autoruns/history/${encodeURIComponent(a)}/case/${encodeURIComponent(m)}/dialog`)},async loadAutoRunAnnotations(a){const m=new URLSearchParams;a?.run_id&&m.set("run_id",a.run_id),a?.case_id&&m.set("case_id",a.case_id),typeof a?.min_rating=="number"&&m.set("min_rating",String(a.min_rating)),a?.manual_case_decision&&m.set("manual_case_decision",a.manual_case_decision),typeof a?.limit=="number"&&m.set("limit",String(a.limit));const p=m.toString();return Oe(`/autoruns/annotations${p?`?${p}`:""}`)},async saveAutoRunAnnotation(a){return Oe("/autoruns/annotations",{method:"POST",body:JSON.stringify(a)})},async loadAutoRunPostAnalysis(a){const m=new URLSearchParams;a?.run_id&&m.set("run_id",a.run_id),typeof a?.limit_per_queue=="number"&&m.set("limit_per_queue",String(a.limit_per_queue)),typeof a?.annotation_limit=="number"&&m.set("annotation_limit",String(a.annotation_limit)),typeof a?.scan_limit=="number"&&m.set("scan_limit",String(a.scan_limit)),a?.from&&m.set("from",a.from),a?.to&&m.set("to",a.to),a?.target&&m.set("target",a.target),a?.mode&&m.set("mode",a.mode),a?.use_mock&&m.set("use_mock",a.use_mock),a?.prompt_contains&&m.set("prompt_contains",a.prompt_contains);const p=m.toString();return Oe(`/autoruns/post-analysis${p?`?${p}`:""}`)},async loadAutoRunAutogenHistory(a){const m=new URLSearchParams;a?.mode&&m.set("mode",a.mode),typeof a?.limit=="number"&&m.set("limit",String(a.limit));const p=m.toString();return Oe(`/autoruns/autogen/history${p?`?${p}`:""}`)},async generateAutoRunQuestions(a){return Oe("/autoruns/autogen/generate",{method:"POST",body:JSON.stringify(a)})}};function ot({value:a}){return s.jsx("pre",{className:"json-view",children:JSON.stringify(a??{},null,2)})}function ln({title:a,subtitle:m,actions:p,className:O,hideHeader:P,children:F}){return s.jsxs("section",{className:O?`panel-frame ${O}`:"panel-frame",children:[P?null:s.jsxs("header",{className:"panel-header",children:[s.jsxs("div",{children:[s.jsx("h2",{children:a}),m?s.jsx("p",{children:m}):null]}),p?s.jsx("div",{className:"panel-actions",children:p}):null]}),s.jsx("div",{className:"panel-body",children:F})]})}function hf(a){const m=new Date(a);return Number.isNaN(m.getTime())?a:m.toLocaleString("ru-RU")}function vf({sessionId:a,conversation:m,statusText:p,errorMessage:O,useMock:P,appLogs:F}){const Q=m.filter(z=>z.role==="assistant").length,Z=m.filter(z=>z.role==="user").length,U=m.length>0?m[m.length-1]:null;return s.jsxs(ln,{title:"SAM",subtitle:"System Assistant Monitor: срез по текущей сессии и логам.",children:[s.jsxs("div",{className:"metrics-grid",children:[s.jsxs("div",{children:[s.jsx("span",{children:"session_id"}),s.jsx("strong",{children:a||"новая сессия"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"mock_mode"}),s.jsx("strong",{children:P?"on":"off"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"сообщений пользователя"}),s.jsx("strong",{children:Z})]}),s.jsxs("div",{children:[s.jsx("span",{children:"ответов ассистента"}),s.jsx("strong",{children:Q})]}),s.jsxs("div",{children:[s.jsx("span",{children:"статус"}),s.jsx("strong",{children:p||"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"ошибка"}),s.jsx("strong",{children:O||"нет"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"последнее сообщение"}),s.jsx("strong",{children:U?.created_at?hf(U.created_at):"нет данных"})]})]}),s.jsx("h3",{style:{marginTop:12},children:"Последние системные логи"}),s.jsx(ot,{value:F.slice(0,120)})]})}const lc={fromLocal:"",toLocal:"",target:"all",mode:"all",useMock:"any",promptContains:"",limit:120},zo="needs_dialog_policy_fix",oc="ndc_autoruns_ui_config_v1",sc="ndc-autoruns-save",Mr=[{id:"general",label:"Общий контур",domain:"",defaultPrompt:"Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл."},{id:"settlements_60_62",label:"Расчеты 60/62",domain:"settlements_60_62",defaultPrompt:"Генерируй вопросы по расчетам с контрагентами (счета 60/62): закрытие задолженности, авансы, сверки, переносы остатков, цепочки документов."},{id:"month_close_costs_20_44",label:"Закрытие месяца 20/44",domain:"month_close_costs_20_44",defaultPrompt:"Генерируй вопросы по закрытию месяца и затратам на счетах 20/44: распределение, закрытие, остатки, аномалии и разницы по периодам."},{id:"vat_document_register_book",label:"НДС и регистры",domain:"vat_document_register_book",defaultPrompt:"Генерируй вопросы по НДС: начисление, вычет, книги покупок/продаж, счета-фактуры, прогноз обязательств и сверка цепочки документов."}];function gf(){return Mr.reduce((a,m)=>(a[m.id]=m.defaultPrompt,a),{})}const yf=new Set(Mr.map(a=>a.id)),xf={mode:"codex_creative",count:24,personalityId:"general",personalityPrompts:gf(),persistToEvalCases:!0,generatedBy:"manual_reviewer"};function _f(a){const m=a.getFullYear(),p=String(a.getMonth()+1).padStart(2,"0"),O=String(a.getDate()).padStart(2,"0"),P=String(a.getHours()).padStart(2,"0"),F=String(a.getMinutes()).padStart(2,"0");return`${m}-${p}-${O}T${P}:${F}`}function ic(){const a=new Date;return a.setDate(a.getDate()-14),_f(a)}function Do(a){if(!a.trim())return;const m=Date.parse(a);if(Number.isFinite(m))return new Date(m).toISOString()}function Tr(a){if(!a)return"нет данных";const m=Date.parse(a);return Number.isFinite(m)?new Date(m).toLocaleString("ru-RU"):a}function Sf(a,m){return m<=0?0:Math.max(0,Math.min(100,Number((a/m*100).toFixed(1))))}function _l(a){return typeof a!="number"?"нет данных":`${a.toFixed(1)}%`}function wf(a){return a==="assistant_stage1"?"assistant/s1":a==="assistant_stage2"?"assistant/s2":a==="assistant_p0"?"assistant/p0":a}function ac(a){return a==="up"?"Рост":a==="down"?"Регресс":"Без изменений"}function jf(a,m){return a.find(p=>p.case_id===m)??null}function uc(a){const m=Math.max(1,Math.min(5,Math.round(a)));return`${"●".repeat(m)}${"○".repeat(5-m)}`}function cc(a){return a.length===0?s.jsx("p",{className:"muted",children:"Покрытие доменов пока не сформировано."}):s.jsx("div",{className:"autoruns-coverage-list",children:a.map(m=>{const p=Sf(m.closed_cases,m.total_cases);return s.jsxs("div",{className:"autoruns-coverage-item",children:[s.jsxs("div",{className:"autoruns-coverage-head",children:[s.jsx("strong",{children:m.domain}),s.jsxs("span",{children:[m.closed_cases,"/",m.total_cases," (",p,"%)"]})]}),s.jsx("div",{className:"autoruns-coverage-bar",children:s.jsx("div",{style:{width:`${p}%`}})})]},m.domain)})})}function kf({connection:a,prompts:m,assistantPromptVersion:p,decompositionPromptVersion:O,showAssistantMode:P,showDecompositionMode:F,showProgressMode:Q,showCommentsMode:Z,onLog:U}){const[z,ee]=j.useState({...lc,fromLocal:ic()}),[C,H]=j.useState(null),[pe,Te]=j.useState(null),[te,re]=j.useState(null),[Se,me]=j.useState([]),[Me,Ne]=j.useState("all"),[Ae,Fe]=j.useState(null),[Ue,Ke]=j.useState([]),[st,be]=j.useState(""),[xe,it]=j.useState(""),[Ee,Ve]=j.useState(""),[X,he]=j.useState(xf),[ye,T]=j.useState([]),[W,M]=j.useState(null),[h,S]=j.useState(!1),[q,b]=j.useState(!1),[ne,le]=j.useState(!1),[de,ue]=j.useState(!1),[se,ve]=j.useState(!1),[Et,on]=j.useState(!1),[St,Kt]=j.useState(!1),[xn,at]=j.useState(""),[ae,et]=j.useState({open:!1,messageIndex:-1,rating:3,comment:"",manualCaseDecision:zo,annotationAuthor:"manual_reviewer",saving:!1,error:""}),Gt=j.useRef(!1),ut=j.useMemo(()=>Mr.find(u=>u.id===X.personalityId)??Mr[0],[X.personalityId]),Pt=C?.items.find(u=>u.run_id===xe)??pe?.run??null,ct=pe?jf(pe.cases,Ee):null,_e=Se.find(u=>u.annotation_id===st)??null,qt=te?.messages.find(u=>u.message_index===ae.messageIndex)??null,_n=j.useMemo(()=>{if(Se.length===0)return null;const u=Se.reduce((_,V)=>_+V.rating,0)/Se.length;return Number(u.toFixed(2))},[Se]),sn=j.useMemo(()=>{const u=[...C?.items??[]];return xe&&!u.some(_=>_.run_id===xe)&&pe?.run&&u.unshift(pe.run),u},[C?.items,pe?.run,xe]),Pe=j.useCallback(u=>{U?.(`[autoruns] ${u}`)},[U]),dt=j.useCallback(async()=>{Kt(!0);try{const u=await Be.loadAutoRunAnnotations({limit:800,manual_case_decision:Me});me(u.items),Fe(u.manual_case_decision_schema??null),Ke(u.available_manual_case_decisions??[]),be(_=>u.items.length===0?"":u.items.some(V=>V.annotation_id===_)?_:u.items[0].annotation_id)}catch(u){Pe(`Annotations load error: ${u instanceof Error?u.message:String(u)}`)}finally{Kt(!1)}},[Me,Pe]),Ot=j.useCallback(async()=>{le(!0);try{const u=await Be.loadAutoRunAutogenHistory({limit:180});T(u.items)}catch(u){Pe(`Autogen history load error: ${u instanceof Error?u.message:String(u)}`)}finally{le(!1)}},[Pe]),Ge=j.useCallback(async()=>{b(!0);try{const u=await Be.loadAutoRunPostAnalysis({run_id:xe||void 0,limit_per_queue:30,annotation_limit:1500,from:Do(z.fromLocal),to:Do(z.toLocal),target:z.target,mode:z.mode,use_mock:z.useMock,prompt_contains:z.promptContains.trim()||void 0});M(u)}catch(u){Pe(`Post-analysis load error: ${u instanceof Error?u.message:String(u)}`),M(null)}finally{b(!1)}},[z.fromLocal,z.mode,z.promptContains,z.target,z.toLocal,z.useMock,Pe,xe]),Yt=j.useCallback(async()=>{S(!0),at("");try{const u=X.personalityPrompts[X.personalityId]??"",_=[m.systemPrompt,m.developerPrompt,m.domainPrompt,m.schemaNotes,m.fewShotExamples].join(` -`).slice(0,900),V=await Be.generateAutoRunQuestions({mode:X.mode,count:X.count,domain:ut.domain||void 0,persist_to_eval_cases:X.persistToEvalCases,generated_by:X.generatedBy.trim()||void 0,context:{llm_provider:a.llmProvider,model:a.model,assistant_prompt_version:p,decomposition_prompt_version:O,prompt_fingerprint:_,autogen_personality_id:ut.id,autogen_personality_prompt:u.trim()||void 0}});Pe(`Generated ${V.generation.count} questions (${V.generation.mode}) id=${V.generation.generation_id}`+(V.generation.saved_case_set_file?` saved=${V.generation.saved_case_set_file}`:"")),await Ot()}catch(u){const _=u instanceof Error?u.message:String(u);at(`Автогенерация: ${_}`),Pe(`Autogen generate error: ${_}`)}finally{S(!1)}},[p,X.count,X.generatedBy,X.mode,X.personalityId,X.personalityPrompts,X.persistToEvalCases,a.llmProvider,a.model,O,Ot,Pe,m.developerPrompt,m.domainPrompt,m.fewShotExamples,m.schemaNotes,m.systemPrompt,ut.domain,ut.id]),At=j.useCallback(async(u,_)=>{on(!0);try{const V=await Be.loadAutoRunCaseDialog(u,_);re(V)}catch(V){const je=V instanceof Error?V.message:String(V);at(`Диалог кейса: ${je}`),re(null),Pe(`Dialog load error for ${u}/${_}: ${je}`)}finally{on(!1)}},[Pe]),mt=j.useCallback(async(u,_)=>{ve(!0);try{const V=await Be.loadAutoRunDetail(u);Te(V);const je=(_&&V.cases.some(Ft=>Ft.case_id===_)?_:"")||V.cases[0]?.case_id||"";it(u),Ve(je),je?await At(u,je):re(null)}catch(V){const je=V instanceof Error?V.message:String(V);at(`Детализация прогона: ${je}`),Te(null),re(null),Pe(`Run detail load error for ${u}: ${je}`)}finally{ve(!1)}},[At,Pe]),It=j.useCallback(async u=>{ue(!0),at("");try{const _=await Be.loadAutoRunsHistory({from:Do(z.fromLocal),to:Do(z.toLocal),target:z.target,mode:z.mode,use_mock:z.useMock,prompt_contains:z.promptContains.trim()||void 0,limit:z.limit});if(H(_),_.items.length===0){it(""),Ve(""),Te(null),re(null);return}const V=u?.keepSelection??!0,je=u?.preferredRunId??"",Ft=u?.preferredCaseId??"",Ut=V&&je&&_.items.some(Xt=>Xt.run_id===je)?je:_.items[0].run_id;await mt(Ut,V?Ft:void 0),Ge()}catch(_){const V=_ instanceof Error?_.message:String(_);at(`История прогонов: ${V}`),Pe(`History load error: ${V}`)}finally{ue(!1)}},[z.fromLocal,z.limit,z.mode,z.promptContains,z.target,z.toLocal,z.useMock,Ge,mt,Pe]),nr=j.useCallback(u=>{u.role==="assistant"&&et({open:!0,messageIndex:u.message_index,rating:u.annotation?.rating??3,comment:u.annotation?.comment??"",manualCaseDecision:u.annotation?.manual_case_decision??zo,annotationAuthor:u.annotation?.annotation_author??X.generatedBy,saving:!1,error:""})},[X.generatedBy]),wt=j.useCallback(()=>{et(u=>u.saving?u:{open:!1,messageIndex:-1,rating:3,comment:"",manualCaseDecision:zo,annotationAuthor:X.generatedBy,saving:!1,error:""})},[X.generatedBy]),Sn=j.useCallback(async()=>{if(!(!xe||!Ee||ae.messageIndex<0)){if(!ae.comment.trim()){et(u=>({...u,error:"Добавьте комментарий."}));return}et(u=>({...u,saving:!0,error:""}));try{await Be.saveAutoRunAnnotation({run_id:xe,case_id:Ee,message_index:ae.messageIndex,rating:ae.rating,comment:ae.comment.trim(),manual_case_decision:ae.manualCaseDecision,annotation_author:ae.annotationAuthor.trim()||void 0}),await Promise.all([mt(xe,Ee),dt(),Ge()]),wt()}catch(u){et(_=>({..._,saving:!1,error:u instanceof Error?u.message:String(u)}))}}},[wt,ae.annotationAuthor,ae.comment,ae.manualCaseDecision,ae.messageIndex,ae.rating,dt,Ge,mt,Ee,xe]),Jt=j.useCallback(async u=>{be(u.annotation_id),await mt(u.run_id,u.case_id),C?.items.some(_=>_.run_id===u.run_id)||at("Комментарий относится к прогону вне текущего фильтра. Детали загружены напрямую.")},[C?.items,mt]);j.useEffect(()=>{Gt.current||(Gt.current=!0,It({keepSelection:!1}),Ot(),Ge())},[Ot,It,Ge]),j.useEffect(()=>{Gt.current&&dt()},[Me,dt]),j.useEffect(()=>{const u=localStorage.getItem(oc);if(u)try{const _=JSON.parse(u);if(_.filters){const V=_.filters;ee(je=>({...je,...V,limit:typeof V.limit=="number"?Math.max(1,Math.min(500,V.limit)):je.limit}))}_.autoGenSettings&&he(V=>{const je={...V.personalityPrompts};for(const Ut of Mr){const Xt=_.autoGenSettings?.personalityPrompts?.[Ut.id];typeof Xt=="string"&&(je[Ut.id]=Xt)}const Ft=_.autoGenSettings?.personalityId&&yf.has(_.autoGenSettings.personalityId)?_.autoGenSettings.personalityId:V.personalityId;return{...V,mode:_.autoGenSettings?.mode==="codex_creative"||_.autoGenSettings?.mode==="qwen_seed"?_.autoGenSettings.mode:V.mode,count:typeof _.autoGenSettings?.count=="number"?Math.max(1,Math.min(200,_.autoGenSettings.count)):V.count,personalityId:Ft,personalityPrompts:je,persistToEvalCases:typeof _.autoGenSettings?.persistToEvalCases=="boolean"?_.autoGenSettings.persistToEvalCases:V.persistToEvalCases,generatedBy:typeof _.autoGenSettings?.generatedBy=="string"?_.autoGenSettings.generatedBy:V.generatedBy}}),(_.annotationDecisionFilter==="all"||typeof _.annotationDecisionFilter=="string"&&_.annotationDecisionFilter.length>0)&&Ne(_.annotationDecisionFilter)}catch{}},[]);const an=j.useCallback(()=>{const u={filters:z,autoGenSettings:{mode:X.mode,count:X.count,personalityId:X.personalityId,personalityPrompts:X.personalityPrompts,persistToEvalCases:X.persistToEvalCases,generatedBy:X.generatedBy},annotationDecisionFilter:Me};localStorage.setItem(oc,JSON.stringify(u))},[Me,X,z]);return j.useEffect(()=>{const u=()=>{an(),Pe("Сохранены настройки панели автопрогонов.")};return window.addEventListener(sc,u),()=>{window.removeEventListener(sc,u)}},[Pe,an]),s.jsxs(ln,{className:"autoruns-frame",title:"",hideHeader:!0,children:[s.jsxs("div",{className:"autoruns-columns",children:[s.jsxs("section",{className:"autoruns-col",children:[s.jsx("div",{className:"autoruns-col-header",children:s.jsx("h3",{children:"Настройки"})}),s.jsx("h4",{children:"Настройки выборки"}),s.jsxs("div",{className:"autoruns-form-grid",children:[s.jsxs("label",{children:["Дата с",s.jsx("input",{type:"datetime-local",value:z.fromLocal,onChange:u=>ee(_=>({..._,fromLocal:u.target.value}))})]}),s.jsxs("label",{children:["Дата по",s.jsx("input",{type:"datetime-local",value:z.toLocal,onChange:u=>ee(_=>({..._,toLocal:u.target.value}))})]}),s.jsxs("label",{children:["Целевой контур",s.jsxs("select",{value:z.target,onChange:u=>ee(_=>({..._,target:u.target.value})),children:[s.jsx("option",{value:"all",children:"все"}),(C?.available.targets??[]).map(u=>s.jsx("option",{value:u,children:u},u))]})]}),s.jsxs("label",{children:["Режим",s.jsxs("select",{value:z.mode,onChange:u=>ee(_=>({..._,mode:u.target.value})),children:[s.jsx("option",{value:"all",children:"все"}),(C?.available.modes??[]).map(u=>s.jsx("option",{value:u,children:u},u))]})]}),s.jsxs("label",{children:["Использовать mock",s.jsxs("select",{value:z.useMock,onChange:u=>ee(_=>({..._,useMock:u.target.value})),children:[s.jsx("option",{value:"any",children:"любой"}),s.jsx("option",{value:"true",children:"да"}),s.jsx("option",{value:"false",children:"нет"})]})]}),s.jsxs("label",{children:["Лимит",s.jsx("input",{type:"number",min:1,max:500,value:z.limit,onChange:u=>ee(_=>({..._,limit:Number(u.target.value||120)}))})]}),s.jsxs("label",{className:"full-width",children:["Версия промпта содержит",s.jsx("input",{value:z.promptContains,onChange:u=>ee(_=>({..._,promptContains:u.target.value})),placeholder:"normalizer_v2_0_2 / address_query_runtime_v1",list:"autoruns-prompt-versions"})]})]}),s.jsx("datalist",{id:"autoruns-prompt-versions",children:(C?.available.prompt_versions??[]).map(u=>s.jsx("option",{value:u},u))}),s.jsxs("div",{className:"button-row",children:[s.jsx("button",{type:"button",disabled:de,onClick:()=>{It({keepSelection:!1})},children:de?"Обновляю...":"Применить"}),s.jsx("button",{type:"button",className:"tab",onClick:()=>{ee({...lc,fromLocal:ic()}),at("")},children:"Сбросить фильтры"})]}),s.jsx("h4",{children:"Контур генерации"}),s.jsxs("div",{className:"autoruns-meta-list",children:[s.jsxs("div",{children:[s.jsx("span",{children:"Провайдер:"}),s.jsx("strong",{children:a.llmProvider})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Модель:"}),s.jsx("strong",{children:a.model||"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Промпт ассистента:"}),s.jsx("strong",{children:p})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Промпт декомпозиции:"}),s.jsx("strong",{children:O})]})]}),s.jsx("h4",{children:"Автогенерация вопросов"}),s.jsxs("div",{className:"autoruns-form-grid",children:[s.jsxs("label",{children:["Режим генерации",s.jsxs("select",{value:X.mode,onChange:u=>he(_=>({..._,mode:u.target.value})),children:[s.jsx("option",{value:"codex_creative",children:"codex_creative"}),s.jsx("option",{value:"qwen_seed",children:"qwen_seed"})]})]}),s.jsxs("label",{children:["Кол-во",s.jsx("input",{type:"number",min:1,max:200,value:X.count,onChange:u=>he(_=>({..._,count:Math.max(1,Math.min(200,Number(u.target.value||24)))}))})]}),s.jsxs("label",{children:["Личность автогенерации",s.jsx("select",{value:X.personalityId,onChange:u=>he(_=>({..._,personalityId:u.target.value})),children:Mr.map(u=>s.jsx("option",{value:u.id,children:u.label},u.id))})]}),s.jsxs("label",{children:["Кто генерирует",s.jsx("input",{value:X.generatedBy,onChange:u=>he(_=>({..._,generatedBy:u.target.value})),placeholder:"manual_reviewer"})]}),s.jsxs("label",{className:"full-width",children:["Промпт личности",s.jsx("textarea",{value:X.personalityPrompts[X.personalityId]??"",onChange:u=>he(_=>({..._,personalityPrompts:{..._.personalityPrompts,[_.personalityId]:u.target.value}})),placeholder:"Текст промпта для выбранной личности автогенерации"})]}),s.jsxs("label",{className:"checkbox-row",children:[s.jsx("input",{type:"checkbox",checked:X.persistToEvalCases,onChange:u=>he(_=>({..._,persistToEvalCases:u.target.checked}))}),"Сохранять кейс-сет в `eval_cases`"]})]}),s.jsxs("div",{className:"button-row",children:[s.jsx("button",{type:"button",disabled:h,onClick:()=>{Yt()},children:h?"Генерирую...":"Сгенерировать пачку"}),s.jsx("button",{type:"button",className:"tab",disabled:ne,onClick:()=>{Ot()},children:ne?"Обновляю...":"Обновить историю"})]}),s.jsxs("div",{className:"autoruns-autogen-list",children:[ne?s.jsx("p",{className:"muted",children:"Загружаю историю автогенераций..."}):null,!ne&&ye.length===0?s.jsx("p",{className:"muted",children:"История автогенераций пока пустая."}):null,ye.slice(0,30).map(u=>s.jsxs("article",{className:"autoruns-autogen-item",children:[s.jsxs("header",{children:[s.jsx("strong",{children:Tr(u.created_at)}),s.jsx("span",{children:u.mode})]}),s.jsxs("div",{className:"autoruns-run-meta",children:["id=",u.generation_id," | count=",u.count]}),s.jsxs("div",{className:"autoruns-run-meta",children:["домен=",u.domain??"общий",u.generated_by?` | автор=${u.generated_by}`:""]}),u.saved_case_set_file?s.jsxs("div",{className:"autoruns-run-meta",children:["кейс-сет=",u.saved_case_set_file]}):null,(u.questions??[]).length>0?s.jsx("p",{children:u.questions[0]}):null]},u.generation_id))]}),s.jsxs("details",{className:"autoruns-prompt-details",children:[s.jsx("summary",{children:"Копия активного промпта (только чтение)"}),s.jsxs("label",{children:["Системный",s.jsx("textarea",{readOnly:!0,value:m.systemPrompt})]}),s.jsxs("label",{children:["Разработчика",s.jsx("textarea",{readOnly:!0,value:m.developerPrompt})]}),s.jsxs("label",{children:["Доменный",s.jsx("textarea",{readOnly:!0,value:m.domainPrompt})]}),s.jsxs("label",{children:["Заметки по схеме",s.jsx("textarea",{readOnly:!0,value:m.schemaNotes})]}),s.jsxs("label",{children:["Примеры few-shot",s.jsx("textarea",{readOnly:!0,value:m.fewShotExamples})]})]}),xn?s.jsx("p",{className:"error-text",children:xn}):null]}),s.jsxs("section",{className:"autoruns-col",children:[s.jsx("div",{className:"autoruns-col-header",children:s.jsx("h3",{children:"Выдача прогонов"})}),s.jsxs("div",{className:"autoruns-stats-grid",children:[s.jsxs("div",{children:[s.jsx("span",{children:"Всего"}),s.jsx("strong",{children:C?.stats.runs_total??0})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Средний score"}),s.jsx("strong",{children:_l(C?.stats.avg_score_index??null)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Тренд"}),s.jsx("strong",{children:C?ac(C.stats.trend):"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Блокеры"}),s.jsx("strong",{children:C?.stats.blocking_runs??0})]})]}),s.jsxs("div",{className:"autoruns-run-list",children:[(C?.items??[]).map(u=>s.jsxs("button",{type:"button",className:xe===u.run_id?"autoruns-run-item selected":"autoruns-run-item",onClick:()=>{mt(u.run_id)},children:[s.jsxs("div",{className:"autoruns-run-head",children:[s.jsx("strong",{children:Tr(u.run_timestamp)}),s.jsx("span",{children:wf(u.eval_target)})]}),s.jsx("div",{className:"autoruns-run-meta",children:u.run_id}),s.jsxs("div",{className:"autoruns-run-meta",children:["режим=",u.mode??"нет данных"," | mock=",String(u.use_mock)]}),u.llm_provider||u.model?s.jsxs("div",{className:"autoruns-run-meta",children:["llm=",u.llm_provider??"нет данных"," | модель=",u.model??"нет данных"]}):null,s.jsxs("div",{className:"autoruns-run-meta",children:["промпт=",u.prompt_version??"нет данных"]}),s.jsxs("div",{className:"autoruns-run-foot",children:[s.jsxs("span",{children:["оценка: ",_l(u.score_index)]}),s.jsxs("span",{children:["закрыто/открыто: ",u.closed_cases,"/",u.open_cases]})]}),s.jsxs("div",{className:"autoruns-run-foot",children:[s.jsxs("span",{children:["блокеры: ",u.blocking_failures]}),s.jsxs("span",{children:["качество: ",u.quality_failures]})]})]},u.run_id)),(C?.items.length??0)===0?s.jsx("p",{className:"muted",children:"За выбранный диапазон прогонов нет."}):null]})]}),s.jsxs("section",{className:"autoruns-col",children:[s.jsxs("div",{className:"autoruns-col-header",children:[s.jsx("h3",{children:"Диалог прогона"}),s.jsxs("div",{className:"autoruns-dialog-toolbar",children:[s.jsxs("label",{children:["Прогон",s.jsx("select",{value:xe,onChange:u=>{const _=u.target.value;mt(_)},children:sn.map(u=>s.jsxs("option",{value:u.run_id,children:[Tr(u.run_timestamp)," | ",u.run_id]},u.run_id))})]}),s.jsxs("label",{children:["Кейс",s.jsx("select",{value:Ee,onChange:u=>{const _=u.target.value;Ve(_),xe&&_&&At(xe,_)},children:(pe?.cases??[]).map(u=>s.jsxs("option",{value:u.case_id,children:[u.case_id," | ",u.status]},u.case_id))})]})]})]}),s.jsx("div",{className:"autoruns-case-list",children:(pe?.cases??[]).map(u=>s.jsxs("button",{type:"button",className:Ee===u.case_id?"autoruns-case-item selected":"autoruns-case-item",onClick:()=>{Ve(u.case_id),xe&&At(xe,u.case_id)},children:[s.jsx("span",{children:u.case_id}),s.jsxs("span",{children:[u.status,u.commented_count>0?` | 💬${u.commented_count}`:""]})]},u.case_id))}),s.jsxs("div",{className:"autoruns-dialog-view",children:[Et||se?s.jsx("p",{className:"muted",children:"Загружаю диалог..."}):null,!Et&&!se&&(te?.messages.length??0)===0?s.jsx("p",{className:"muted",children:"Диалог для этого кейса не найден."}):null,(te?.messages??[]).map((u,_)=>{const V=u.role==="assistant"?"assistant":"user";return s.jsxs("article",{className:`autoruns-msg ${V}`,children:[s.jsxs("header",{children:[s.jsx("strong",{children:V==="assistant"?"Система":"Модель/вопрос"}),s.jsxs("div",{className:"autoruns-msg-head-actions",children:[s.jsx("span",{children:u.created_at?Tr(u.created_at):"нет данных"}),V==="assistant"?s.jsx("button",{type:"button",className:u.commented?"autoruns-comment-icon commented":"autoruns-comment-icon",onClick:()=>nr(u),title:"Комментировать ответ системы",children:"💬"}):null]})]}),s.jsx("p",{children:u.text}),V==="assistant"&&u.annotation?s.jsxs("div",{className:"autoruns-msg-annotation",children:[s.jsx("strong",{children:uc(u.annotation.rating)}),s.jsx("span",{children:u.annotation.comment}),s.jsxs("span",{className:"muted",children:[u.annotation.manual_case_decision,u.annotation.annotation_author?` | ${u.annotation.annotation_author}`:""]})]}):null,(u.trace_id||u.reply_type)&&s.jsxs("footer",{children:[u.trace_id?s.jsxs("span",{children:["trace=",u.trace_id]}):null,u.reply_type?s.jsxs("span",{children:["reply_type=",u.reply_type]}):null]})]},u.message_id??`${V}-${_}`)})]})]}),P?s.jsxs("section",{className:"autoruns-col",children:[s.jsx("div",{className:"autoruns-col-header",children:s.jsx("h3",{children:"Режим ассистента"})}),s.jsxs("div",{className:"autoruns-meta-list",children:[s.jsxs("div",{children:[s.jsx("span",{children:"источник:"}),s.jsx("strong",{children:te?.source??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"сессия:"}),s.jsx("strong",{children:te?.session_id??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"контур прогона:"}),s.jsx("strong",{children:Pt?.eval_target??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"оценка прогона:"}),s.jsx("strong",{children:_l(Pt?.score_index??null)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"комментарии:"}),s.jsx("strong",{children:pe?.annotations_summary?.total??0})]})]}),s.jsx("h4",{children:"Пакет режима ассистента"}),s.jsx(ot,{value:te?.assistant_mode??{note:"assistant_mode недоступен"}}),s.jsx("h4",{style:{marginTop:12},children:"Проверки кейса"}),s.jsx(ot,{value:ct?.checks??{note:"checks недоступен"}}),s.jsx("h4",{style:{marginTop:12},children:"Сабскор метрик"}),s.jsx(ot,{value:ct?.metric_subscores??{note:"metric_subscores недоступен"}})]}):null,F?s.jsxs("section",{className:"autoruns-col",children:[s.jsx("div",{className:"autoruns-col-header",children:s.jsx("h3",{children:"Режим декомпозиции"})}),s.jsxs("div",{className:"autoruns-meta-list",children:[s.jsxs("div",{children:[s.jsx("span",{children:"кейс:"}),s.jsx("strong",{children:ct?.case_id??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"домен:"}),s.jsx("strong",{children:ct?.domain??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"класс запроса:"}),s.jsx("strong",{children:ct?.query_class??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"trace:"}),s.jsx("strong",{children:ct?.trace_id??"нет данных"})]})]}),s.jsx("h4",{children:"Шаги декомпозиции"}),(te?.decomposition.length??0)>0?s.jsx("ol",{className:"autoruns-decomposition-list",children:(te?.decomposition??[]).map((u,_)=>s.jsx("li",{children:u},`${_}-${u.slice(0,24)}`))}):s.jsx("p",{className:"muted",children:"В логах кейса нет явной декомпозиции."})]}):null,Q?s.jsxs("section",{className:"autoruns-col",children:[s.jsx("div",{className:"autoruns-col-header",children:s.jsx("h3",{children:"Прогресс / регресс"})}),s.jsxs("div",{className:"autoruns-stats-grid",children:[s.jsxs("div",{children:[s.jsx("span",{children:"Последний score"}),s.jsx("strong",{children:_l(C?.stats.latest_score_index??null)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Предыдущий"}),s.jsx("strong",{children:_l(C?.stats.previous_score_index??null)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Тренд"}),s.jsx("strong",{children:C?ac(C.stats.trend):"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Пробелы качества"}),s.jsx("strong",{children:C?.stats.quality_gap_runs??0})]})]}),s.jsx("h4",{children:"Покрытие доменов (история)"}),cc(C?.stats.domain_coverage??[]),s.jsx("h4",{style:{marginTop:14},children:"Покрытие доменов (выбранный прогон)"}),cc(pe?.coverage.domain_coverage??[]),s.jsx("h4",{style:{marginTop:14},children:"Очереди фиксов пост-анализа"}),q?s.jsx("p",{className:"muted",children:"Собираю пост-анализ..."}):null,q?null:s.jsx("div",{className:"autoruns-stats-grid",children:Object.entries(W?.post_analysis.stats.by_queue??{}).map(([u,_])=>s.jsxs("div",{children:[s.jsx("span",{children:u}),s.jsx("strong",{children:_})]},u))}),s.jsxs("div",{className:"autoruns-autogen-list",children:[(W?.post_analysis.recommended_regression_candidates??[]).slice(0,12).map(u=>s.jsxs("article",{className:"autoruns-autogen-item",children:[s.jsxs("header",{children:[s.jsx("strong",{children:u.manual_case_decision}),s.jsxs("span",{children:[u.rating,"/5"]})]}),s.jsxs("div",{className:"autoruns-run-meta",children:[u.domain??"неизвестно"," / ",u.query_class??"неизвестно"]}),s.jsx("p",{children:u.comment})]},u.annotation_id)),!q&&(W?.post_analysis.recommended_regression_candidates.length??0)===0?s.jsx("p",{className:"muted",children:"Рекомендованных кандидатов пока нет."}):null]})]}):null,Z?s.jsxs("section",{className:"autoruns-col",children:[s.jsx("div",{className:"autoruns-col-header",children:s.jsx("h3",{children:"Комментарии"})}),s.jsx("h4",{children:"Размеченные ответы"}),s.jsx("div",{className:"autoruns-form-grid",children:s.jsxs("label",{children:["Фильтр решений",s.jsxs("select",{value:Me,onChange:u=>Ne(u.target.value),children:[s.jsx("option",{value:"all",children:"все"}),(Ue.length>0?Ue:Ae?.enum??[]).map(u=>s.jsx("option",{value:u,children:String(Ae?.labels?.[u]??u)},u))]})]})}),s.jsxs("div",{className:"autoruns-stats-grid",children:[s.jsxs("div",{children:[s.jsx("span",{children:"Комментариев"}),s.jsx("strong",{children:Se.length})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Средний рейтинг"}),s.jsx("strong",{children:_n===null?"нет данных":`${_n.toFixed(2)} / 5`})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Последний"}),s.jsx("strong",{children:Se.length>0?Tr(Se[0].updated_at):"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"Статус"}),s.jsx("strong",{children:St?"обновляю":"готово"})]})]}),s.jsxs("div",{className:"button-row",children:[s.jsx("button",{type:"button",disabled:St,onClick:()=>{dt()},children:St?"Обновляю...":"Обновить список"}),s.jsx("button",{type:"button",className:"tab",disabled:q,onClick:()=>{Ge()},children:q?"Идет пост-анализ...":"Обновить пост-анализ"})]}),s.jsxs("div",{className:"autoruns-comments-list",children:[St?s.jsx("p",{className:"muted",children:"Загружаю комментарии..."}):null,!St&&Se.length===0?s.jsx("p",{className:"muted",children:"Пока нет откомментированных ответов."}):null,Se.map(u=>s.jsxs("button",{type:"button",className:st===u.annotation_id?"autoruns-comment-item selected":"autoruns-comment-item",onClick:()=>{Jt(u)},children:[s.jsxs("div",{className:"autoruns-comment-head",children:[s.jsx("strong",{children:uc(u.rating)}),s.jsx("span",{children:Tr(u.updated_at)})]}),s.jsx("div",{className:"autoruns-run-meta",children:u.run_id}),s.jsxs("div",{className:"autoruns-run-meta",children:["case=",u.case_id," | msg=",u.message_index]}),s.jsxs("div",{className:"autoruns-run-meta",children:["decision=",u.manual_case_decision,u.annotation_author?` | author=${u.annotation_author}`:""]}),s.jsx("p",{children:u.comment})]},u.annotation_id))]}),_e?s.jsxs(s.Fragment,{children:[s.jsx("h4",{children:"Тех-контекст брака"}),s.jsxs("div",{className:"autoruns-meta-list",children:[s.jsxs("div",{children:[s.jsx("span",{children:"trace:"}),s.jsx("strong",{children:_e.technical_context.trace_id??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"reply_type:"}),s.jsx("strong",{children:_e.technical_context.reply_type??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"domain:"}),s.jsx("strong",{children:_e.technical_context.domain??"нет данных"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"query_class:"}),s.jsx("strong",{children:_e.technical_context.query_class??"нет данных"})]})]}),s.jsx("h4",{children:"JSON разбор"}),s.jsx(ot,{value:{annotation_id:_e.annotation_id,run_id:_e.run_id,case_id:_e.case_id,message_index:_e.message_index,rating:_e.rating,comment:_e.comment,manual_case_decision:_e.manual_case_decision,annotation_author:_e.annotation_author,context:_e.context,technical_context:_e.technical_context,case_summary:_e.case_summary?{case_id:_e.case_summary.case_id,domain:_e.case_summary.domain,query_class:_e.case_summary.query_class,checks:_e.case_summary.checks,metric_subscores:_e.case_summary.metric_subscores}:null}})]}):null]}):null]}),ae.open?s.jsx("div",{className:"autoruns-comment-modal-backdrop",onClick:u=>{u.target===u.currentTarget&&wt()},children:s.jsxs("div",{className:"autoruns-comment-modal",children:[s.jsx("h3",{children:"Комментарий к ответу системы"}),s.jsx("p",{className:"muted",children:"Оцените ответ по 5-балльной шкале и добавьте комментарий по браку."}),qt?s.jsxs("details",{className:"autoruns-prompt-details",open:!0,children:[s.jsx("summary",{children:"Ответ системы"}),s.jsx("p",{className:"autoruns-comment-quote",children:qt.text})]}):null,s.jsx("div",{className:"autoruns-rating-row",role:"group","aria-label":"Рейтинг ответа",children:[1,2,3,4,5].map(u=>s.jsx("button",{type:"button",className:ae.rating>=u?"autoruns-rating-dot active":"autoruns-rating-dot",onClick:()=>et(_=>({..._,rating:u})),disabled:ae.saving,"aria-label":`Оценка ${u}`,children:ae.rating>=u?"●":"○"},u))}),s.jsxs("div",{className:"autoruns-form-grid",children:[s.jsxs("label",{children:["Решение по кейсу",s.jsx("select",{value:ae.manualCaseDecision,onChange:u=>et(_=>({..._,manualCaseDecision:u.target.value})),disabled:ae.saving,children:(Ue.length>0?Ue:Ae?.enum??[zo]).map(u=>s.jsx("option",{value:u,children:String(Ae?.labels?.[u]??u)},u))})]}),s.jsxs("label",{children:["Автор комментария",s.jsx("input",{value:ae.annotationAuthor,onChange:u=>et(_=>({..._,annotationAuthor:u.target.value})),placeholder:"manual_reviewer",disabled:ae.saving})]})]}),s.jsxs("label",{children:["Комментарий",s.jsx("textarea",{value:ae.comment,onChange:u=>et(_=>({..._,comment:u.target.value})),placeholder:"Почему ответ бракованный, что именно пошло не так, какие технические детали проверить.",rows:4,disabled:ae.saving})]}),ae.error?s.jsx("p",{className:"error-text",children:ae.error}):null,s.jsxs("div",{className:"button-row",children:[s.jsx("button",{type:"button",onClick:()=>{Sn()},disabled:ae.saving,children:ae.saving?"Сохраняю...":"Готово"}),s.jsx("button",{type:"button",className:"tab",onClick:wt,disabled:ae.saving,children:"Отмена"})]})]})}):null]})}const Cf=/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json|debug_payload|technical_breakdown)\b/i,Nf=[/\b(?:debug_payload_json|technical_breakdown_json)\b/i,/\b(?:route_summary|semantic_profile|domain_scope|relation_patterns|account_scope)\b/i,/\b(?:coverage_report|retrieval_status|problem_unit_state|candidate_evidence)\b/i,/\b(?:graph_domain_scope|graph_runtime|selection_reason|why_included)\b/i];function Ef(a){try{return JSON.stringify(a,null,2)}catch{return String(a)}}function Pf(a){const m=String(a??""),p=m.match(Cf);return(p?m.slice(0,p.index):m).replace(/###\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)[\s\S]*?(?:```[\s\S]*?```|$)/gi,"").replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)\b[\s\S]*$/gi,"").split(/\r?\n/g).map(Q=>Q.trimEnd()).filter(Q=>Q.trim().length>0).filter(Q=>!Nf.some(Z=>Z.test(Q))).join(` -`).trim()}function Rf(a,m,p="default"){const O=p==="technical",P=[];P.push("# Assistant conversation export"),P.push(`session_id: ${a||"n/a"}`),P.push(`export_mode: ${p}`),P.push(`exported_at: ${new Date().toISOString()}`),P.push("");for(let F=0;F{C.current&&(C.current.scrollTop=C.current.scrollHeight)},[m,z]),j.useEffect(()=>()=>{H.current!==null&&window.clearTimeout(H.current)},[]);async function Se(me){if(m.length===0)return;const Me=Rf(a,m,me),Ne=await Lf(Me);re(me==="technical"?"тех":"чат"),Te(Ne?"success":"error"),H.current!==null&&window.clearTimeout(H.current),H.current=window.setTimeout(()=>{Te("idle")},2200)}return s.jsxs(ln,{title:"Режим ассистента",subtitle:"Диалоговый слой поверх normalizer, маршрутизации и factual retrieval.",actions:s.jsxs("div",{className:"assistant-panel-actions",children:[s.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>{Se("default")},disabled:m.length===0,title:"Экспорт только user-facing чата",children:"Скопировать чат"}),s.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>{Se("technical")},disabled:m.length===0,title:"Технический экспорт с debug payload",children:"Скопировать техчат"}),pe==="success"?s.jsxs("span",{className:"assistant-copy-feedback success",children:["Скопировано (",te,")"]}):null,pe==="error"?s.jsx("span",{className:"assistant-copy-feedback error",children:"Ошибка копирования"}):null,s.jsx("span",{className:"status-chip",children:a?`session: ${a}`:"новая сессия"})]}),children:[s.jsxs("div",{ref:C,className:"assistant-chat-list",children:[m.length===0?s.jsx("div",{className:"assistant-empty muted",children:"Диалог пуст. Отправьте первый вопрос, чтобы запустить контур ассистента."}):null,m.map(me=>s.jsxs("article",{className:`assistant-msg ${me.role}`,children:[s.jsxs("header",{className:"assistant-msg-head",children:[s.jsx("strong",{children:Tf(me.role)}),s.jsx("span",{children:Mf(me.created_at)})]}),s.jsx("div",{className:"assistant-msg-body",children:me.text}),me.role==="assistant"&&me.debug?s.jsxs("details",{className:"assistant-debug",children:[s.jsx("summary",{children:"Показать технический разбор"}),s.jsx(ot,{value:me.debug})]}):null]},me.message_id))]}),s.jsxs("div",{className:"assistant-compose",children:[s.jsxs("label",{className:"full-width",children:["Сообщение",s.jsx("textarea",{value:p,onChange:me=>O(me.target.value),rows:4,placeholder:"Введите вопрос к данным компании..."})]}),s.jsxs("div",{className:"button-row",children:[s.jsxs("label",{className:"checkbox-row",children:[s.jsx("input",{type:"checkbox",checked:P,onChange:me=>F(me.target.checked)}),"Mock-режим"]}),s.jsx("button",{type:"button",onClick:()=>Q(),disabled:U||!p.trim(),children:U?"Выполняю...":"Отправить"}),s.jsx("button",{type:"button",onClick:()=>Z(),disabled:U&&m.length===0,children:"Сбросить сессию"})]}),z?s.jsx("p",{className:"diff-summary",children:z}):null,ee?s.jsx("p",{className:"error-text",children:ee}):null]})]})}function dc({value:a,modelOptions:m,modelsBusy:p,onChange:O,onReloadModels:P,onTestConnection:F,onSaveLocalConfig:Q,lastStatus:Z,busy:U}){const z=a.llmProvider==="local",ee=m.includes(a.model);return s.jsxs(ln,{title:"LLM Connection",subtitle:"Switch between OpenAI cloud and local OpenAI-compatible server.",actions:s.jsx("span",{className:"status-chip",children:Z||"Status: not checked"}),children:[s.jsxs("div",{className:"grid-two",children:[s.jsxs("label",{children:["Provider",s.jsxs("select",{value:a.llmProvider,onChange:C=>{const H=C.target.value==="local"?"local":"openai";O({...a,llmProvider:H,baseUrl:H==="local"?"http://127.0.0.1:1234/v1":"https://api.openai.com/v1"})},children:[s.jsx("option",{value:"openai",children:"OpenAI (token)"}),s.jsx("option",{value:"local",children:"Local (LM Studio / OpenAI-compatible)"})]})]}),s.jsxs("label",{children:["Model",s.jsxs("select",{value:ee?a.model:"__manual__",onChange:C=>{const H=C.target.value;H!=="__manual__"&&O({...a,model:H})},children:[s.jsx("option",{value:"__manual__",children:"Manual input"}),m.map(C=>s.jsx("option",{value:C,children:C},C))]})]}),s.jsxs("label",{children:["Model ID (manual)",s.jsx("input",{value:a.model,onChange:C=>O({...a,model:C.target.value}),placeholder:"qwen2.5-14b-instruct or lmstudio loaded model id"})]}),z?null:s.jsxs("label",{className:"full-width",children:["OpenAI API Key",s.jsx("input",{type:"password",value:a.apiKey,onChange:C=>O({...a,apiKey:C.target.value}),placeholder:"sk-..."})]}),s.jsxs("label",{className:z?"full-width":void 0,children:[z?"Local server base URL":"Base URL",s.jsx("input",{value:a.baseUrl,onChange:C=>O({...a,baseUrl:C.target.value}),placeholder:z?"http://127.0.0.1:1234/v1":"https://api.openai.com/v1"})]}),s.jsxs("label",{children:["Temperature",s.jsx("input",{type:"number",step:"0.1",value:a.temperature,onChange:C=>O({...a,temperature:Number(C.target.value)})})]}),s.jsxs("label",{children:["Max output tokens",s.jsx("input",{type:"number",value:a.maxOutputTokens,onChange:C=>O({...a,maxOutputTokens:Number(C.target.value)})})]})]}),s.jsxs("div",{className:"button-row",children:[s.jsx("button",{type:"button",onClick:()=>Q(),children:"Save local config"}),s.jsx("button",{type:"button",onClick:()=>P(),disabled:U||p,children:p?"Loading models...":"Load model list"}),s.jsx("button",{type:"button",onClick:()=>F(),disabled:U,children:U?"Checking...":"Test connection"})]})]})}function Df({items:a,onRefresh:m,onOpenTrace:p}){return s.jsx(ln,{title:"История нормализаций",subtitle:"Короткий вопрос, confidence, route hint и статус валидации.",actions:s.jsx("button",{type:"button",onClick:()=>m(),children:"Обновить"}),children:s.jsxs("div",{className:"history-list",children:[a.length===0?s.jsx("p",{className:"muted",children:"История пока пустая."}):null,a.map(O=>s.jsxs("button",{type:"button",className:"history-item",onClick:()=>p(O.trace_id),children:[s.jsxs("div",{className:"history-row",children:[s.jsx("strong",{children:O.route_hint??"route: n/a"}),s.jsx("span",{children:O.validation_passed?"schema: ok":"schema: fail"})]}),s.jsx("p",{children:O.question_short}),s.jsxs("div",{className:"history-row",children:[s.jsx("span",{children:O.model}),s.jsx("span",{children:new Date(O.timestamp).toLocaleString("ru-RU")})]})]},O.trace_id))]})})}function yn(a){return a==null||a===""?"—":String(a)}function Of({result:a}){return s.jsx(ln,{title:"Runtime метрики",subtitle:"trace_id, токены, latency и статус валидации.",children:s.jsxs("div",{className:"metrics-grid",children:[s.jsxs("div",{children:[s.jsx("span",{children:"trace_id"}),s.jsx("strong",{children:yn(a?.trace_id)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"request_started_at"}),s.jsx("strong",{children:yn(a?new Date(Date.now()-a.latency_ms).toISOString():null)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"request_finished_at"}),s.jsx("strong",{children:yn(a?new Date().toISOString():null)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"latency_ms"}),s.jsx("strong",{children:yn(a?.latency_ms)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"input_tokens"}),s.jsx("strong",{children:yn(a?.usage?.input_tokens)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"output_tokens"}),s.jsx("strong",{children:yn(a?.usage?.output_tokens)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"total_tokens"}),s.jsx("strong",{children:yn(a?.usage?.total_tokens)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"validation_status"}),s.jsx("strong",{children:a?.validation?.passed?"passed":"failed"})]}),s.jsxs("div",{children:[s.jsx("span",{children:"prompt_version"}),s.jsx("strong",{children:yn(a?.prompt_version)})]}),s.jsxs("div",{children:[s.jsx("span",{children:"schema_version"}),s.jsx("strong",{children:yn(a?.schema_version)})]})]})})}const Af={normalized:"Normalized JSON",fragments:"Fragment View",scope:"Scope View",flags:"Flags View",route:"Route Simulation",raw:"Raw model output",validation:"Validation",logs:"Logs"};function If(a){return a&&typeof a=="object"?a:null}function Ff({tab:a,onTabChange:m,result:p,appLogs:O}){const P=["normalized","fragments","scope","flags","route","raw","validation","logs"],F=If(p?.normalized),Q=String(F?.schema_version??""),Z=Q==="normalized_query_v2"||Q==="normalized_query_v2_0_1"||Q==="normalized_query_v2_0_2",U=Z?{fragments:F?.fragments??[],discarded_fragments:F?.discarded_fragments??[]}:{note:"Fragment View доступен для normalized_query_v2."},z=Z?{message_in_scope:F?.message_in_scope??null,scope_confidence:F?.scope_confidence??null,contains_multiple_tasks:F?.contains_multiple_tasks??null,global_notes:F?.global_notes??null}:{note:"Scope View доступен для normalized_query_v2."},ee=Z?Array.isArray(F?.fragments)?(F?.fragments).map(C=>({fragment_id:C.fragment_id??null,domain_relevance:C.domain_relevance??null,candidate_labels:C.candidate_labels??[],execution_readiness:C.execution_readiness??null,clarification_reason:C.clarification_reason??null,soft_assumption_used:C.soft_assumption_used??[],route_status:C.route_status??null,no_route_reason:C.no_route_reason??null,flags:C.flags??{}})):[]:{note:"Flags View доступен для normalized_query_v2."};return s.jsxs(ln,{title:"Выходные данные",subtitle:"Structured output и диагностические вкладки.",children:[s.jsx("div",{className:"tab-row",children:P.map(C=>s.jsx("button",{type:"button",className:a===C?"tab active":"tab",onClick:()=>m(C),children:Af[C]},C))}),a==="normalized"?s.jsx(ot,{value:p?.normalized??{note:"Нет данных."}}):null,a==="fragments"?s.jsx(ot,{value:U}):null,a==="scope"?s.jsx(ot,{value:z}):null,a==="flags"?s.jsx(ot,{value:ee}):null,a==="route"?s.jsx(ot,{value:p?.route_hint_summary??{note:"Нет данных."}}):null,a==="raw"?s.jsx(ot,{value:p?.raw_model_output??{note:"Нет данных."}}):null,a==="validation"?s.jsx(ot,{value:p?.validation??{note:"Нет данных."}}):null,a==="logs"?s.jsx(ot,{value:O}):null]})}function fc({value:a,onChange:m,presets:p,selectedPresetId:O,onSelectPreset:P,onLoadPreset:F,onSavePreset:Q,onResetDefaults:Z,onDiffPrevious:U,presetName:z,onPresetNameChange:ee,diffSummary:C}){return s.jsxs(ln,{title:"Prompt Manager",subtitle:"Системный, developer и domain уровни управляются отдельно.",children:[s.jsxs("div",{className:"prompt-manager-grid",children:[s.jsxs("label",{children:["Системный prompt",s.jsx("textarea",{value:a.systemPrompt,onChange:H=>m({...a,systemPrompt:H.target.value}),rows:6})]}),s.jsxs("label",{children:["Developer / Instruction prompt",s.jsx("textarea",{value:a.developerPrompt,onChange:H=>m({...a,developerPrompt:H.target.value}),rows:6})]}),s.jsxs("label",{children:["Domain prompt",s.jsx("textarea",{value:a.domainPrompt,onChange:H=>m({...a,domainPrompt:H.target.value}),rows:6})]}),s.jsxs("label",{children:["Schema notes",s.jsx("textarea",{value:a.schemaNotes,onChange:H=>m({...a,schemaNotes:H.target.value}),rows:6})]}),s.jsxs("label",{className:"full-width",children:["Few-shot examples",s.jsx("textarea",{value:a.fewShotExamples,onChange:H=>m({...a,fewShotExamples:H.target.value}),rows:8})]})]}),s.jsxs("div",{className:"button-row",children:[s.jsxs("select",{value:O,onChange:H=>P(H.target.value),children:[s.jsx("option",{value:"",children:"Выберите preset..."}),p.map(H=>s.jsx("option",{value:H.id,children:H.name},H.id))]}),s.jsx("button",{type:"button",onClick:()=>F(),children:"Загрузить preset"}),s.jsx("input",{value:z,onChange:H=>ee(H.target.value),placeholder:"Имя для сохранения"}),s.jsx("button",{type:"button",onClick:()=>Q(),children:"Сохранить preset"}),s.jsx("button",{type:"button",onClick:()=>U(),children:"Diff с предыдущим"}),s.jsx("button",{type:"button",onClick:()=>Z(),children:"Сбросить к default"})]}),C?s.jsx("p",{className:"diff-summary",children:C}):null]})}function Uf({value:a,onChange:m,onApplyBatchFormat:p,onNormalize:O,busy:P,useMock:F,onUseMockChange:Q,errorMessage:Z}){return s.jsxs(ln,{title:"Запрос пользователя",subtitle:"NDC semantic front-end: нормализуем, но не отвечаем за бухгалтерскую суть.",children:[s.jsxs("div",{className:"grid-two",children:[s.jsxs("label",{className:"full-width",children:["Raw user question",s.jsx("textarea",{value:a.userQuestion,onChange:U=>m({...a,userQuestion:U.target.value}),rows:6,placeholder:"Например: По каким покупателям у нас на конец июня висят отгрузки без оплаты..."})]}),s.jsxs("label",{className:"full-width",children:["Batch queries (`;` separator)",s.jsx("textarea",{value:a.batchQuestionsRaw,onChange:U=>m({...a,batchQuestionsRaw:U.target.value}),onBlur:()=>p(),rows:8,placeholder:"Вопрос 1; Вопрос 2; Вопрос 3"})]}),s.jsxs("label",{children:["Optional period context",s.jsx("input",{value:a.periodHint,onChange:U=>m({...a,periodHint:U.target.value})})]}),s.jsxs("label",{children:["Optional business context",s.jsx("input",{value:a.businessContext,onChange:U=>m({...a,businessContext:U.target.value})})]}),s.jsxs("label",{children:["Optional expected route (eval)",s.jsx("input",{value:a.expectedRoute,onChange:U=>m({...a,expectedRoute:U.target.value})})]})]}),s.jsxs("div",{className:"button-row",children:[s.jsxs("label",{className:"checkbox-row",children:[s.jsx("input",{type:"checkbox",checked:F,onChange:U=>Q(U.target.checked)}),"Mock-режим (без вызова OpenAI)"]}),s.jsx("button",{type:"button",onClick:()=>p(),disabled:P||!a.batchQuestionsRaw.trim(),children:"Применить `;` в переносы"}),s.jsx("button",{type:"button",onClick:()=>O(!1),disabled:P||!a.userQuestion.trim(),children:P?"Нормализуем...":"Normalize"}),s.jsx("button",{type:"button",onClick:()=>O(!0),disabled:P||!a.userQuestion.trim(),children:P?"Сохраняем...":"Normalize + Save as test case"})]}),Z?s.jsx("p",{className:"error-text",children:Z}):null]})}function $f({runs:a,selectedRunId:m,onSelectRun:p,onStartRun:O,onFinishRun:P,onRefreshRuns:F,onRunEval:Q,onCopyEvalReport:Z,evalBusy:U,traceItems:z,evalReport:ee}){return s.jsxs(ln,{title:"NDC Run Monitor",subtitle:"Важно: кнопка Запустить run создает только run-сущность. Кнопка eval запускает batch-проверку normalizer v2.0.2.",children:[s.jsxs("div",{className:"button-row",children:[s.jsx("button",{type:"button",onClick:()=>O(),children:"Запустить run"}),s.jsx("button",{type:"button",onClick:()=>P(),disabled:!m,children:"Завершить выбранный run"}),s.jsx("button",{type:"button",onClick:()=>F(),children:"Обновить runs"}),s.jsx("button",{type:"button",onClick:()=>Q(),disabled:U,children:U?"Идет eval v2.0.2...":"Запустить eval v2.0.2"})]}),s.jsxs("div",{className:"runtime-stack",children:[s.jsxs("div",{className:"runtime-runs",children:[a.map(C=>s.jsxs("button",{type:"button",className:m===C.runId?"history-item selected":"history-item",onClick:()=>p(C.runId),children:[s.jsxs("div",{className:"history-row",children:[s.jsx("strong",{children:C.status}),s.jsx("span",{children:C.runId})]}),s.jsxs("div",{className:"history-row",children:[s.jsx("span",{children:C.sessionId}),s.jsx("span",{children:new Date(C.updatedAt).toLocaleString("ru-RU")})]})]},C.runId)),a.length===0?s.jsx("p",{className:"muted",children:"Нет активных запусков."}):null]}),s.jsxs("div",{className:"runtime-details",children:[s.jsx("h3",{children:"Trace выбранного run"}),s.jsx(ot,{value:z}),s.jsxs("div",{className:"eval-report-wrap",children:[s.jsx("h3",{style:{marginTop:12},children:"Отчет eval"}),s.jsx(ot,{value:ee??{note:"Eval пока не запускался"}}),s.jsx("button",{type:"button",className:"copy-cube-button",title:"Скопировать отчет eval",onClick:()=>Z(),children:"⧉"})]})]})]})]})}const Bf={llmProvider:"openai",apiKey:"",model:"gpt-4o-mini",baseUrl:"https://api.openai.com/v1",temperature:0,maxOutputTokens:700},pc={systemPrompt:"Ты semantic-normalizer для бухгалтерского ассистента NDC. Возвращай только JSON по схеме normalized_query_v2_0_2.",developerPrompt:"Сначала делай decomposition сообщения на task fragments, затем определяй domain scope и route-critical flags. Для каждого fragment заполняй execution_readiness + route_status + no_route_reason. Если fragment routable, не оставляй его в no_route.",domainPrompt:"Контур: данные текущего предприятия в 1С/NDC. In-scope: документы, проводки, взаиморасчеты, остатки, периодное закрытие, аномалии и контрольные проверки. Out-of-scope: общая теория, законы и оффтоп.",schemaNotes:"schema_version: normalized_query_v2_0_2. Строгий JSON без дополнительных полей.",fewShotExamples:"Q: Проверь по поставщикам хвосты и разложи цепочку документов/оплат. => fragment in_scope, flags: multi_entity + chain_explanation. Q: Как вообще по ФСБУ? => out_of_scope/generic_accounting."},Hf={userQuestion:"",batchQuestionsRaw:"",periodHint:"",businessContext:"",expectedRoute:""},wi={colors:{backgroundRgb:"18, 18, 18",mainSurfaceRgb:"25, 25, 25",horizontalSurfaceRgb:"30, 30, 30",focusSurfaceRgb:"35, 35, 35",activeRgb:"167, 59, 255",activeTextRgb:"240, 240, 240",textMainRgb:"240, 240, 240",textMutedRgb:"166, 166, 166",dangerRgb:"126, 126, 126",scrollbarTrackRgb:"20, 20, 20",scrollbarThumbRgb:"30, 30, 30",scrollbarThumbHoverRgb:"30, 50, 30"},layout:{modeColumnWidthPx:440,modeToggleWidthPx:188}},mc="ndc_normalizer_session_config_v1",hc="ndc_autoruns_layout_config_v1",Vf="ndc-autoruns-save",ji=["Анализ запроса","Получение данных","Подготовка ответа"],Qf="assistant",ki="normalizer_v2_0_2",vc="address_query_runtime_v1",Wf=["normalized","fragments","scope","flags","route","raw","validation","logs"];function Kf(a){return`[${new Date().toLocaleTimeString("ru-RU")}] ${a}`}function Gf(a,m){if(!m)return"Previous preset is not selected.";const O=["systemPrompt","developerPrompt","domainPrompt","schemaNotes","fewShotExamples"].filter(P=>a[P]!==m[P]).map(P=>`${P}: ${Math.abs(a[P].length-m[P].length)} chars delta`);return O.length===0?"No changes against previous preset.":`Changed fields: ${O.length}. ${O.join(" | ")}`}function qf(){const[a,m]=j.useState(Bf),[p,O]=j.useState(pc),[P,F]=j.useState(Hf),[Q,Z]=j.useState(null),[U,z]=j.useState([]),[ee,C]=j.useState([]),[H,pe]=j.useState("normalized"),[Te,te]=j.useState(!1),[re,Se]=j.useState(!1),[me,Me]=j.useState([]),[Ne,Ae]=j.useState(""),[Fe,Ue]=j.useState([]),[Ke,st]=j.useState(""),[be,xe]=j.useState("NDC custom preset"),[it,Ee]=j.useState(null),[Ve,X]=j.useState(""),[he,ye]=j.useState(!1),[T,W]=j.useState([]),[M,h]=j.useState(""),[S,q]=j.useState([]),[b,ne]=j.useState(!1),[le,de]=j.useState(null),[ue,se]=j.useState(""),[ve,Et]=j.useState(Qf),[on,St]=j.useState(!0),[Kt,xn]=j.useState(!0),[at,ae]=j.useState(!0),[et,Gt]=j.useState(!0),[ut,Pt]=j.useState(!0),[ct,_e]=j.useState(!0),[qt,_n]=j.useState(!0),[sn,Pe]=j.useState(!0),[dt,Ot]=j.useState(!0),[Ge,Yt]=j.useState(!0),[At,mt]=j.useState(!0),[It,nr]=j.useState(!0),[wt,Sn]=j.useState(!0),[Jt,an]=j.useState(!0),[u,_]=j.useState(!0),[V,je]=j.useState(""),[Ft,Ut]=j.useState([]),[Xt,rr]=j.useState(""),[lr,Lr]=j.useState(!1),[wn,un]=j.useState(""),[jn,zr]=j.useState(""),Zt=j.useRef(!1),Vn=j.useRef(!1);j.useEffect(()=>{const y=document.documentElement,{colors:L}=wi;y.style.setProperty("--rgb-background",L.backgroundRgb),y.style.setProperty("--rgb-surface-main",L.mainSurfaceRgb),y.style.setProperty("--rgb-surface-horizontal",L.horizontalSurfaceRgb),y.style.setProperty("--rgb-surface-focus",L.focusSurfaceRgb),y.style.setProperty("--rgb-active",L.activeRgb),y.style.setProperty("--rgb-active-text",L.activeTextRgb),y.style.setProperty("--rgb-text-main",L.textMainRgb),y.style.setProperty("--rgb-text-muted",L.textMutedRgb),y.style.setProperty("--rgb-danger",L.dangerRgb),y.style.setProperty("--rgb-scrollbar-track",L.scrollbarTrackRgb),y.style.setProperty("--rgb-scrollbar-thumb",L.scrollbarThumbRgb),y.style.setProperty("--rgb-scrollbar-thumb-hover",L.scrollbarThumbHoverRgb),y.style.setProperty("--mode-column-width",`${wi.layout.modeColumnWidthPx}px`),y.style.setProperty("--mode-toggle-width",`${wi.layout.modeToggleWidthPx}px`)},[]);const Y=y=>{C(L=>[Kf(y),...L].slice(0,300))};function Dr(){let y=0;un(ji[0]);const L=window.setInterval(()=>{y=Math.min(y+1,ji.length-1),un(ji[y])},650);return()=>window.clearInterval(L)}j.useEffect(()=>{const y=localStorage.getItem(mc);if(y)try{const E=JSON.parse(y);m(ie=>({...ie,llmProvider:E.llmProvider==="local"?"local":"openai",model:E.model??ie.model,baseUrl:E.baseUrl??ie.baseUrl,temperature:E.temperature??ie.temperature,maxOutputTokens:E.maxOutputTokens??ie.maxOutputTokens}))}catch{}const L=localStorage.getItem(hc);if(L)try{const E=JSON.parse(L);(E.uiMode==="assistant"||E.uiMode==="decomposition"||E.uiMode==="autoruns")&&Et(E.uiMode),E.activeTab&&Wf.includes(E.activeTab)&&pe(E.activeTab),typeof E.showAutorunsAssistantMode=="boolean"&&St(E.showAutorunsAssistantMode),typeof E.showAutorunsDecompositionMode=="boolean"&&xn(E.showAutorunsDecompositionMode),typeof E.showAutorunsProgressMode=="boolean"&&ae(E.showAutorunsProgressMode),typeof E.showAutorunsCommentsMode=="boolean"&&Gt(E.showAutorunsCommentsMode),typeof E.showAssistantConnectionMode=="boolean"&&Pt(E.showAssistantConnectionMode),typeof E.showAssistantPromptMode=="boolean"&&_e(E.showAssistantPromptMode),typeof E.showAssistantChatMode=="boolean"&&_n(E.showAssistantChatMode),typeof E.showAssistantSamMode=="boolean"&&Pe(E.showAssistantSamMode),typeof E.showDecompositionConnectionMode=="boolean"&&Ot(E.showDecompositionConnectionMode),typeof E.showDecompositionPromptMode=="boolean"&&Yt(E.showDecompositionPromptMode),typeof E.showDecompositionQueryMode=="boolean"&&mt(E.showDecompositionQueryMode),typeof E.showDecompositionOutputMode=="boolean"&&nr(E.showDecompositionOutputMode),typeof E.showDecompositionMetricsMode=="boolean"&&Sn(E.showDecompositionMetricsMode),typeof E.showDecompositionHistoryMode=="boolean"&&an(E.showDecompositionHistoryMode),typeof E.showDecompositionRuntimeMode=="boolean"&&_(E.showDecompositionRuntimeMode),E.prompts&&(O(ie=>({...ie,...E.prompts})),Vn.current=!0)}catch{}or(),Sl(),sr()},[]);async function or(){try{const y=await Be.loadHistory();z(y.items??[])}catch(y){Y(`History load error: ${y instanceof Error?y.message:String(y)}`)}}async function Sl(){try{const L=(await Be.loadPresets()).presets??[];if(Ue(L),Vn.current){Zt.current=!0;return}if(Zt.current)return;const E=L.find(ie=>ie.prompt_version===ki)??L.find(ie=>ie.id==="default-normalizer-v2_0_2");if(!E){Zt.current=!0,Y(`Preset autoload skipped: ${ki} not found.`);return}st(E.id),Ee(p),O({systemPrompt:E.systemPrompt,developerPrompt:E.developerPrompt,domainPrompt:E.domainPrompt,schemaNotes:E.schemaNotes??"",fewShotExamples:E.fewShotExamples??""}),Zt.current=!0,Y(`Preset autoloaded: ${E.name} (${E.prompt_version}).`)}catch(y){Y(`Presets load error: ${y instanceof Error?y.message:String(y)}`)}}async function sr(){try{const y=await Be.listRuns();W(y.items??[])}catch(y){Y(`Runs load error: ${y instanceof Error?y.message:String(y)}`)}}function bt(){localStorage.setItem(mc,JSON.stringify({model:a.model,llmProvider:a.llmProvider,baseUrl:a.baseUrl,temperature:a.temperature,maxOutputTokens:a.maxOutputTokens})),Y("Local config saved (without API key).")}function wl(){localStorage.setItem(hc,JSON.stringify({uiMode:ve,activeTab:H,showAutorunsAssistantMode:on,showAutorunsDecompositionMode:Kt,showAutorunsProgressMode:at,showAutorunsCommentsMode:et,showAssistantConnectionMode:ut,showAssistantPromptMode:ct,showAssistantChatMode:qt,showAssistantSamMode:sn,showDecompositionConnectionMode:dt,showDecompositionPromptMode:Ge,showDecompositionQueryMode:At,showDecompositionOutputMode:It,showDecompositionMetricsMode:wt,showDecompositionHistoryMode:Jt,showDecompositionRuntimeMode:u,prompts:p})),window.dispatchEvent(new CustomEvent(Vf)),Y("UI layout and prompts saved.")}async function Or(){te(!0),se("");try{const y=await Be.testConnection(a);y.provider==="local"?y.model_found===!0?(Ae(`LOCAL OK - ${y.model}`),Y(`Local model is available: ${y.model} (catalog size=${y.models_count??"n/a"}).`)):y.model_found===!1?(Ae(`LOCAL OK, model not loaded - ${y.model}`),Y(`Local server is reachable, but model '${y.model}' is not in loaded catalog. Use 'Load model list' and select one of loaded models.`)):(Ae(`LOCAL OK (model list unavailable) - ${y.model}`),Y("Local server is reachable, but model catalog could not be verified.")):(Ae(`OPENAI OK - ${y.model}`),Y(`OpenAI connection ok: ${y.model}`))}catch(y){const L=y instanceof Error?y.message:String(y);Ae("Connection error"),se(`Test connection: ${L}`),Y(`Test connection error: ${L}`)}finally{te(!1)}}async function jl(){Se(!0);try{const L=(await Be.listModels(a)).models??[];Me(L),L.length>0&&m(E=>E.model&&L.includes(E.model)?E:{...E,model:L[0]}),Y(`Model catalog loaded (${a.llmProvider}): ${L.length} items.`)}catch(y){const L=y instanceof Error?y.message:String(y);Y(`Load model list error: ${L}`)}finally{Se(!1)}}j.useEffect(()=>{Me([])},[a.llmProvider,a.baseUrl]);async function kl(y){te(!0),se("");try{const L=await Be.normalize({connection:a,prompts:p,promptVersion:"normalizer_v2_0_2",query:{userQuestion:P.userQuestion,periodHint:P.periodHint,businessContext:P.businessContext,expectedRoute:P.expectedRoute},saveAsTestCase:y,useMock:he});Z(L),pe("normalized"),Y(`Normalize done: trace=${L.trace_id}, validation=${L.validation.passed?"passed":"failed"}`),or()}catch(L){const E=L instanceof Error?L.message:String(L);se(`Normalize: ${E}`),Y(`Normalize error: ${E}`)}finally{te(!1)}}function Ar(){const y=Fe.find(L=>L.id===Ke);if(!y){Y("Preset is not selected.");return}Ee(p),O({systemPrompt:y.systemPrompt,developerPrompt:y.developerPrompt,domainPrompt:y.domainPrompt,schemaNotes:y.schemaNotes??"",fewShotExamples:y.fewShotExamples??""}),Y(`Preset loaded: ${y.name}`)}async function Ir(){try{await Be.savePreset({name:be||"NDC preset",prompt_version:"normalizer_v2_0_2",systemPrompt:p.systemPrompt,developerPrompt:p.developerPrompt,domainPrompt:p.domainPrompt,schemaNotes:p.schemaNotes,fewShotExamples:p.fewShotExamples}),Y("Preset saved."),await Sl()}catch(y){Y(`Preset save error: ${y instanceof Error?y.message:String(y)}`)}}function Fr(){O(pc),Y("Prompt panel reset to defaults.")}function Cl(){const y=Gf(p,it);X(y),Y(y)}function Oo(){const y=P.batchQuestionsRaw.split(";").map(L=>L.trim()).filter(Boolean).join(` - -`);y&&(F(L=>({...L,batchQuestionsRaw:y})),Y("Batch field formatted: `;` converted to blank-line separators."))}async function Le(y){try{const E=(await Be.loadTrace(y)).trace,ie=E.parsed_normalized_json??null;Z({trace_id:String(E.trace_id??y),ok:!!E.validation_result?.passed,normalized:ie,route_hint_summary:E.route_hint_summary??(ie?{route_hint:ie.route_hint??null,confidence:ie.confidence?.route_hint??null}:null),raw_model_output:E.raw_model_response??{},validation:E.validation_result??{passed:!1,errors:["validation not found"]},usage:E.usage??{input_tokens:0,output_tokens:0,total_tokens:0},latency_ms:Number(E.latency_ms??0),prompt_version:String(E.prompt_version??"unknown"),schema_version:String(E.schema_version??"unknown")}),pe("raw"),se(""),Y(`Trace opened: ${y}`)}catch(L){const E=L instanceof Error?L.message:String(L);se(`Trace: ${E}`),Y(`Trace open error ${y}: ${E}`)}}async function Ao(){try{const y=await Be.startRun();h(y.run.runId),Y(`Run started: ${y.run.runId}`),Y("Tip: start run does not execute normalize by itself. Use 'Run eval v2.0.2' button."),await sr()}catch(y){Y(`Run start error: ${y instanceof Error?y.message:String(y)}`)}}async function Ur(){if(M)try{await Be.finishRun(M),Y(`Run finished: ${M}`),await sr()}catch(y){Y(`Run finish error: ${y instanceof Error?y.message:String(y)}`)}}async function Nl(){ne(!0),se("");try{Y("Starting eval in v2 contour.");const y=P.batchQuestionsRaw.trim()||P.userQuestion.trim();if(!y)throw new Error("Fill batch field or Raw user question first.");const L=await Be.runEval({connection:a,prompts:p,promptVersion:"normalizer_v2_0_2",mode:"single-pass-strict",rawQuestions:y,useMock:he});de(L.report),Y("Eval v2.0.2 run finished.");const E=L.report;if(E.run_id&&Y(`Eval run id: ${E.run_id}`),E.metrics){const ie=E.metrics;Y(`Eval metrics v2.0.2: schema=${ie.schema_validation_pass_rate??"n/a"}%, route_accuracy=${ie.route_resolution_accuracy??"n/a"}%, no_route_precision=${ie.no_route_precision??"n/a"}%, state_consistency=${ie.execution_state_consistency_rate??"n/a"}%`)}await or()}catch(y){const L=y instanceof Error?y.message:String(y);L.includes("Legacy eval runner supports normalized_query_v1 only")?(de({status:"plan_only",prompt_version:"normalizer_v2",reason:"backend eval runner is still legacy-v1 only",plan_file:"reports/v2_pilot_eval_plan.md",next_steps:["run cheap mock sanity for schema/fragment/scope","run small real batch (10-15 messages, temperature=0)","run challenge-30 replay with v2 metrics"]}),Y("Backend is legacy-only for eval right now. Showing v2 pilot plan.")):(se(`Eval: ${L}`),Y(`Eval run error: ${L}`))}finally{ne(!1)}}async function ir(){try{const y=JSON.stringify(le??{},null,2);await navigator.clipboard.writeText(y),Y("Eval report copied to clipboard.")}catch(y){Y(`Eval report copy error: ${y instanceof Error?y.message:String(y)}`)}}function Io(){je(""),Ut([]),rr(""),un(""),zr(""),Y("Assistant session reset.")}async function El(){const y=Xt.trim();if(!y)return;Lr(!0),zr(""),rr(""),Ut(E=>[...E,{message_id:`local-${Date.now()}`,session_id:V||"pending",role:"user",text:y,reply_type:null,created_at:new Date().toISOString(),trace_id:null,debug:null}]);const L=Dr();try{const E=await Be.sendAssistantMessage({connection:a,prompts:p,userMessage:y,sessionId:V||void 0,promptVersion:vc,useMock:he});je(E.session_id),Ut(E.conversation),un("Ответ готов"),Y(`Assistant reply received: trace=${E.debug.trace_id}`)}catch(E){const ie=E instanceof Error?E.message:String(E);zr(ie),un("Ошибка ассистента"),Y(`Assistant error: ${ie}`)}finally{L(),Lr(!1)}}return j.useEffect(()=>{if(!M){q([]);return}Be.runTrace(M).then(y=>q(y.items)).catch(y=>Y(`Run trace error: ${y instanceof Error?y.message:String(y)}`))},[M]),s.jsxs("main",{className:`app-root ${ve==="assistant"||ve==="decomposition"||ve==="autoruns"?"app-root-autoruns":""}`,children:[s.jsxs("header",{className:"app-topbar",children:[s.jsxs("div",{className:"mode-switch-row",children:[s.jsx("button",{type:"button",className:ve==="assistant"?"tab active":"tab",onClick:()=>Et("assistant"),children:"Ассистент"}),s.jsx("button",{type:"button",className:ve==="decomposition"?"tab active":"tab",onClick:()=>Et("decomposition"),children:"Декомпозиция"}),s.jsx("button",{type:"button",className:ve==="autoruns"?"tab active":"tab",onClick:()=>Et("autoruns"),children:"История автопрогонов"}),s.jsx("button",{type:"button",className:"tab",onClick:wl,children:"Сохранить"})]}),ve==="assistant"?s.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[s.jsx("button",{type:"button",className:ut?"tab active":"tab",onClick:()=>Pt(y=>!y),children:"LLM Connector"}),s.jsx("button",{type:"button",className:ct?"tab active":"tab",onClick:()=>_e(y=>!y),children:"Prompt Manager"}),s.jsx("button",{type:"button",className:qt?"tab active":"tab",onClick:()=>_n(y=>!y),children:"Режим ассистента"}),s.jsx("button",{type:"button",className:sn?"tab active":"tab",onClick:()=>Pe(y=>!y),children:"SAM"})]}):ve==="decomposition"?s.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[s.jsx("button",{type:"button",className:dt?"tab active":"tab",onClick:()=>Ot(y=>!y),children:"LLM"}),s.jsx("button",{type:"button",className:Ge?"tab active":"tab",onClick:()=>Yt(y=>!y),children:"Prompt"}),s.jsx("button",{type:"button",className:At?"tab active":"tab",onClick:()=>mt(y=>!y),children:"Запрос"}),s.jsx("button",{type:"button",className:It?"tab active":"tab",onClick:()=>nr(y=>!y),children:"Выход"}),s.jsx("button",{type:"button",className:wt?"tab active":"tab",onClick:()=>Sn(y=>!y),children:"Метрики"}),s.jsx("button",{type:"button",className:Jt?"tab active":"tab",onClick:()=>an(y=>!y),children:"История"}),s.jsx("button",{type:"button",className:u?"tab active":"tab",onClick:()=>_(y=>!y),children:"NDC Run Monitor"})]}):ve==="autoruns"?s.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[s.jsx("button",{type:"button",className:on?"tab active":"tab",onClick:()=>St(y=>!y),children:"Режим ассистента"}),s.jsx("button",{type:"button",className:Kt?"tab active":"tab",onClick:()=>xn(y=>!y),children:"Режим декомпозиции"}),s.jsx("button",{type:"button",className:at?"tab active":"tab",onClick:()=>ae(y=>!y),children:"Прогресс/регресс"}),s.jsx("button",{type:"button",className:et?"tab active":"tab",onClick:()=>Gt(y=>!y),children:"Комментарии"})]}):null]}),ve==="assistant"?s.jsx("div",{className:"layout-grid layout-grid-mode-columns",children:s.jsxs("div",{className:"mode-columns",children:[ut?s.jsx("div",{className:"mode-col",children:s.jsx(dc,{value:a,modelOptions:me,modelsBusy:re,onChange:m,onReloadModels:jl,onSaveLocalConfig:bt,onTestConnection:Or,lastStatus:Ne,busy:Te||lr})}):null,ct?s.jsx("div",{className:"mode-col mode-col-wide",children:s.jsx(fc,{value:p,onChange:O,presets:Fe,selectedPresetId:Ke,onSelectPreset:st,onLoadPreset:Ar,onSavePreset:Ir,onResetDefaults:Fr,onDiffPrevious:Cl,presetName:be,onPresetNameChange:xe,diffSummary:Ve})}):null,qt?s.jsx("div",{className:"mode-col mode-col-xwide",children:s.jsx(zf,{sessionId:V,conversation:Ft,inputValue:Xt,onInputChange:rr,useMock:he,onUseMockChange:ye,onSend:El,onClear:Io,busy:lr,statusText:wn,errorMessage:jn})}):null,sn?s.jsx("div",{className:"mode-col",children:s.jsx(vf,{sessionId:V,conversation:Ft,statusText:wn,errorMessage:jn,useMock:he,appLogs:ee})}):null,!ut&&!ct&&!qt&&!sn?s.jsx("div",{className:"mode-columns-empty",children:"Все панели режима ассистента скрыты. Включите нужные блоки справа в шапке."}):null]})}):ve==="decomposition"?s.jsx("div",{className:"layout-grid layout-grid-mode-columns",children:s.jsxs("div",{className:"mode-columns",children:[dt?s.jsx("div",{className:"mode-col",children:s.jsx(dc,{value:a,modelOptions:me,modelsBusy:re,onChange:m,onReloadModels:jl,onSaveLocalConfig:bt,onTestConnection:Or,lastStatus:Ne,busy:Te})}):null,Ge?s.jsx("div",{className:"mode-col mode-col-wide",children:s.jsx(fc,{value:p,onChange:O,presets:Fe,selectedPresetId:Ke,onSelectPreset:st,onLoadPreset:Ar,onSavePreset:Ir,onResetDefaults:Fr,onDiffPrevious:Cl,presetName:be,onPresetNameChange:xe,diffSummary:Ve})}):null,At?s.jsx("div",{className:"mode-col",children:s.jsx(Uf,{value:P,onChange:F,onApplyBatchFormat:Oo,onNormalize:kl,busy:Te,useMock:he,onUseMockChange:ye,errorMessage:ue})}):null,It?s.jsx("div",{className:"mode-col mode-col-xwide",children:s.jsx(Ff,{tab:H,onTabChange:pe,result:Q,appLogs:ee})}):null,wt?s.jsx("div",{className:"mode-col",children:s.jsx(Of,{result:Q})}):null,Jt?s.jsx("div",{className:"mode-col",children:s.jsx(Df,{items:U,onRefresh:or,onOpenTrace:Le})}):null,u?s.jsx("div",{className:"mode-col mode-col-xwide",children:s.jsx($f,{runs:T,selectedRunId:M,onSelectRun:h,onStartRun:Ao,onFinishRun:Ur,onRefreshRuns:sr,onRunEval:Nl,onCopyEvalReport:ir,evalBusy:b,traceItems:S,evalReport:le})}):null,!dt&&!Ge&&!At&&!It&&!wt&&!Jt&&!u?s.jsx("div",{className:"mode-columns-empty",children:"Все панели режима декомпозиции скрыты. Включите нужные блоки справа в шапке."}):null]})}):s.jsx("div",{className:"layout-grid layout-grid-autoruns",children:s.jsx(kf,{connection:a,prompts:p,assistantPromptVersion:vc,decompositionPromptVersion:ki,showAssistantMode:on,showDecompositionMode:Kt,showProgressMode:at,showCommentsMode:et,onLog:Y})})]})}pf.createRoot(document.getElementById("root")).render(s.jsx(of.StrictMode,{children:s.jsx(qf,{})})); diff --git a/llm_normalizer/frontend/dist/assets/index-DNcr9aV9.js b/llm_normalizer/frontend/dist/assets/index-DNcr9aV9.js new file mode 100644 index 0000000..b5c9f90 --- /dev/null +++ b/llm_normalizer/frontend/dist/assets/index-DNcr9aV9.js @@ -0,0 +1,13 @@ +(function(){const h=document.createElement("link").relList;if(h&&h.supports&&h.supports("modulepreload"))return;for(const E of document.querySelectorAll('link[rel="modulepreload"]'))O(E);new MutationObserver(E=>{for(const D of E)if(D.type==="childList")for(const K of D.addedNodes)K.tagName==="LINK"&&K.rel==="modulepreload"&&O(K)}).observe(document,{childList:!0,subtree:!0});function p(E){const D={};return E.integrity&&(D.integrity=E.integrity),E.referrerPolicy&&(D.referrerPolicy=E.referrerPolicy),E.crossOrigin==="use-credentials"?D.credentials="include":E.crossOrigin==="anonymous"?D.credentials="omit":D.credentials="same-origin",D}function O(E){if(E.ep)return;E.ep=!0;const D=p(E);fetch(E.href,D)}})();function Cc(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var Si={exports:{}},Ts={},wi={exports:{}},ue={};var tc;function ff(){if(tc)return ue;tc=1;var i=Symbol.for("react.element"),h=Symbol.for("react.portal"),p=Symbol.for("react.fragment"),O=Symbol.for("react.strict_mode"),E=Symbol.for("react.profiler"),D=Symbol.for("react.provider"),K=Symbol.for("react.context"),Z=Symbol.for("react.forward_ref"),B=Symbol.for("react.suspense"),I=Symbol.for("react.memo"),q=Symbol.for("react.lazy"),T=Symbol.iterator;function H(g){return g===null||typeof g!="object"?null:(g=T&&g[T]||g["@@iterator"],typeof g=="function"?g:null)}var fe={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},ye=Object.assign,ne={};function X(g,j,ee){this.props=g,this.context=j,this.refs=ne,this.updater=ee||fe}X.prototype.isReactComponent={},X.prototype.setState=function(g,j){if(typeof g!="object"&&typeof g!="function"&&g!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,g,j,"setState")},X.prototype.forceUpdate=function(g){this.updater.enqueueForceUpdate(this,g,"forceUpdate")};function G(){}G.prototype=X.prototype;function se(g,j,ee){this.props=g,this.context=j,this.refs=ne,this.updater=ee||fe}var Se=se.prototype=new G;Se.constructor=se,ye(Se,X.prototype),Se.isPureReactComponent=!0;var Le=Array.isArray,Oe=Object.prototype.hasOwnProperty,$e={current:null},Ue={key:!0,ref:!0,__self:!0,__source:!0};function Ge(g,j,ee){var oe,le={},ae=null,re=null;if(j!=null)for(oe in j.ref!==void 0&&(re=j.ref),j.key!==void 0&&(ae=""+j.key),j)Oe.call(j,oe)&&!Ue.hasOwnProperty(oe)&&(le[oe]=j[oe]);var he=arguments.length-2;if(he===1)le.children=ee;else if(1>>1,j=A[g];if(0>>1;gE(le,L))aeE(re,le)?(A[g]=re,A[ae]=L,g=ae):(A[g]=le,A[oe]=L,g=oe);else if(aeE(re,L))A[g]=re,A[ae]=L,g=ae;else break e}}return R}function E(A,R){var L=A.sortIndex-R.sortIndex;return L!==0?L:A.id-R.id}if(typeof performance=="object"&&typeof performance.now=="function"){var D=performance;i.unstable_now=function(){return D.now()}}else{var K=Date,Z=K.now();i.unstable_now=function(){return K.now()-Z}}var B=[],I=[],q=1,T=null,H=3,fe=!1,ye=!1,ne=!1,X=typeof setTimeout=="function"?setTimeout:null,G=typeof clearTimeout=="function"?clearTimeout:null,se=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function Se(A){for(var R=p(I);R!==null;){if(R.callback===null)O(I);else if(R.startTime<=A)O(I),R.sortIndex=R.expirationTime,h(B,R);else break;R=p(I)}}function Le(A){if(ne=!1,Se(A),!ye)if(p(B)!==null)ye=!0,xe(Oe);else{var R=p(I);R!==null&&me(Le,R.startTime-A)}}function Oe(A,R){ye=!1,ne&&(ne=!1,G(Ge),Ge=-1),fe=!0;var L=H;try{for(Se(R),T=p(B);T!==null&&(!(T.expirationTime>R)||A&&!Ct());){var g=T.callback;if(typeof g=="function"){T.callback=null,H=T.priorityLevel;var j=g(T.expirationTime<=R);R=i.unstable_now(),typeof j=="function"?T.callback=j:T===p(B)&&O(B),Se(R)}else O(B);T=p(B)}if(T!==null)var ee=!0;else{var oe=p(I);oe!==null&&me(Le,oe.startTime-R),ee=!1}return ee}finally{T=null,H=L,fe=!1}}var $e=!1,Ue=null,Ge=-1,Ze=5,ft=-1;function Ct(){return!(i.unstable_now()-ftA||125g?(A.sortIndex=L,h(I,A),p(B)===null&&A===p(I)&&(ne?(G(Ge),Ge=-1):ne=!0,me(Le,L-g))):(A.sortIndex=j,h(B,A),ye||fe||(ye=!0,xe(Oe))),A},i.unstable_shouldYield=Ct,i.unstable_wrapCallback=function(A){var R=H;return function(){var L=H;H=R;try{return A.apply(this,arguments)}finally{H=L}}}})(Ci)),Ci}var lc;function vf(){return lc||(lc=1,ki.exports=gf()),ki.exports}var ic;function yf(){if(ic)return kt;ic=1;var i=Mi(),h=vf();function p(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),B=Object.prototype.hasOwnProperty,I=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,q={},T={};function H(e){return B.call(T,e)?!0:B.call(q,e)?!1:I.test(e)?T[e]=!0:(q[e]=!0,!1)}function fe(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function ye(e,t,n,r){if(t===null||typeof t>"u"||fe(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function ne(e,t,n,r,s,l,a){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=s,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=l,this.removeEmptyString=a}var X={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){X[e]=new ne(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];X[t]=new ne(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){X[e]=new ne(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){X[e]=new ne(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){X[e]=new ne(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){X[e]=new ne(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){X[e]=new ne(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){X[e]=new ne(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){X[e]=new ne(e,5,!1,e.toLowerCase(),null,!1,!1)});var G=/[\-:]([a-z])/g;function se(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(G,se);X[t]=new ne(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(G,se);X[t]=new ne(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(G,se);X[t]=new ne(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){X[e]=new ne(e,1,!1,e.toLowerCase(),null,!1,!1)}),X.xlinkHref=new ne("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){X[e]=new ne(e,1,!1,e.toLowerCase(),null,!0,!0)});function Se(e,t,n,r){var s=X.hasOwnProperty(t)?X[t]:null;(s!==null?s.type!==0:r||!(2c||s[a]!==l[c]){var d=` +`+s[a].replace(" at new "," at ");return e.displayName&&d.includes("")&&(d=d.replace("",e.displayName)),d}while(1<=a&&0<=c);break}}}finally{ee=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?j(e):""}function le(e){switch(e.tag){case 5:return j(e.type);case 16:return j("Lazy");case 13:return j("Suspense");case 19:return j("SuspenseList");case 0:case 2:case 15:return e=oe(e.type,!1),e;case 11:return e=oe(e.type.render,!1),e;case 1:return e=oe(e.type,!0),e;default:return""}}function ae(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ue:return"Fragment";case $e:return"Portal";case Ze:return"Profiler";case Ge:return"StrictMode";case ie:return"Suspense";case Be:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Ct:return(e.displayName||"Context")+".Consumer";case ft:return(e._context.displayName||"Context")+".Provider";case rt:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Re:return t=e.displayName||null,t!==null?t:ae(e.type)||"Memo";case xe:t=e._payload,e=e._init;try{return ae(e(t))}catch{}}return null}function re(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ae(t);case 8:return t===Ge?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function he(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ce(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function ke(e){var t=ce(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var s=n.get,l=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(a){r=""+a,l.call(this,a)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(a){r=""+a},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function zt(e){e._valueTracker||(e._valueTracker=ke(e))}function wn(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ce(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function cn(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Nt(e,t){var n=t.checked;return L({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Jt(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=he(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function jn(e,t){t=t.checked,t!=null&&Se(e,"checked",t,!1)}function bt(e,t){jn(e,t);var n=he(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Cn(e,t.type,n):t.hasOwnProperty("defaultValue")&&Cn(e,t.type,he(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function kn(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Cn(e,t,n){(t!=="number"||cn(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Et=Array.isArray;function Dt(e,t,n,r){if(e=e.options,t){t={};for(var s=0;s"+t.valueOf().toString()+"",t=$t.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Ee(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var pn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Pt=["Webkit","ms","Moz","O"];Object.keys(pn).forEach(function(e){Pt.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),pn[t]=pn[e]})});function cr(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||pn.hasOwnProperty(e)&&pn[e]?(""+t).trim():t+"px"}function gt(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,s=cr(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,s):e[n]=s}}var de=L({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function We(e,t){if(t){if(de[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(p(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(p(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(p(61))}if(t.style!=null&&typeof t.style!="object")throw Error(p(62))}}function mn(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Rt=null;function Zt(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var st=null,Ft=null,Je=null;function Ve(e){if(e=hs(e)){if(typeof st!="function")throw Error(p(280));var t=e.stateNode;t&&(t=Ys(t),st(e.stateNode,e.type,t))}}function je(e){Ft?Je?Je.push(e):Je=[e]:Ft=e}function En(){if(Ft){var e=Ft,t=Je;if(Je=Ft=null,Ve(e),t)for(e=0;e>>=0,e===0?32:31-(Ec(e)/Pc|0)|0}var Ls=64,As=4194304;function Yr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Os(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,s=e.suspendedLanes,l=e.pingedLanes,a=n&268435455;if(a!==0){var c=a&~s;c!==0?r=Yr(c):(l&=a,l!==0&&(r=Yr(l)))}else a=n&~s,a!==0?r=Yr(a):l!==0&&(r=Yr(l));if(r===0)return 0;if(t!==0&&t!==r&&(t&s)===0&&(s=r&-r,l=t&-t,s>=l||s===16&&(l&4194240)!==0))return t;if((r&4)!==0&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Xr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-pe(t),e[t]=n}function Ic(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=ls),Gi=" ",Ji=!1;function bi(e,t){switch(e){case"keyup":return ld.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Yi(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Tr=!1;function ad(e,t){switch(e){case"compositionend":return Yi(t);case"keypress":return t.which!==32?null:(Ji=!0,Gi);case"textInput":return e=t.data,e===Gi&&Ji?null:e;default:return null}}function ud(e,t){if(Tr)return e==="compositionend"||!tl&&bi(e,t)?(e=Hi(),Us=Jo=Gn=null,Tr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=sa(n)}}function la(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?la(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ia(){for(var e=window,t=cn();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=cn(e.document)}return t}function sl(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function yd(e){var t=ia(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&la(n.ownerDocument.documentElement,n)){if(r!==null&&sl(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var s=n.textContent.length,l=Math.min(r.start,s);r=r.end===void 0?l:Math.min(r.end,s),!e.extend&&l>r&&(s=r,r=l,l=s),s=oa(n,l);var a=oa(n,r);s&&a&&(e.rangeCount!==1||e.anchorNode!==s.node||e.anchorOffset!==s.offset||e.focusNode!==a.node||e.focusOffset!==a.offset)&&(t=t.createRange(),t.setStart(s.node,s.offset),e.removeAllRanges(),l>r?(e.addRange(t),e.extend(a.node,a.offset)):(t.setEnd(a.node,a.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Mr=null,ol=null,cs=null,ll=!1;function aa(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ll||Mr==null||Mr!==cn(r)||(r=Mr,"selectionStart"in r&&sl(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),cs&&us(cs,r)||(cs=r,r=Gs(ol,"onSelect"),0zr||(e.current=yl[zr],yl[zr]=null,zr--)}function Pe(e,t){zr++,yl[zr]=e.current,e.current=t}var Xn={},lt=Yn(Xn),xt=Yn(!1),hr=Xn;function Dr(e,t){var n=e.type.contextTypes;if(!n)return Xn;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var s={},l;for(l in n)s[l]=t[l];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=s),s}function _t(e){return e=e.childContextTypes,e!=null}function Xs(){Me(xt),Me(lt)}function ja(e,t,n){if(lt.current!==Xn)throw Error(p(168));Pe(lt,t),Pe(xt,n)}function ka(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var s in r)if(!(s in t))throw Error(p(108,re(e)||"Unknown",s));return L({},n,r)}function Zs(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Xn,hr=lt.current,Pe(lt,e),Pe(xt,xt.current),!0}function Ca(e,t,n){var r=e.stateNode;if(!r)throw Error(p(169));n?(e=ka(e,t,hr),r.__reactInternalMemoizedMergedChildContext=e,Me(xt),Me(lt),Pe(lt,e)):Me(xt),Pe(xt,n)}var Mn=null,eo=!1,xl=!1;function Na(e){Mn===null?Mn=[e]:Mn.push(e)}function Td(e){eo=!0,Na(e)}function Zn(){if(!xl&&Mn!==null){xl=!0;var e=0,t=Ce;try{var n=Mn;for(Ce=1;e>=a,s-=a,In=1<<32-pe(t)+s|n<te?(Xe=b,b=null):Xe=b.sibling;var we=k(v,b,y[te],M);if(we===null){b===null&&(b=Xe);break}e&&b&&we.alternate===null&&t(v,b),m=l(we,m,te),J===null?W=we:J.sibling=we,J=we,b=Xe}if(te===y.length)return n(v,b),Ae&&vr(v,te),W;if(b===null){for(;tete?(Xe=b,b=null):Xe=b.sibling;var ar=k(v,b,we.value,M);if(ar===null){b===null&&(b=Xe);break}e&&b&&ar.alternate===null&&t(v,b),m=l(ar,m,te),J===null?W=ar:J.sibling=ar,J=ar,b=Xe}if(we.done)return n(v,b),Ae&&vr(v,te),W;if(b===null){for(;!we.done;te++,we=y.next())we=N(v,we.value,M),we!==null&&(m=l(we,m,te),J===null?W=we:J.sibling=we,J=we);return Ae&&vr(v,te),W}for(b=r(v,b);!we.done;te++,we=y.next())we=$(b,v,te,we.value,M),we!==null&&(e&&we.alternate!==null&&b.delete(we.key===null?te:we.key),m=l(we,m,te),J===null?W=we:J.sibling=we,J=we);return e&&b.forEach(function(df){return t(v,df)}),Ae&&vr(v,te),W}function He(v,m,y,M){if(typeof y=="object"&&y!==null&&y.type===Ue&&y.key===null&&(y=y.props.children),typeof y=="object"&&y!==null){switch(y.$$typeof){case Oe:e:{for(var W=y.key,J=m;J!==null;){if(J.key===W){if(W=y.type,W===Ue){if(J.tag===7){n(v,J.sibling),m=s(J,y.props.children),m.return=v,v=m;break e}}else if(J.elementType===W||typeof W=="object"&&W!==null&&W.$$typeof===xe&&Ia(W)===J.type){n(v,J.sibling),m=s(J,y.props),m.ref=gs(v,J,y),m.return=v,v=m;break e}n(v,J);break}else t(v,J);J=J.sibling}y.type===Ue?(m=Cr(y.props.children,v.mode,M,y.key),m.return=v,v=m):(M=Ro(y.type,y.key,y.props,null,v.mode,M),M.ref=gs(v,m,y),M.return=v,v=M)}return a(v);case $e:e:{for(J=y.key;m!==null;){if(m.key===J)if(m.tag===4&&m.stateNode.containerInfo===y.containerInfo&&m.stateNode.implementation===y.implementation){n(v,m.sibling),m=s(m,y.children||[]),m.return=v,v=m;break e}else{n(v,m);break}else t(v,m);m=m.sibling}m=gi(y,v.mode,M),m.return=v,v=m}return a(v);case xe:return J=y._init,He(v,m,J(y._payload),M)}if(Et(y))return V(v,m,y,M);if(R(y))return Q(v,m,y,M);so(v,y)}return typeof y=="string"&&y!==""||typeof y=="number"?(y=""+y,m!==null&&m.tag===6?(n(v,m.sibling),m=s(m,y),m.return=v,v=m):(n(v,m),m=hi(y,v.mode,M),m.return=v,v=m),a(v)):n(v,m)}return He}var Br=La(!0),Aa=La(!1),oo=Yn(null),lo=null,Hr=null,Cl=null;function Nl(){Cl=Hr=lo=null}function El(e){var t=oo.current;Me(oo),e._currentValue=t}function Pl(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Vr(e,t){lo=e,Cl=Hr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(St=!0),e.firstContext=null)}function Wt(e){var t=e._currentValue;if(Cl!==e)if(e={context:e,memoizedValue:t,next:null},Hr===null){if(lo===null)throw Error(p(308));Hr=e,lo.dependencies={lanes:0,firstContext:e}}else Hr=Hr.next=e;return t}var yr=null;function Rl(e){yr===null?yr=[e]:yr.push(e)}function Oa(e,t,n,r){var s=t.interleaved;return s===null?(n.next=n,Rl(t)):(n.next=s.next,s.next=n),t.interleaved=n,An(e,r)}function An(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var er=!1;function Tl(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function za(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function On(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function tr(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,(_e&2)!==0){var s=r.pending;return s===null?t.next=t:(t.next=s.next,s.next=t),r.pending=t,An(e,n)}return s=r.interleaved,s===null?(t.next=t,Rl(r)):(t.next=s.next,s.next=t),r.interleaved=t,An(e,n)}function io(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Qo(e,n)}}function Da(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var s=null,l=null;if(n=n.firstBaseUpdate,n!==null){do{var a={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};l===null?s=l=a:l=l.next=a,n=n.next}while(n!==null);l===null?s=l=t:l=l.next=t}else s=l=t;n={baseState:r.baseState,firstBaseUpdate:s,lastBaseUpdate:l,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ao(e,t,n,r){var s=e.updateQueue;er=!1;var l=s.firstBaseUpdate,a=s.lastBaseUpdate,c=s.shared.pending;if(c!==null){s.shared.pending=null;var d=c,_=d.next;d.next=null,a===null?l=_:a.next=_,a=d;var C=e.alternate;C!==null&&(C=C.updateQueue,c=C.lastBaseUpdate,c!==a&&(c===null?C.firstBaseUpdate=_:c.next=_,C.lastBaseUpdate=d))}if(l!==null){var N=s.baseState;a=0,C=_=d=null,c=l;do{var k=c.lane,$=c.eventTime;if((r&k)===k){C!==null&&(C=C.next={eventTime:$,lane:0,tag:c.tag,payload:c.payload,callback:c.callback,next:null});e:{var V=e,Q=c;switch(k=t,$=n,Q.tag){case 1:if(V=Q.payload,typeof V=="function"){N=V.call($,N,k);break e}N=V;break e;case 3:V.flags=V.flags&-65537|128;case 0:if(V=Q.payload,k=typeof V=="function"?V.call($,N,k):V,k==null)break e;N=L({},N,k);break e;case 2:er=!0}}c.callback!==null&&c.lane!==0&&(e.flags|=64,k=s.effects,k===null?s.effects=[c]:k.push(c))}else $={eventTime:$,lane:k,tag:c.tag,payload:c.payload,callback:c.callback,next:null},C===null?(_=C=$,d=N):C=C.next=$,a|=k;if(c=c.next,c===null){if(c=s.shared.pending,c===null)break;k=c,c=k.next,k.next=null,s.lastBaseUpdate=k,s.shared.pending=null}}while(!0);if(C===null&&(d=N),s.baseState=d,s.firstBaseUpdate=_,s.lastBaseUpdate=C,t=s.shared.interleaved,t!==null){s=t;do a|=s.lane,s=s.next;while(s!==t)}else l===null&&(s.shared.lanes=0);Sr|=a,e.lanes=a,e.memoizedState=N}}function $a(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Ol.transition;Ol.transition={};try{e(!1),t()}finally{Ce=n,Ol.transition=r}}function ru(){return Kt().memoizedState}function Ad(e,t,n){var r=or(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},su(e))ou(t,n);else if(n=Oa(e,t,n,r),n!==null){var s=ht();un(n,e,r,s),lu(n,t,r)}}function Od(e,t,n){var r=or(e),s={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(su(e))ou(t,s);else{var l=e.alternate;if(e.lanes===0&&(l===null||l.lanes===0)&&(l=t.lastRenderedReducer,l!==null))try{var a=t.lastRenderedState,c=l(a,n);if(s.hasEagerState=!0,s.eagerState=c,rn(c,a)){var d=t.interleaved;d===null?(s.next=s,Rl(t)):(s.next=d.next,d.next=s),t.interleaved=s;return}}catch{}n=Oa(e,t,s,r),n!==null&&(s=ht(),un(n,e,r,s),lu(n,t,r))}}function su(e){var t=e.alternate;return e===De||t!==null&&t===De}function ou(e,t){_s=fo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function lu(e,t,n){if((n&4194240)!==0){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Qo(e,n)}}var ho={readContext:Wt,useCallback:it,useContext:it,useEffect:it,useImperativeHandle:it,useInsertionEffect:it,useLayoutEffect:it,useMemo:it,useReducer:it,useRef:it,useState:it,useDebugValue:it,useDeferredValue:it,useTransition:it,useMutableSource:it,useSyncExternalStore:it,useId:it,unstable_isNewReconciler:!1},zd={readContext:Wt,useCallback:function(e,t){return xn().memoizedState=[e,t===void 0?null:t],e},useContext:Wt,useEffect:Ja,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,po(4194308,4,Xa.bind(null,t,e),n)},useLayoutEffect:function(e,t){return po(4194308,4,e,t)},useInsertionEffect:function(e,t){return po(4,2,e,t)},useMemo:function(e,t){var n=xn();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=xn();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Ad.bind(null,De,e),[r.memoizedState,e]},useRef:function(e){var t=xn();return e={current:e},t.memoizedState=e},useState:qa,useDebugValue:Hl,useDeferredValue:function(e){return xn().memoizedState=e},useTransition:function(){var e=qa(!1),t=e[0];return e=Ld.bind(null,e[1]),xn().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=De,s=xn();if(Ae){if(n===void 0)throw Error(p(407));n=n()}else{if(n=t(),Ye===null)throw Error(p(349));(_r&30)!==0||Ha(r,t,n)}s.memoizedState=n;var l={value:n,getSnapshot:t};return s.queue=l,Ja(Qa.bind(null,r,l,e),[e]),r.flags|=2048,js(9,Va.bind(null,r,l,n,t),void 0,null),n},useId:function(){var e=xn(),t=Ye.identifierPrefix;if(Ae){var n=Ln,r=In;n=(r&~(1<<32-pe(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Ss++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=a.createElement(n,{is:r.is}):(e=a.createElement(n),n==="select"&&(a=e,r.multiple?a.multiple=!0:r.size&&(a.size=r.size))):e=a.createElementNS(e,n),e[vn]=t,e[ms]=r,Nu(e,t,!1,!1),t.stateNode=e;e:{switch(a=mn(n,r),n){case"dialog":Te("cancel",e),Te("close",e),s=r;break;case"iframe":case"object":case"embed":Te("load",e),s=r;break;case"video":case"audio":for(s=0;sGr&&(t.flags|=128,r=!0,ks(l,!1),t.lanes=4194304)}else{if(!r)if(e=uo(a),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),ks(l,!0),l.tail===null&&l.tailMode==="hidden"&&!a.alternate&&!Ae)return at(t),null}else 2*f()-l.renderingStartTime>Gr&&n!==1073741824&&(t.flags|=128,r=!0,ks(l,!1),t.lanes=4194304);l.isBackwards?(a.sibling=t.child,t.child=a):(n=l.last,n!==null?n.sibling=a:t.child=a,l.last=a)}return l.tail!==null?(t=l.tail,l.rendering=t,l.tail=t.sibling,l.renderingStartTime=f(),t.sibling=null,n=ze.current,Pe(ze,r?n&1|2:n&1),t):(at(t),null);case 22:case 23:return fi(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&(t.mode&1)!==0?(Ot&1073741824)!==0&&(at(t),t.subtreeFlags&6&&(t.flags|=8192)):at(t),null;case 24:return null;case 25:return null}throw Error(p(156,t.tag))}function Qd(e,t){switch(Sl(t),t.tag){case 1:return _t(t.type)&&Xs(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Qr(),Me(xt),Me(lt),Al(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 5:return Il(t),null;case 13:if(Me(ze),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(p(340));Ur()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Me(ze),null;case 4:return Qr(),null;case 10:return El(t.type._context),null;case 22:case 23:return fi(),null;case 24:return null;default:return null}}var xo=!1,ut=!1,Wd=typeof WeakSet=="function"?WeakSet:Set,U=null;function Kr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Fe(e,t,r)}else n.current=null}function ei(e,t,n){try{n()}catch(r){Fe(e,t,r)}}var Ru=!1;function Kd(e,t){if(fl=$s,e=ia(),sl(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var s=r.anchorOffset,l=r.focusNode;r=r.focusOffset;try{n.nodeType,l.nodeType}catch{n=null;break e}var a=0,c=-1,d=-1,_=0,C=0,N=e,k=null;t:for(;;){for(var $;N!==n||s!==0&&N.nodeType!==3||(c=a+s),N!==l||r!==0&&N.nodeType!==3||(d=a+r),N.nodeType===3&&(a+=N.nodeValue.length),($=N.firstChild)!==null;)k=N,N=$;for(;;){if(N===e)break t;if(k===n&&++_===s&&(c=a),k===l&&++C===r&&(d=a),($=N.nextSibling)!==null)break;N=k,k=N.parentNode}N=$}n=c===-1||d===-1?null:{start:c,end:d}}else n=null}n=n||{start:0,end:0}}else n=null;for(pl={focusedElem:e,selectionRange:n},$s=!1,U=t;U!==null;)if(t=U,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,U=e;else for(;U!==null;){t=U;try{var V=t.alternate;if((t.flags&1024)!==0)switch(t.tag){case 0:case 11:case 15:break;case 1:if(V!==null){var Q=V.memoizedProps,He=V.memoizedState,v=t.stateNode,m=v.getSnapshotBeforeUpdate(t.elementType===t.type?Q:on(t.type,Q),He);v.__reactInternalSnapshotBeforeUpdate=m}break;case 3:var y=t.stateNode.containerInfo;y.nodeType===1?y.textContent="":y.nodeType===9&&y.documentElement&&y.removeChild(y.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(p(163))}}catch(M){Fe(t,t.return,M)}if(e=t.sibling,e!==null){e.return=t.return,U=e;break}U=t.return}return V=Ru,Ru=!1,V}function Cs(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var s=r=r.next;do{if((s.tag&e)===e){var l=s.destroy;s.destroy=void 0,l!==void 0&&ei(t,n,l)}s=s.next}while(s!==r)}}function _o(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ti(e){var t=e.ref;if(t!==null){var n=e.stateNode;e.tag,e=n,typeof t=="function"?t(e):t.current=e}}function Tu(e){var t=e.alternate;t!==null&&(e.alternate=null,Tu(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[vn],delete t[ms],delete t[vl],delete t[Pd],delete t[Rd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Mu(e){return e.tag===5||e.tag===3||e.tag===4}function Iu(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Mu(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function ni(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=bs));else if(r!==4&&(e=e.child,e!==null))for(ni(e,t,n),e=e.sibling;e!==null;)ni(e,t,n),e=e.sibling}function ri(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ri(e,t,n),e=e.sibling;e!==null;)ri(e,t,n),e=e.sibling}var tt=null,ln=!1;function nr(e,t,n){for(n=n.child;n!==null;)Lu(e,t,n),n=n.sibling}function Lu(e,t,n){if(z&&typeof z.onCommitFiberUnmount=="function")try{z.onCommitFiberUnmount(x,n)}catch{}switch(n.tag){case 5:ut||Kr(n,t);case 6:var r=tt,s=ln;tt=null,nr(e,t,n),tt=r,ln=s,tt!==null&&(ln?(e=tt,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):tt.removeChild(n.stateNode));break;case 18:tt!==null&&(ln?(e=tt,n=n.stateNode,e.nodeType===8?gl(e.parentNode,n):e.nodeType===1&&gl(e,n),rs(e)):gl(tt,n.stateNode));break;case 4:r=tt,s=ln,tt=n.stateNode.containerInfo,ln=!0,nr(e,t,n),tt=r,ln=s;break;case 0:case 11:case 14:case 15:if(!ut&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){s=r=r.next;do{var l=s,a=l.destroy;l=l.tag,a!==void 0&&((l&2)!==0||(l&4)!==0)&&ei(n,t,a),s=s.next}while(s!==r)}nr(e,t,n);break;case 1:if(!ut&&(Kr(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(c){Fe(n,t,c)}nr(e,t,n);break;case 21:nr(e,t,n);break;case 22:n.mode&1?(ut=(r=ut)||n.memoizedState!==null,nr(e,t,n),ut=r):nr(e,t,n);break;default:nr(e,t,n)}}function Au(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Wd),t.forEach(function(r){var s=tf.bind(null,e,r);n.has(r)||(n.add(r),r.then(s,s))})}}function an(e,t){var n=t.deletions;if(n!==null)for(var r=0;rs&&(s=a),r&=~l}if(r=s,r=f()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Gd(r/1960))-r,10e?16:e,sr===null)var r=!1;else{if(e=sr,sr=null,Co=0,(_e&6)!==0)throw Error(p(331));var s=_e;for(_e|=4,U=e.current;U!==null;){var l=U,a=l.child;if((U.flags&16)!==0){var c=l.deletions;if(c!==null){for(var d=0;df()-li?jr(e,0):oi|=n),jt(e,t)}function qu(e,t){t===0&&((e.mode&1)===0?t=1:(t=As,As<<=1,(As&130023424)===0&&(As=4194304)));var n=ht();e=An(e,t),e!==null&&(Xr(e,t,n),jt(e,n))}function ef(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function tf(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,s=e.memoizedState;s!==null&&(n=s.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(p(314))}r!==null&&r.delete(t),qu(e,n)}var Gu;Gu=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||xt.current)St=!0;else{if((e.lanes&n)===0&&(t.flags&128)===0)return St=!1,Hd(e,t,n);St=(e.flags&131072)!==0}else St=!1,Ae&&(t.flags&1048576)!==0&&Ea(t,no,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;yo(e,t),e=t.pendingProps;var s=Dr(t,lt.current);Vr(t,n),s=Dl(null,t,r,e,s,n);var l=$l();return t.flags|=1,typeof s=="object"&&s!==null&&typeof s.render=="function"&&s.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,_t(r)?(l=!0,Zs(t)):l=!1,t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,Tl(t),s.updater=go,t.stateNode=s,s._reactInternals=t,Ql(t,r,e,n),t=Gl(null,t,r,!0,l,n)):(t.tag=0,Ae&&l&&_l(t),mt(null,t,s,n),t=t.child),t;case 16:r=t.elementType;e:{switch(yo(e,t),e=t.pendingProps,s=r._init,r=s(r._payload),t.type=r,s=t.tag=rf(r),e=on(r,e),s){case 0:t=ql(null,t,r,e,n);break e;case 1:t=_u(null,t,r,e,n);break e;case 11:t=hu(null,t,r,e,n);break e;case 14:t=gu(null,t,r,on(r.type,e),n);break e}throw Error(p(306,r,""))}return t;case 0:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:on(r,s),ql(e,t,r,s,n);case 1:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:on(r,s),_u(e,t,r,s,n);case 3:e:{if(Su(t),e===null)throw Error(p(387));r=t.pendingProps,l=t.memoizedState,s=l.element,za(e,t),ao(t,r,null,n);var a=t.memoizedState;if(r=a.element,l.isDehydrated)if(l={element:r,isDehydrated:!1,cache:a.cache,pendingSuspenseBoundaries:a.pendingSuspenseBoundaries,transitions:a.transitions},t.updateQueue.baseState=l,t.memoizedState=l,t.flags&256){s=Wr(Error(p(423)),t),t=wu(e,t,r,n,s);break e}else if(r!==s){s=Wr(Error(p(424)),t),t=wu(e,t,r,n,s);break e}else for(At=bn(t.stateNode.containerInfo.firstChild),Lt=t,Ae=!0,sn=null,n=Aa(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Ur(),r===s){t=zn(e,t,n);break e}mt(e,t,r,n)}t=t.child}return t;case 5:return Fa(t),e===null&&jl(t),r=t.type,s=t.pendingProps,l=e!==null?e.memoizedProps:null,a=s.children,ml(r,s)?a=null:l!==null&&ml(r,l)&&(t.flags|=32),xu(e,t),mt(e,t,a,n),t.child;case 6:return e===null&&jl(t),null;case 13:return ju(e,t,n);case 4:return Ml(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Br(t,null,r,n):mt(e,t,r,n),t.child;case 11:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:on(r,s),hu(e,t,r,s,n);case 7:return mt(e,t,t.pendingProps,n),t.child;case 8:return mt(e,t,t.pendingProps.children,n),t.child;case 12:return mt(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,s=t.pendingProps,l=t.memoizedProps,a=s.value,Pe(oo,r._currentValue),r._currentValue=a,l!==null)if(rn(l.value,a)){if(l.children===s.children&&!xt.current){t=zn(e,t,n);break e}}else for(l=t.child,l!==null&&(l.return=t);l!==null;){var c=l.dependencies;if(c!==null){a=l.child;for(var d=c.firstContext;d!==null;){if(d.context===r){if(l.tag===1){d=On(-1,n&-n),d.tag=2;var _=l.updateQueue;if(_!==null){_=_.shared;var C=_.pending;C===null?d.next=d:(d.next=C.next,C.next=d),_.pending=d}}l.lanes|=n,d=l.alternate,d!==null&&(d.lanes|=n),Pl(l.return,n,t),c.lanes|=n;break}d=d.next}}else if(l.tag===10)a=l.type===t.type?null:l.child;else if(l.tag===18){if(a=l.return,a===null)throw Error(p(341));a.lanes|=n,c=a.alternate,c!==null&&(c.lanes|=n),Pl(a,n,t),a=l.sibling}else a=l.child;if(a!==null)a.return=l;else for(a=l;a!==null;){if(a===t){a=null;break}if(l=a.sibling,l!==null){l.return=a.return,a=l;break}a=a.return}l=a}mt(e,t,s.children,n),t=t.child}return t;case 9:return s=t.type,r=t.pendingProps.children,Vr(t,n),s=Wt(s),r=r(s),t.flags|=1,mt(e,t,r,n),t.child;case 14:return r=t.type,s=on(r,t.pendingProps),s=on(r.type,s),gu(e,t,r,s,n);case 15:return vu(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:on(r,s),yo(e,t),t.tag=1,_t(r)?(e=!0,Zs(t)):e=!1,Vr(t,n),au(t,r,s),Ql(t,r,s,n),Gl(null,t,r,!0,e,n);case 19:return Cu(e,t,n);case 22:return yu(e,t,n)}throw Error(p(156,t.tag))};function Ju(e,t){return Hn(e,t)}function nf(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Gt(e,t,n,r){return new nf(e,t,n,r)}function mi(e){return e=e.prototype,!(!e||!e.isReactComponent)}function rf(e){if(typeof e=="function")return mi(e)?1:0;if(e!=null){if(e=e.$$typeof,e===rt)return 11;if(e===Re)return 14}return 2}function ir(e,t){var n=e.alternate;return n===null?(n=Gt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Ro(e,t,n,r,s,l){var a=2;if(r=e,typeof e=="function")mi(e)&&(a=1);else if(typeof e=="string")a=5;else e:switch(e){case Ue:return Cr(n.children,s,l,t);case Ge:a=8,s|=8;break;case Ze:return e=Gt(12,n,t,s|2),e.elementType=Ze,e.lanes=l,e;case ie:return e=Gt(13,n,t,s),e.elementType=ie,e.lanes=l,e;case Be:return e=Gt(19,n,t,s),e.elementType=Be,e.lanes=l,e;case me:return To(n,s,l,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ft:a=10;break e;case Ct:a=9;break e;case rt:a=11;break e;case Re:a=14;break e;case xe:a=16,r=null;break e}throw Error(p(130,e==null?e:typeof e,""))}return t=Gt(a,n,t,s),t.elementType=e,t.type=r,t.lanes=l,t}function Cr(e,t,n,r){return e=Gt(7,e,r,t),e.lanes=n,e}function To(e,t,n,r){return e=Gt(22,e,r,t),e.elementType=me,e.lanes=n,e.stateNode={isHidden:!1},e}function hi(e,t,n){return e=Gt(6,e,null,t),e.lanes=n,e}function gi(e,t,n){return t=Gt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function sf(e,t,n,r,s){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Vo(0),this.expirationTimes=Vo(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Vo(0),this.identifierPrefix=r,this.onRecoverableError=s,this.mutableSourceEagerHydrationData=null}function vi(e,t,n,r,s,l,a,c,d){return e=new sf(e,t,n,c,d),t===1?(t=1,l===!0&&(t|=8)):t=0,l=Gt(3,null,null,t),e.current=l,l.stateNode=e,l.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Tl(l),e}function of(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(h){console.error(h)}}return i(),ji.exports=yf(),ji.exports}var uc;function _f(){if(uc)return Do;uc=1;var i=xf();return Do.createRoot=i.createRoot,Do.hydrateRoot=i.hydrateRoot,Do}var Sf=_f();const wf=Cc(Sf),jf="/api";async function Ne(i,h){const p=await fetch(`${jf}${i}`,{...h,headers:{"Content-Type":"application/json",...h?.headers??{}}}),O=await p.json();if(!p.ok){const E=O.error?.message??"Ошибка запроса";throw new Error(E)}return O}const Ie={async listModels(i){return Ne("/llm/models",{method:"POST",body:JSON.stringify({llmProvider:i.llmProvider,apiKey:i.apiKey,model:i.model,baseUrl:i.baseUrl})})},async testConnection(i){return Ne("/llm/test-connection",{method:"POST",body:JSON.stringify({llmProvider:i.llmProvider,apiKey:i.apiKey,model:i.model,baseUrl:i.baseUrl})})},async normalize(i){return Ne("/normalize",{method:"POST",body:JSON.stringify({llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples,userQuestion:i.query.userQuestion,context:{period_hint:i.query.periodHint??"",business_context:i.query.businessContext??"",expected_route:i.query.expectedRoute??""},saveAsTestCase:!!i.saveAsTestCase,useMock:!!i.useMock})})},async loadHistory(){return Ne("/history")},async loadTrace(i){return Ne(`/history/${i}`)},async loadPresets(){return Ne("/presets")},async savePreset(i){return Ne("/presets/save",{method:"POST",body:JSON.stringify(i)})},async runEval(i){return Ne("/eval/run",{method:"POST",body:JSON.stringify({normalizeConfig:{llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples},caseIds:i.caseIds,useMock:!!i.useMock,mode:i.mode??"standard",caseSetFile:i.caseSetFile,rawQuestions:i.rawQuestions,eval_target:i.evalTarget,compare_with_report_file:i.compareWithReportFile})})},async startEvalRunAsync(i){return Ne("/eval/run-async/start",{method:"POST",body:JSON.stringify({normalizeConfig:{llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples},caseIds:i.caseIds,useMock:!!i.useMock,mode:i.mode??"standard",caseSetFile:i.caseSetFile,rawQuestions:i.rawQuestions,eval_target:i.evalTarget,compare_with_report_file:i.compareWithReportFile,questions:i.questions})})},async loadEvalRunAsyncStatus(i){return Ne(`/eval/run-async/${encodeURIComponent(i)}`)},async startRun(){return Ne("/accounting-agent/v1/runs/start",{method:"POST",body:JSON.stringify({initiator:"ndc_operator",source:"gui"})})},async finishRun(i){return Ne("/accounting-agent/v1/runs/finish",{method:"POST",body:JSON.stringify({runId:i,status:"DONE",source:"gui",reason:"Остановлено оператором из GUI"})})},async listRuns(){return Ne("/accounting-agent/v1/runs")},async listResults(){return Ne("/accounting-agent/v1/results")},async runTrace(i){return Ne(`/accounting-agent/v1/trace/run/${i}`)},async sendAssistantMessage(i){return Ne("/assistant/message",{method:"POST",body:JSON.stringify({session_id:i.sessionId??"",mode:"assistant",message:i.userMessage,user_message:i.userMessage,llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion??"address_query_runtime_v1",systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples,context:{period_hint:i.context?.periodHint??"",business_context:i.context?.businessContext??""},useMock:!!i.useMock})})},async loadAssistantSession(i){return Ne(`/assistant/session/${i}`)},async loadAutoRunsHistory(i){const h=new URLSearchParams;i?.from&&h.set("from",i.from),i?.to&&h.set("to",i.to),i?.target&&h.set("target",i.target),i?.mode&&h.set("mode",i.mode),i?.use_mock&&h.set("use_mock",i.use_mock),i?.prompt_contains&&h.set("prompt_contains",i.prompt_contains),typeof i?.limit=="number"&&h.set("limit",String(i.limit)),typeof i?.scan_limit=="number"&&h.set("scan_limit",String(i.scan_limit));const p=h.toString();return Ne(`/autoruns/history${p?`?${p}`:""}`)},async loadAutoRunDetail(i){return Ne(`/autoruns/history/${encodeURIComponent(i)}`)},async loadAutoRunCaseDialog(i,h){return Ne(`/autoruns/history/${encodeURIComponent(i)}/case/${encodeURIComponent(h)}/dialog`)},async loadAutoRunAnnotations(i){const h=new URLSearchParams;i?.run_id&&h.set("run_id",i.run_id),i?.case_id&&h.set("case_id",i.case_id),typeof i?.min_rating=="number"&&h.set("min_rating",String(i.min_rating)),i?.manual_case_decision&&h.set("manual_case_decision",i.manual_case_decision),typeof i?.limit=="number"&&h.set("limit",String(i.limit));const p=h.toString();return Ne(`/autoruns/annotations${p?`?${p}`:""}`)},async saveAutoRunAnnotation(i){return Ne("/autoruns/annotations",{method:"POST",body:JSON.stringify(i)})},async updateAutoRunAnnotation(i){return Ne(`/autoruns/annotations/${encodeURIComponent(i.annotation_id)}`,{method:"PATCH",body:JSON.stringify({resolved:i.resolved,resolved_by:i.resolved_by})})},async loadAutoRunPostAnalysis(i){const h=new URLSearchParams;i?.run_id&&h.set("run_id",i.run_id),typeof i?.limit_per_queue=="number"&&h.set("limit_per_queue",String(i.limit_per_queue)),typeof i?.annotation_limit=="number"&&h.set("annotation_limit",String(i.annotation_limit)),typeof i?.scan_limit=="number"&&h.set("scan_limit",String(i.scan_limit)),i?.from&&h.set("from",i.from),i?.to&&h.set("to",i.to),i?.target&&h.set("target",i.target),i?.mode&&h.set("mode",i.mode),i?.use_mock&&h.set("use_mock",i.use_mock),i?.prompt_contains&&h.set("prompt_contains",i.prompt_contains);const p=h.toString();return Ne(`/autoruns/post-analysis${p?`?${p}`:""}`)},async loadAutoRunAutogenHistory(i){const h=new URLSearchParams;i?.mode&&h.set("mode",i.mode),typeof i?.limit=="number"&&h.set("limit",String(i.limit));const p=h.toString();return Ne(`/autoruns/autogen/history${p?`?${p}`:""}`)},async loadAutoRunAutogenPersonalityCatalog(){return Ne("/autoruns/autogen/personality-catalog")},async generateAutoRunQuestions(i){return Ne("/autoruns/autogen/generate",{method:"POST",body:JSON.stringify(i)})}};function dt({value:i}){return o.jsx("pre",{className:"json-view",children:JSON.stringify(i??{},null,2)})}function Sn({title:i,subtitle:h,actions:p,className:O,hideHeader:E,children:D}){return o.jsxs("section",{className:O?`panel-frame ${O}`:"panel-frame",children:[E?null:o.jsxs("header",{className:"panel-header",children:[o.jsxs("div",{children:[o.jsx("h2",{children:i}),h?o.jsx("p",{children:h}):null]}),p?o.jsx("div",{className:"panel-actions",children:p}):null]}),o.jsx("div",{className:"panel-body",children:D})]})}function kf(i){const h=new Date(i);return Number.isNaN(h.getTime())?i:h.toLocaleString("ru-RU")}function Cf({sessionId:i,conversation:h,statusText:p,errorMessage:O,useMock:E,appLogs:D}){const K=h.filter(I=>I.role==="assistant").length,Z=h.filter(I=>I.role==="user").length,B=h.length>0?h[h.length-1]:null;return o.jsxs(Sn,{title:"SAM",subtitle:"System Assistant Monitor: срез по текущей сессии и логам.",children:[o.jsxs("div",{className:"metrics-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"session_id"}),o.jsx("strong",{children:i||"новая сессия"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"mock_mode"}),o.jsx("strong",{children:E?"on":"off"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"сообщений пользователя"}),o.jsx("strong",{children:Z})]}),o.jsxs("div",{children:[o.jsx("span",{children:"ответов ассистента"}),o.jsx("strong",{children:K})]}),o.jsxs("div",{children:[o.jsx("span",{children:"статус"}),o.jsx("strong",{children:p||"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"ошибка"}),o.jsx("strong",{children:O||"нет"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"последнее сообщение"}),o.jsx("strong",{children:B?.created_at?kf(B.created_at):"нет данных"})]})]}),o.jsx("h3",{style:{marginTop:12},children:"Последние системные логи"}),o.jsx(dt,{value:D.slice(0,120)})]})}const Ni={fromLocal:"",toLocal:"",target:"all",mode:"all",useMock:"any",promptContains:"",limit:120},$o="needs_dialog_policy_fix",ct="__all__",Bo="__live__:",cc="ndc_autoruns_ui_config_v1",dc="ndc-autoruns-save",Ti=[{id:"general",label:"Общий контур",domain:"",defaultPrompt:"Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл."}];function Nf(i=Ti){return i.reduce((h,p)=>(h[p.id]=p.defaultPrompt,h),{})}const fc={mode:"codex_creative",count:24,personalityId:"general",personalityPrompts:Nf(),persistToEvalCases:!0,generatedBy:"manual_reviewer"};function Ef(i){const h=i.getFullYear(),p=String(i.getMonth()+1).padStart(2,"0"),O=String(i.getDate()).padStart(2,"0"),E=String(i.getHours()).padStart(2,"0"),D=String(i.getMinutes()).padStart(2,"0");return`${h}-${p}-${O}T${E}:${D}`}function pc(){const i=new Date;return i.setDate(i.getDate()-14),Ef(i)}function Fo(i){if(!i.trim())return;const h=Date.parse(i);if(Number.isFinite(h))return new Date(h).toISOString()}function ur(i){if(!i)return"нет данных";const h=Date.parse(i);return Number.isFinite(h)?new Date(h).toLocaleString("ru-RU"):i}function Pf(i,h){return h<=0?0:Math.max(0,Math.min(100,Number((i/h*100).toFixed(1))))}function Ms(i){return typeof i!="number"?"нет данных":`${i.toFixed(1)}%`}function Rf(i){return i==="assistant_stage1"?"assistant/s1":i==="assistant_stage2"?"assistant/s2":i==="assistant_p0"?"assistant/p0":i}function mc(i){return i==="up"?"Рост":i==="down"?"Регресс":"Без изменений"}function Tf(i,h){return i.find(p=>p.case_id===h)??null}function hc(i){const h=Math.max(1,Math.min(5,Math.round(i)));return`${"●".repeat(h)}${"○".repeat(5-h)}`}function gc(i){return i.length===0?o.jsx("p",{className:"muted",children:"Покрытие доменов пока не сформировано."}):o.jsx("div",{className:"autoruns-coverage-list",children:i.map(h=>{const p=Pf(h.closed_cases,h.total_cases);return o.jsxs("div",{className:"autoruns-coverage-item",children:[o.jsxs("div",{className:"autoruns-coverage-head",children:[o.jsx("strong",{children:h.domain}),o.jsxs("span",{children:[h.closed_cases,"/",h.total_cases," (",p,"%)"]})]}),o.jsx("div",{className:"autoruns-coverage-bar",children:o.jsx("div",{style:{width:`${p}%`}})})]},h.domain)})})}function Uo(i){return`${Bo}${i}`}function br(i){return i.startsWith(Bo)}function vc(i){return i.startsWith(Bo)?i.slice(Bo.length):""}function Nc(i){const h=i.report_summary?.run_timestamp??i.created_at,p=Math.max(0,i.total_cases-i.completed_cases);return{run_id:Uo(i.job_id),eval_target:i.eval_target,run_timestamp:h,mode:"single-pass-strict",llm_provider:null,model:null,use_mock:null,prompt_version:null,schema_version:null,suite_id:i.case_set_file,cases_total:i.total_cases,requests_total:null,report_path:`async_job:${i.job_id}`,score_index:i.report_summary?.score_index??null,blocking_failures:0,quality_failures:0,closed_cases:i.completed_cases,open_cases:p,domain_coverage:[{domain:"runtime",total_cases:i.total_cases,closed_cases:i.completed_cases}]}}function Is(i,h){const p=Nc(i),O=i.cases.map(q=>({case_id:q.case_id,domain:null,query_class:null,status:q.status==="completed"?"closed":q.status==="failed"?"open":"unknown",score_index:null,trace_id:null,reply_type:null,session_id:`${i.run_id}-${q.case_id}`,dialog_available:q.messages.length>0,commented_count:0,latest_annotation_at:null,avg_rating:null,checks:null,metric_subscores:null})),D=h!==ct&&O.some(q=>q.case_id===h)?h:O.length>0?ct:"",K={ok:!0,run:p,coverage:{closed_cases:i.completed_cases,open_cases:Math.max(0,i.total_cases-i.completed_cases),domain_coverage:[{domain:"runtime",total_cases:i.total_cases,closed_cases:i.completed_cases}]},cases:O,annotations_summary:{total:0},report:i.report_summary?{run_id:i.report_summary.run_id,run_timestamp:i.report_summary.run_timestamp,score_index:i.report_summary.score_index,cases_total:i.report_summary.cases_total}:{}},Z=[];let B=0;if(D===ct)for(const q of i.cases)for(let T=0;TT.case_id===D)??null;for(let T=0;T<(q?.messages.length??0);T+=1){const H=q?.messages[T];H&&Z.push({...H,message_index:T,case_id:D,case_message_index:T,commented:!1,annotation:null})}}const I={ok:!0,run_id:p.run_id,case_id:D,source:"assistant_session",session_id:D===ct?`${i.run_id}::__all__`:`${i.run_id}-${D}`,messages:Z,decomposition:[],assistant_mode:{status:i.status,completed_cases:i.completed_cases,total_cases:i.total_cases},annotations:[]};return{detail:K,dialog:I,caseId:D}}function Mf({commented:i}){const h=i?"comment-icon-svg commented":"comment-icon-svg";return o.jsxs("svg",{className:h,viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",children:[o.jsx("path",{d:"M5 6.5h14v9H11.5l-4.5 3v-3H5z"}),o.jsx("circle",{className:"comment-icon-dot",cx:"9",cy:"11",r:"1.05"}),o.jsx("circle",{className:"comment-icon-dot",cx:"12",cy:"11",r:"1.05"}),o.jsx("circle",{className:"comment-icon-dot",cx:"15",cy:"11",r:"1.05"})]})}function yc({resolved:i}){return o.jsxs("svg",{className:i?"resolve-icon-svg resolved":"resolve-icon-svg",viewBox:"0 0 16 16","aria-hidden":"true",focusable:"false",children:[o.jsx("circle",{cx:"8",cy:"8",r:"6.2"}),i?o.jsx("path",{d:"M5.1 8.2 7.2 10.3 11 6.5"}):null]})}function If({connection:i,prompts:h,assistantPromptVersion:p,decompositionPromptVersion:O,showAssistantMode:E,showDecompositionMode:D,showProgressMode:K,showCommentsMode:Z,onLog:B}){const[I,q]=S.useState({...Ni,fromLocal:pc()}),[T,H]=S.useState(null),[fe,ye]=S.useState(null),[ne,X]=S.useState(null),[G,se]=S.useState([]),[Se,Le]=S.useState("all"),[Oe,$e]=S.useState(!1),[Ue,Ge]=S.useState(null),[Ze,ft]=S.useState([]),[Ct,rt]=S.useState(""),[ie,Be]=S.useState(""),[Re,xe]=S.useState(""),[me,A]=S.useState(Ti),[R,L]=S.useState(fc),[g,j]=S.useState([]),[ee,oe]=S.useState(""),[le,ae]=S.useState([]),[re,he]=S.useState(null),[ce,ke]=S.useState(null),[zt,wn]=S.useState(!1),[cn,Nt]=S.useState(!1),[Jt,jn]=S.useState(!1),[bt,kn]=S.useState(!1),[Cn,Et]=S.useState(!1),[Dt,Yt]=S.useState(!1),[Fn,dn]=S.useState(!1),[Xt,fn]=S.useState(!1),[Nn,$t]=S.useState(""),[Un,Ee]=S.useState(""),[pn,Pt]=S.useState(String(Ni.limit)),[cr,gt]=S.useState(String(fc.count)),[de,We]=S.useState({open:!1,caseId:"",caseMessageIndex:-1,messageIndex:-1,rating:3,comment:"",manualCaseDecision:$o,annotationAuthor:"manual_reviewer",saving:!1,error:""}),mn=S.useRef(!1),Rt=S.useRef(null),Zt=S.useMemo(()=>me.find(u=>u.id===R.personalityId)??me[0]??Ti[0],[R.personalityId,me]),st=S.useMemo(()=>g.find(u=>u.generation_id===ee)??g[0]??null,[g,ee]),Ft=T?.items.find(u=>u.run_id===ie)??fe?.run??null,Je=fe?Tf(fe.cases,Re):null,Ve=S.useMemo(()=>Oe?G.filter(u=>!u.resolved):G,[G,Oe]),je=Ve.find(u=>u.annotation_id===Ct)??null,En=ne?.messages.find(u=>u.message_index===de.messageIndex)??null,dr=S.useMemo(()=>{if(!ne||de.messageIndex<0)return null;for(let u=de.messageIndex-1;u>=0;u-=1){const f=ne.messages[u];if(f?.role==="user")return f}return null},[de.messageIndex,ne]),Pn=S.useMemo(()=>{if(Ve.length===0)return null;const u=Ve.reduce((f,w)=>f+w.rating,0)/Ve.length;return Number(u.toFixed(2))},[Ve]),hn=S.useMemo(()=>{const u=[...T?.items??[]];return re&&u.unshift(Nc(re)),ie&&!u.some(f=>f.run_id===ie)&&fe?.run&&u.unshift(fe.run),u},[re,T?.items,fe?.run,ie]),ge=S.useCallback(u=>{B?.(`[autoruns] ${u}`)},[B]),en=S.useCallback(u=>{const f=u.trim();if(!f){Pt(String(I.limit));return}if(!/^\d+$/.test(f)){Pt(String(I.limit));return}const w=Number.parseInt(f,10);if(!Number.isFinite(w)){Pt(String(I.limit));return}const F=Math.max(1,Math.min(500,w));F!==I.limit&&q(ve=>({...ve,limit:F})),Pt(String(F))},[I.limit]),Ut=S.useCallback(u=>{const f=u.trim();if(!f){gt(String(R.count));return}if(!/^\d+$/.test(f)){gt(String(R.count));return}const w=Number.parseInt(f,10);if(!Number.isFinite(w)){gt(String(R.count));return}const F=Math.max(1,Math.min(200,w));F!==R.count&&L(ve=>({...ve,count:F})),gt(String(F))},[R.count]),vt=S.useCallback(async()=>{fn(!0);try{const u=await Ie.loadAutoRunAnnotations({limit:800,manual_case_decision:Se});se(u.items),Ge(u.manual_case_decision_schema??null),ft(u.available_manual_case_decisions??[]),rt(f=>u.items.length===0?"":u.items.some(w=>w.annotation_id===f)?f:u.items[0].annotation_id)}catch(u){ge(`Annotations load error: ${u instanceof Error?u.message:String(u)}`)}finally{fn(!1)}},[Se,ge]),Tt=S.useCallback(async()=>{kn(!0);try{const u=await Ie.loadAutoRunAutogenHistory({limit:180});j(u.items)}catch(u){ge(`Autogen history load error: ${u instanceof Error?u.message:String(u)}`)}finally{kn(!1)}},[ge]),Mt=S.useCallback(async()=>{try{const f=(await Ie.loadAutoRunAutogenPersonalityCatalog()).items.map(w=>({id:String(w.id??"").trim(),label:String(w.label??"").trim(),domain:typeof w.domain=="string"?w.domain.trim():"",defaultPrompt:String(w.default_prompt??"").trim()})).filter(w=>w.id.length>0&&w.label.length>0);if(f.length===0)return;A(f.map(w=>({id:w.id,label:w.label,domain:w.domain||"",defaultPrompt:w.defaultPrompt||"Генерируй реалистичные вопросы бухгалтера по выбранному профилю. Не выдумывай непокрытые возможности."})))}catch(u){ge(`Autogen personality catalog load error: ${u instanceof Error?u.message:String(u)}`)}},[ge]),et=S.useCallback(async()=>{jn(!0);try{const u=await Ie.loadAutoRunPostAnalysis({run_id:ie&&!br(ie)?ie:void 0,limit_per_queue:30,annotation_limit:1500,from:Fo(I.fromLocal),to:Fo(I.toLocal),target:I.target,mode:I.mode,use_mock:I.useMock,prompt_contains:I.promptContains.trim()||void 0});ke(u)}catch(u){ge(`Post-analysis load error: ${u instanceof Error?u.message:String(u)}`),ke(null)}finally{jn(!1)}},[I.fromLocal,I.mode,I.promptContains,I.target,I.toLocal,I.useMock,ge,ie]),Y=S.useCallback(async()=>{wn(!0),Ee("");try{const u=R.personalityPrompts[R.personalityId]??"",f=[h.systemPrompt,h.developerPrompt,h.domainPrompt,h.schemaNotes,h.fewShotExamples].join(` +`).slice(0,900),w=await Ie.generateAutoRunQuestions({mode:R.mode,count:R.count,domain:Zt.domain||void 0,persist_to_eval_cases:R.persistToEvalCases,generated_by:R.generatedBy.trim()||void 0,llm:{llm_provider:i.llmProvider,api_key:i.apiKey,model:i.model,base_url:i.baseUrl,temperature:i.temperature,max_output_tokens:i.maxOutputTokens},context:{llm_provider:i.llmProvider,model:i.model,assistant_prompt_version:p,decomposition_prompt_version:O,prompt_fingerprint:f,autogen_personality_id:Zt.id,autogen_personality_prompt:u.trim()||void 0}});ge(`Generated ${w.generation.count} questions (${w.generation.mode}) id=${w.generation.generation_id}`+(w.generation.saved_case_set_file?` saved=${w.generation.saved_case_set_file}`:"")),oe(w.generation.generation_id),ae([...w.generation.questions??[]]),await Tt()}catch(u){const f=u instanceof Error?u.message:String(u);Ee(`Автогенерация: ${f}`),ge(`Autogen generate error: ${f}`)}finally{wn(!1)}},[p,R.count,R.generatedBy,R.mode,R.personalityId,R.personalityPrompts,R.persistToEvalCases,i.apiKey,i.baseUrl,i.llmProvider,i.maxOutputTokens,i.model,i.temperature,O,Tt,ge,h.developerPrompt,h.domainPrompt,h.fewShotExamples,h.schemaNotes,h.systemPrompt,Zt.domain,Zt.id]),tn=S.useCallback(async(u,f)=>{if(br(u)){const w=vc(u);if(re&&re.job_id===w){const F=Is(re,f);Be(u),xe(F.caseId),X(F.dialog);return}X(null);return}dn(!0);try{const w=await Ie.loadAutoRunCaseDialog(u,f);X(w)}catch(w){const F=w instanceof Error?w.message:String(w);Ee(`Диалог кейса: ${F}`),X(null),ge(`Dialog load error for ${u}/${f}: ${F}`)}finally{dn(!1)}},[re,ge]),pt=S.useCallback(async(u,f)=>{if(br(u)){const w=vc(u);if(re&&re.job_id===w){const F=Is(re,f??ct);Be(u),xe(F.caseId),ye(F.detail),X(F.dialog);return}Be(u),xe(""),ye(null),X(null);return}Yt(!0);try{const w=await Ie.loadAutoRunDetail(u);ye(w);const F=(f&&(f===ct||w.cases.some(ve=>ve.case_id===f))?f:"")||(w.cases.length>0?ct:"")||"";Be(u),xe(F),F?await tn(u,F):X(null)}catch(w){const F=w instanceof Error?w.message:String(w);Ee(`Детализация прогона: ${F}`),ye(null),X(null),ge(`Run detail load error for ${u}: ${F}`)}finally{Yt(!1)}},[re,tn,ge]),gn=S.useCallback(async u=>{Et(!0),Ee("");try{const f=await Ie.loadAutoRunsHistory({from:Fo(I.fromLocal),to:Fo(I.toLocal),target:I.target,mode:I.mode,use_mock:I.useMock,prompt_contains:I.promptContains.trim()||void 0,limit:I.limit});if(H(f),f.items.length===0){Be(""),xe(""),ye(null),X(null);return}const w=u?.keepSelection??!0,F=u?.preferredRunId??"",ve=u?.preferredCaseId??"",Bt=w&&F&&f.items.some(Ht=>Ht.run_id===F)?F:f.items[0].run_id;await pt(Bt,w?ve:void 0),et()}catch(f){const w=f instanceof Error?f.message:String(f);Ee(`История прогонов: ${w}`),ge(`History load error: ${w}`)}finally{Et(!1)}},[I.fromLocal,I.limit,I.mode,I.promptContains,I.target,I.toLocal,I.useMock,et,pt,ge]),ot=S.useCallback(()=>{Rt.current!==null&&(window.clearTimeout(Rt.current),Rt.current=null)},[]),yt=S.useCallback(async u=>{try{const f=await Ie.loadEvalRunAsyncStatus(u);he(f.job);const w=Uo(u);if(ie===w){const F=Is(f.job,Re||ct);ye(F.detail),X(F.dialog),xe(F.caseId)}if(f.job.status==="completed"){ot(),Nt(!1);const F=f.job.report_summary?.run_id??f.job.run_id;await gn({keepSelection:!0,preferredRunId:F||ie,preferredCaseId:ct}),await Tt(),he(null);return}if(f.job.status==="failed"){ot(),Nt(!1),Ee(`Запуск прогонов: ${f.job.error??"неизвестная ошибка"}`),ge(`Autogen async run failed: ${f.job.error??"unknown error"}`);return}ot(),Rt.current=window.setTimeout(()=>{yt(u)},500)}catch(f){ot(),Nt(!1);const w=f instanceof Error?f.message:String(f);Ee(`Запуск прогонов: ${w}`),ge(`Autogen async status error: ${w}`)}},[Tt,gn,ge,Re,ie,ot]),Nr=S.useCallback(async()=>{ot(),Nt(!0),Ee("");try{const u=st;if(!u)throw new Error("История автогенерации пуста. Сначала сгенерируйте пачку вопросов.");const f=le.map(nn=>nn.trim()).filter(nn=>nn.length>0);if(f.length===0)throw new Error("Нет вопросов для запуска: список пустой после ручного редактирования.");const w=I.useMock==="true",ve=(await Ie.startEvalRunAsync({connection:i,prompts:h,promptVersion:p,mode:"single-pass-strict",caseSetFile:u.saved_case_set_file??void 0,useMock:w,evalTarget:"assistant_stage1",questions:f})).job;he(ve);const Bt=Uo(ve.job_id),Ht=Is(ve,ct);Be(Bt),xe(Ht.caseId),ye(Ht.detail),X(Ht.dialog),ge(`Запущен async-прогон job=${ve.job_id}, run_id=${ve.run_id}, вопросов=${f.length}`+(u.saved_case_set_file?`, base_case_set=${u.saved_case_set_file}`:"")),yt(ve.job_id)}catch(u){const f=u instanceof Error?u.message:String(u);Ee(`Запуск прогонов: ${f}`),ge(`Autogen run error: ${f}`),Nt(!1)}},[p,i,le,I.useMock,ge,yt,h,st,ot]),fr=S.useCallback(u=>{if(u.role!=="assistant")return;const f=u.case_id??Re,w=u.case_message_index??u.message_index;We({open:!0,caseId:f,caseMessageIndex:w,messageIndex:u.message_index,rating:u.annotation?.rating??3,comment:u.annotation?.comment??"",manualCaseDecision:u.annotation?.manual_case_decision??$o,annotationAuthor:u.annotation?.annotation_author??R.generatedBy,saving:!1,error:""})},[R.generatedBy,Re]),Rn=S.useCallback(u=>{We(f=>f.saving&&!u?.force?f:{open:!1,caseId:"",caseMessageIndex:-1,messageIndex:-1,rating:3,comment:"",manualCaseDecision:$o,annotationAuthor:R.generatedBy,saving:!1,error:""})},[R.generatedBy]),Er=S.useCallback(async()=>{const u=ie,f=de.caseId,w=de.caseMessageIndex;if(!(!u||!f||w<0)){if(br(u)){We(F=>({...F,error:"Комментарий можно сохранить после завершения прогона."}));return}if(!de.comment.trim()){We(F=>({...F,error:"Добавьте комментарий."}));return}We(F=>({...F,saving:!0,error:""}));try{await Ie.saveAutoRunAnnotation({run_id:u,case_id:f,message_index:w,rating:de.rating,comment:de.comment.trim(),manual_case_decision:de.manualCaseDecision,annotation_author:de.annotationAuthor.trim()||void 0}),Rn({force:!0}),Promise.all([pt(u,Re),vt(),et()]).catch(F=>{const ve=F instanceof Error?F.message:String(F);Ee(`Обновление после комментария: ${ve}`),ge(`Comment refresh error: ${ve}`)})}catch(F){We(ve=>({...ve,saving:!1,error:F instanceof Error?F.message:String(F)}))}}},[Rn,de.annotationAuthor,de.caseId,de.caseMessageIndex,de.comment,de.manualCaseDecision,de.rating,vt,et,pt,ge,Re,ie]),Bn=S.useCallback(u=>{se(f=>f.map(w=>w.annotation_id===u.annotation_id?{...w,...u}:w)),X(f=>f&&{...f,annotations:f.annotations.map(w=>w.annotation_id===u.annotation_id?u:w),messages:f.messages.map(w=>!w.annotation||w.annotation.annotation_id!==u.annotation_id?w:{...w,commented:!0,annotation:u})})},[]),Hn=S.useCallback(async(u,f)=>{if(u.annotation_id){if(br(u.run_id)){Ee("Статус выполнения можно менять только для завершённых прогонов.");return}$t(u.annotation_id);try{const w=await Ie.updateAutoRunAnnotation({annotation_id:u.annotation_id,resolved:f,resolved_by:R.generatedBy||void 0});Bn(w.annotation),et()}catch(w){const F=w instanceof Error?w.message:String(w);Ee(`Смена статуса кейса: ${F}`),ge(`Annotation resolve toggle error: ${F}`)}finally{$t("")}}},[Bn,R.generatedBy,et,ge]),Vn=S.useCallback(async u=>{rt(u.annotation_id),await pt(u.run_id,u.case_id),T?.items.some(f=>f.run_id===u.run_id)||Ee("Комментарий относится к прогону вне текущего фильтра. Детали загружены напрямую.")},[T?.items,pt]);S.useEffect(()=>{mn.current||(mn.current=!0,gn({keepSelection:!1}),Tt(),Mt(),et())},[Tt,Mt,gn,et]),S.useEffect(()=>{mn.current&&vt()},[Se,vt]),S.useEffect(()=>{rt(u=>Ve.length===0?"":Ve.some(f=>f.annotation_id===u)?u:Ve[0].annotation_id)},[Ve]),S.useEffect(()=>{oe(u=>g.length===0?"":u&&g.some(f=>f.generation_id===u)?u:g[0].generation_id)},[g]),S.useEffect(()=>{if(!st){ae([]);return}ae([...st.questions])},[st?.generation_id]),S.useEffect(()=>{Pt(String(I.limit))},[I.limit]),S.useEffect(()=>{gt(String(R.count))},[R.count]),S.useEffect(()=>{if(!re)return;const u=Uo(re.job_id);if(ie!==u)return;const f=Is(re,Re||ct);ye(f.detail),X(f.dialog),xe(f.caseId)},[re,Re,ie]),S.useEffect(()=>()=>{ot()},[ot]),S.useEffect(()=>{me.length!==0&&L(u=>{let f=!1;const w={...u.personalityPrompts};for(const ve of me)(typeof w[ve.id]!="string"||w[ve.id].trim().length===0)&&(w[ve.id]=ve.defaultPrompt,f=!0);let F=u.personalityId;return me.some(ve=>ve.id===u.personalityId)||(F=me[0].id,f=!0),f?{...u,personalityId:F,personalityPrompts:w}:u})},[me]),S.useEffect(()=>{const u=localStorage.getItem(cc);if(u)try{const f=JSON.parse(u);if(f.filters){const w=f.filters;q(F=>({...F,...w,limit:typeof w.limit=="number"?Math.max(1,Math.min(500,w.limit)):F.limit}))}f.autoGenSettings&&L(w=>{const F={...w.personalityPrompts},ve=f.autoGenSettings?.personalityPrompts??{};for(const[Ht,nn]of Object.entries(ve))typeof nn=="string"&&Ht.trim().length>0&&(F[Ht.trim()]=nn);const Bt=typeof f.autoGenSettings?.personalityId=="string"&&f.autoGenSettings.personalityId.trim().length>0?f.autoGenSettings.personalityId.trim():w.personalityId;return{...w,mode:f.autoGenSettings?.mode==="codex_creative"||f.autoGenSettings?.mode==="qwen_seed"?f.autoGenSettings.mode:w.mode,count:typeof f.autoGenSettings?.count=="number"?Math.max(1,Math.min(200,f.autoGenSettings.count)):w.count,personalityId:Bt,personalityPrompts:F,persistToEvalCases:typeof f.autoGenSettings?.persistToEvalCases=="boolean"?f.autoGenSettings.persistToEvalCases:w.persistToEvalCases,generatedBy:typeof f.autoGenSettings?.generatedBy=="string"?f.autoGenSettings.generatedBy:w.generatedBy}}),(f.annotationDecisionFilter==="all"||typeof f.annotationDecisionFilter=="string"&&f.annotationDecisionFilter.length>0)&&Le(f.annotationDecisionFilter),typeof f.hideResolvedAnnotations=="boolean"&&$e(f.hideResolvedAnnotations)}catch{}},[]);const pr=S.useCallback(()=>{const u={filters:I,autoGenSettings:{mode:R.mode,count:R.count,personalityId:R.personalityId,personalityPrompts:R.personalityPrompts,persistToEvalCases:R.persistToEvalCases,generatedBy:R.generatedBy},annotationDecisionFilter:Se,hideResolvedAnnotations:Oe};localStorage.setItem(cc,JSON.stringify(u))},[Se,R,I,Oe]);return S.useEffect(()=>{const u=()=>{pr(),ge("Сохранены настройки панели автопрогонов.")};return window.addEventListener(dc,u),()=>{window.removeEventListener(dc,u)}},[ge,pr]),o.jsxs(Sn,{className:"autoruns-frame",title:"",hideHeader:!0,children:[o.jsxs("div",{className:"autoruns-columns",children:[o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Настройки"})}),o.jsx("h4",{children:"Настройки выборки"}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Дата с",o.jsx("input",{type:"datetime-local",value:I.fromLocal,onChange:u=>q(f=>({...f,fromLocal:u.target.value}))})]}),o.jsxs("label",{children:["Дата по",o.jsx("input",{type:"datetime-local",value:I.toLocal,onChange:u=>q(f=>({...f,toLocal:u.target.value}))})]}),o.jsxs("label",{children:["Целевой контур",o.jsxs("select",{value:I.target,onChange:u=>q(f=>({...f,target:u.target.value})),children:[o.jsx("option",{value:"all",children:"все"}),(T?.available.targets??[]).map(u=>o.jsx("option",{value:u,children:u},u))]})]}),o.jsxs("label",{children:["Режим",o.jsxs("select",{value:I.mode,onChange:u=>q(f=>({...f,mode:u.target.value})),children:[o.jsx("option",{value:"all",children:"все"}),(T?.available.modes??[]).map(u=>o.jsx("option",{value:u,children:u},u))]})]}),o.jsxs("label",{children:["Использовать mock",o.jsxs("select",{value:I.useMock,onChange:u=>q(f=>({...f,useMock:u.target.value})),children:[o.jsx("option",{value:"any",children:"любой"}),o.jsx("option",{value:"true",children:"да"}),o.jsx("option",{value:"false",children:"нет"})]})]}),o.jsxs("label",{children:["Лимит",o.jsx("input",{type:"number",min:1,max:500,value:pn,onChange:u=>{const f=u.target.value;(f===""||/^\d+$/.test(f))&&Pt(f)},onBlur:u=>en(u.target.value),onKeyDown:u=>{u.key==="Enter"&&en(u.target.value)}})]}),o.jsxs("label",{className:"full-width",children:["Версия промпта содержит",o.jsx("input",{value:I.promptContains,onChange:u=>q(f=>({...f,promptContains:u.target.value})),placeholder:"normalizer_v2_0_2 / address_query_runtime_v1",list:"autoruns-prompt-versions"})]})]}),o.jsx("datalist",{id:"autoruns-prompt-versions",children:(T?.available.prompt_versions??[]).map(u=>o.jsx("option",{value:u},u))}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:Cn,onClick:()=>{gn({keepSelection:!1})},children:Cn?"Обновляю...":"Применить"}),o.jsx("button",{type:"button",className:"tab",onClick:()=>{q({...Ni,fromLocal:pc()}),Ee("")},children:"Сбросить фильтры"})]}),o.jsx("h4",{children:"Контур генерации"}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Провайдер:"}),o.jsx("strong",{children:i.llmProvider})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Модель:"}),o.jsx("strong",{children:i.model||"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Промпт ассистента:"}),o.jsx("strong",{children:p})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Промпт декомпозиции:"}),o.jsx("strong",{children:O})]})]}),o.jsx("h4",{children:"Автогенерация вопросов"}),o.jsx("p",{className:"muted",children:"`qwen_seed` использует текущую LLM-модель из активного контура подключения (та же модель, что и для ответов ассистента)."}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Режим генерации",o.jsxs("select",{value:R.mode,onChange:u=>L(f=>({...f,mode:u.target.value})),children:[o.jsx("option",{value:"codex_creative",children:"codex_creative"}),o.jsx("option",{value:"qwen_seed",children:"qwen_seed"})]})]}),o.jsxs("label",{children:["Кол-во",o.jsx("input",{type:"number",min:1,max:200,value:cr,onChange:u=>{const f=u.target.value;(f===""||/^\d+$/.test(f))&>(f)},onBlur:u=>Ut(u.target.value),onKeyDown:u=>{u.key==="Enter"&&Ut(u.target.value)}})]}),o.jsxs("label",{children:["Личность автогенерации",o.jsx("select",{value:R.personalityId,onChange:u=>L(f=>({...f,personalityId:u.target.value})),children:me.map(u=>o.jsx("option",{value:u.id,children:u.label},u.id))})]}),o.jsxs("label",{children:["Кто генерирует",o.jsx("input",{value:R.generatedBy,onChange:u=>L(f=>({...f,generatedBy:u.target.value})),placeholder:"manual_reviewer"})]}),o.jsxs("label",{className:"full-width",children:["Промпт личности",o.jsx("textarea",{value:R.personalityPrompts[R.personalityId]??"",onChange:u=>L(f=>({...f,personalityPrompts:{...f.personalityPrompts,[f.personalityId]:u.target.value}})),placeholder:"Текст промпта для выбранной личности автогенерации"})]}),o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:R.persistToEvalCases,onChange:u=>L(f=>({...f,persistToEvalCases:u.target.checked}))}),"Сохранять кейс-сет в `eval_cases`"]})]}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:zt,onClick:()=>{Y()},children:zt?"Генерирую...":"Сгенерировать пачку"}),o.jsx("button",{type:"button",className:"tab",disabled:bt,onClick:()=>{Tt()},children:bt?"Обновляю...":"Обновить историю"}),o.jsx("button",{type:"button",className:"tab",disabled:cn||le.length===0,onClick:()=>{Nr()},children:cn?"Запускаю...":"Запустить прогоны"})]}),o.jsx("div",{className:"autoruns-form-grid",children:o.jsxs("label",{className:"full-width",children:["Кейс-сет для запуска",o.jsxs("select",{value:ee,onChange:u=>oe(u.target.value),disabled:g.length===0,children:[g.length===0?o.jsx("option",{value:"",children:"нет генераций"}):null,g.map(u=>o.jsxs("option",{value:u.generation_id,children:[ur(u.created_at)," | ",u.mode," | ",u.count," | ",u.saved_case_set_file??"без файла"]},u.generation_id))]})]})}),o.jsxs("div",{className:"autoruns-generated-questions",children:[o.jsxs("div",{className:"autoruns-generated-questions-head",children:[o.jsxs("strong",{children:["Вопросы к запуску: ",le.length]}),o.jsx("button",{type:"button",className:"tab",onClick:()=>ae([...st?.questions??[]]),disabled:!st,children:"Восстановить"})]}),le.length===0?o.jsx("p",{className:"muted",children:"Список вопросов пуст. Сгенерируйте пачку или восстановите из выбранной генерации."}):o.jsx("div",{className:"autoruns-generated-questions-list",children:le.map((u,f)=>o.jsxs("div",{className:"autoruns-generated-question-item",children:[o.jsxs("span",{children:[f+1,". ",u]}),o.jsx("button",{type:"button",className:"autoruns-remove-question-btn",onClick:()=>ae(w=>w.filter((F,ve)=>ve!==f)),title:"Удалить вопрос из запуска","aria-label":"Удалить вопрос из запуска",children:"X"})]},`${f}-${u.slice(0,24)}`))})]}),o.jsx("p",{className:"muted",children:"Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету."}),o.jsxs("div",{className:"autoruns-autogen-list",children:[bt?o.jsx("p",{className:"muted",children:"Загружаю историю автогенераций..."}):null,!bt&&g.length===0?o.jsx("p",{className:"muted",children:"История автогенераций пока пустая."}):null,g.slice(0,30).map(u=>o.jsxs("article",{className:ee===u.generation_id?"autoruns-autogen-item selected":"autoruns-autogen-item",onClick:()=>oe(u.generation_id),children:[o.jsxs("header",{children:[o.jsx("strong",{children:ur(u.created_at)}),o.jsx("span",{children:u.mode})]}),o.jsxs("div",{className:"autoruns-run-meta",children:["id=",u.generation_id," | count=",u.count]}),o.jsxs("div",{className:"autoruns-run-meta",children:["домен=",u.domain??"общий",u.generated_by?` | автор=${u.generated_by}`:""]}),u.saved_case_set_file?o.jsxs("div",{className:"autoruns-run-meta",children:["кейс-сет=",u.saved_case_set_file]}):null,(u.questions??[]).length>0?o.jsx("p",{children:u.questions[0]}):null]},u.generation_id))]}),o.jsxs("details",{className:"autoruns-prompt-details",children:[o.jsx("summary",{children:"Копия активного промпта (только чтение)"}),o.jsxs("label",{children:["Системный",o.jsx("textarea",{readOnly:!0,value:h.systemPrompt})]}),o.jsxs("label",{children:["Разработчика",o.jsx("textarea",{readOnly:!0,value:h.developerPrompt})]}),o.jsxs("label",{children:["Доменный",o.jsx("textarea",{readOnly:!0,value:h.domainPrompt})]}),o.jsxs("label",{children:["Заметки по схеме",o.jsx("textarea",{readOnly:!0,value:h.schemaNotes})]}),o.jsxs("label",{children:["Примеры few-shot",o.jsx("textarea",{readOnly:!0,value:h.fewShotExamples})]})]}),Un?o.jsx("p",{className:"error-text",children:Un}):null]}),o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Выдача прогонов"})}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Всего"}),o.jsx("strong",{children:(T?.stats.runs_total??0)+(re?1:0)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Средний score"}),o.jsx("strong",{children:Ms(T?.stats.avg_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Тренд"}),o.jsx("strong",{children:T?mc(T.stats.trend):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Блокеры"}),o.jsx("strong",{children:T?.stats.blocking_runs??0})]})]}),o.jsxs("div",{className:"autoruns-run-list",children:[hn.map(u=>o.jsxs("button",{type:"button",className:ie===u.run_id?"autoruns-run-item selected":"autoruns-run-item",onClick:()=>{pt(u.run_id)},children:[o.jsxs("div",{className:"autoruns-run-head",children:[o.jsx("strong",{children:ur(u.run_timestamp)}),o.jsx("span",{children:Rf(u.eval_target)})]}),o.jsx("div",{className:"autoruns-run-meta",children:u.run_id}),o.jsxs("div",{className:"autoruns-run-meta",children:["режим=",u.mode??"нет данных"," | mock=",String(u.use_mock)]}),u.llm_provider||u.model?o.jsxs("div",{className:"autoruns-run-meta",children:["llm=",u.llm_provider??"нет данных"," | модель=",u.model??"нет данных"]}):null,o.jsxs("div",{className:"autoruns-run-meta",children:["промпт=",u.prompt_version??"нет данных"]}),o.jsxs("div",{className:"autoruns-run-foot",children:[o.jsxs("span",{children:["оценка: ",Ms(u.score_index)]}),o.jsxs("span",{children:["закрыто/открыто: ",u.closed_cases,"/",u.open_cases]})]}),o.jsxs("div",{className:"autoruns-run-foot",children:[o.jsxs("span",{children:["блокеры: ",u.blocking_failures]}),o.jsxs("span",{children:["качество: ",u.quality_failures]})]})]},u.run_id)),hn.length===0?o.jsx("p",{className:"muted",children:"За выбранный диапазон прогонов нет."}):null]})]}),o.jsxs("section",{className:"autoruns-col",children:[o.jsxs("div",{className:"autoruns-col-header",children:[o.jsx("h3",{children:"Диалог прогона"}),o.jsxs("div",{className:"autoruns-dialog-toolbar",children:[o.jsxs("label",{children:["Прогон",o.jsx("select",{value:ie,onChange:u=>{const f=u.target.value;pt(f)},children:hn.map(u=>o.jsxs("option",{value:u.run_id,children:[ur(u.run_timestamp)," | ",u.run_id]},u.run_id))})]}),o.jsxs("label",{children:["Кейс",o.jsxs("select",{value:Re,onChange:u=>{const f=u.target.value;xe(f),ie&&f&&tn(ie,f)},children:[(fe?.cases.length??0)>0?o.jsx("option",{value:ct,children:"ВСЕ кейсы подряд"}):null,(fe?.cases??[]).map(u=>o.jsxs("option",{value:u.case_id,children:[u.case_id," | ",u.status]},u.case_id))]})]})]})]}),o.jsxs("div",{className:"autoruns-case-list",children:[(fe?.cases.length??0)>0?o.jsxs("button",{type:"button",className:Re===ct?"autoruns-case-item selected":"autoruns-case-item",onClick:()=>{xe(ct),ie&&tn(ie,ct)},children:[o.jsx("span",{children:"ВСЕ кейсы подряд"}),o.jsx("span",{children:fe?.cases.length})]},ct):null,(fe?.cases??[]).map(u=>o.jsxs("button",{type:"button",className:Re===u.case_id?"autoruns-case-item selected":"autoruns-case-item",onClick:()=>{xe(u.case_id),ie&&tn(ie,u.case_id)},children:[o.jsx("span",{children:u.case_id}),o.jsxs("span",{children:[u.status,u.commented_count>0?` | комм=${u.commented_count}`:""]})]},u.case_id))]}),o.jsxs("div",{className:"autoruns-dialog-view",children:[Fn||Dt?o.jsx("p",{className:"muted",children:"Загружаю диалог..."}):null,!Fn&&!Dt&&(ne?.messages.length??0)===0?o.jsx("p",{className:"muted",children:"Диалог для этого прогона не найден."}):null,(ne?.messages??[]).map((u,f)=>{const w=u.role==="assistant"?"assistant":"user";return o.jsxs("article",{className:`autoruns-msg ${w}`,children:[o.jsxs("header",{children:[o.jsx("strong",{children:w==="assistant"?"Система":"Модель/вопрос"}),o.jsxs("div",{className:"autoruns-msg-head-actions",children:[u.case_id?o.jsx("span",{className:"autoruns-msg-case-tag",children:u.case_id}):null,o.jsx("span",{children:u.created_at?ur(u.created_at):"нет данных"}),w==="assistant"&&!br(ie)?o.jsxs(o.Fragment,{children:[o.jsx("button",{type:"button",className:u.commented?"autoruns-comment-icon commented":"autoruns-comment-icon",onClick:()=>fr(u),title:"\\u041a\\u043e\\u043c\\u043c\\u0435\\u043d\\u0442\\u0438\\u0440\\u043e\\u0432\\u0430\\u0442\\u044c \\u043e\\u0442\\u0432\\u0435\\u0442 \\u0441\\u0438\\u0441\\u0442\\u0435\\u043c\\u044b","aria-label":"\\u041a\\u043e\\u043c\\u043c\\u0435\\u043d\\u0442\\u0438\\u0440\\u043e\\u0432\\u0430\\u0442\\u044c \\u043e\\u0442\\u0432\\u0435\\u0442 \\u0441\\u0438\\u0441\\u0442\\u0435\\u043c\\u044b",children:o.jsx(Mf,{commented:u.commented})}),u.annotation?o.jsx("button",{type:"button",className:u.annotation.resolved?"autoruns-resolve-toggle resolved":"autoruns-resolve-toggle",onClick:()=>{Hn(u.annotation,!u.annotation.resolved)},disabled:Nn===u.annotation.annotation_id,title:u.annotation.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный","aria-label":u.annotation.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный",children:o.jsx(yc,{resolved:u.annotation.resolved})}):null]}):null]})]}),o.jsx("p",{children:u.text}),w==="assistant"&&u.annotation?o.jsxs("div",{className:"autoruns-msg-annotation",children:[o.jsx("strong",{children:hc(u.annotation.rating)}),o.jsx("span",{children:u.annotation.comment}),o.jsxs("span",{className:"muted",children:[u.annotation.manual_case_decision,u.annotation.annotation_author?` | ${u.annotation.annotation_author}`:""]})]}):null,(u.trace_id||u.reply_type)&&o.jsxs("footer",{children:[u.trace_id?o.jsxs("span",{children:["trace=",u.trace_id]}):null,u.reply_type?o.jsxs("span",{children:["reply_type=",u.reply_type]}):null]})]},u.message_id??`${w}-${f}`)})]})]}),E?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Режим ассистента"})}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"источник:"}),o.jsx("strong",{children:ne?.source??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"сессия:"}),o.jsx("strong",{children:ne?.session_id??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"контур прогона:"}),o.jsx("strong",{children:Ft?.eval_target??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"оценка прогона:"}),o.jsx("strong",{children:Ms(Ft?.score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"комментарии:"}),o.jsx("strong",{children:fe?.annotations_summary?.total??0})]})]}),o.jsx("h4",{children:"Пакет режима ассистента"}),o.jsx(dt,{value:ne?.assistant_mode??{note:"assistant_mode недоступен"}}),o.jsx("h4",{style:{marginTop:12},children:"Проверки кейса"}),o.jsx(dt,{value:Je?.checks??{note:"checks недоступен"}}),o.jsx("h4",{style:{marginTop:12},children:"Сабскор метрик"}),o.jsx(dt,{value:Je?.metric_subscores??{note:"metric_subscores недоступен"}})]}):null,D?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Режим декомпозиции"})}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"кейс:"}),o.jsx("strong",{children:Je?.case_id??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"домен:"}),o.jsx("strong",{children:Je?.domain??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"класс запроса:"}),o.jsx("strong",{children:Je?.query_class??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"trace:"}),o.jsx("strong",{children:Je?.trace_id??"нет данных"})]})]}),o.jsx("h4",{children:"Шаги декомпозиции"}),(ne?.decomposition.length??0)>0?o.jsx("ol",{className:"autoruns-decomposition-list",children:(ne?.decomposition??[]).map((u,f)=>o.jsx("li",{children:u},`${f}-${u.slice(0,24)}`))}):o.jsx("p",{className:"muted",children:"В логах кейса нет явной декомпозиции."})]}):null,K?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Прогресс / регресс"})}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Последний score"}),o.jsx("strong",{children:Ms(T?.stats.latest_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Предыдущий"}),o.jsx("strong",{children:Ms(T?.stats.previous_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Тренд"}),o.jsx("strong",{children:T?mc(T.stats.trend):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Пробелы качества"}),o.jsx("strong",{children:T?.stats.quality_gap_runs??0})]})]}),o.jsx("h4",{children:"Покрытие доменов (история)"}),gc(T?.stats.domain_coverage??[]),o.jsx("h4",{style:{marginTop:14},children:"Покрытие доменов (выбранный прогон)"}),gc(fe?.coverage.domain_coverage??[]),o.jsx("h4",{style:{marginTop:14},children:"Очереди фиксов пост-анализа"}),Jt?o.jsx("p",{className:"muted",children:"Собираю пост-анализ..."}):null,Jt?null:o.jsx("div",{className:"autoruns-stats-grid",children:Object.entries(ce?.post_analysis.stats.by_queue??{}).map(([u,f])=>o.jsxs("div",{children:[o.jsx("span",{children:u}),o.jsx("strong",{children:f})]},u))}),o.jsxs("div",{className:"autoruns-autogen-list",children:[(ce?.post_analysis.recommended_regression_candidates??[]).slice(0,12).map(u=>o.jsxs("article",{className:"autoruns-autogen-item",children:[o.jsxs("header",{children:[o.jsx("strong",{children:u.manual_case_decision}),o.jsxs("span",{children:[u.rating,"/5"]})]}),o.jsxs("div",{className:"autoruns-run-meta",children:[u.domain??"неизвестно"," / ",u.query_class??"неизвестно"]}),o.jsx("p",{children:u.comment})]},u.annotation_id)),!Jt&&(ce?.post_analysis.recommended_regression_candidates.length??0)===0?o.jsx("p",{className:"muted",children:"Рекомендованных кандидатов пока нет."}):null]})]}):null,Z?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Комментарии"})}),o.jsx("h4",{children:"Размеченные ответы"}),o.jsxs("div",{className:"autoruns-comment-filter-row",children:[o.jsxs("label",{children:["Фильтр решений",o.jsxs("select",{value:Se,onChange:u=>Le(u.target.value),children:[o.jsx("option",{value:"all",children:"все"}),(Ze.length>0?Ze:Ue?.enum??[]).map(u=>o.jsx("option",{value:u,children:String(Ue?.labels?.[u]??u)},u))]})]}),o.jsx("button",{type:"button",className:"tab autoruns-resolved-filter-toggle",onClick:()=>$e(u=>!u),children:Oe?"Показать выполненные":"Скрыть выполненные"})]}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Комментариев"}),o.jsx("strong",{children:Ve.length})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Средний рейтинг"}),o.jsx("strong",{children:Pn===null?"нет данных":`${Pn.toFixed(2)} / 5`})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Последний"}),o.jsx("strong",{children:Ve.length>0?ur(Ve[0].updated_at):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Статус"}),o.jsx("strong",{children:Xt?"обновляю":"готово"})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:Xt,onClick:()=>{vt()},children:Xt?"Обновляю...":"Обновить список"}),o.jsx("button",{type:"button",className:"tab",disabled:Jt,onClick:()=>{et()},children:Jt?"Идет пост-анализ...":"Обновить пост-анализ"})]}),o.jsxs("div",{className:"autoruns-comments-list",children:[Xt?o.jsx("p",{className:"muted",children:"Загружаю комментарии..."}):null,!Xt&&Ve.length===0?o.jsx("p",{className:"muted",children:G.length===0?"Пока нет откомментированных ответов.":"Нет открытых кейсов по текущему фильтру."}):null,Ve.map(u=>o.jsxs("article",{className:Ct===u.annotation_id?"autoruns-comment-item selected":"autoruns-comment-item",onClick:()=>{Vn(u)},role:"button",tabIndex:0,onKeyDown:f=>{(f.key==="Enter"||f.key===" ")&&(f.preventDefault(),Vn(u))},children:[o.jsxs("div",{className:"autoruns-comment-head",children:[o.jsx("strong",{children:hc(u.rating)}),o.jsxs("div",{className:"autoruns-comment-head-actions",children:[o.jsx("span",{children:ur(u.updated_at)}),o.jsx("button",{type:"button",className:u.resolved?"autoruns-resolve-toggle resolved":"autoruns-resolve-toggle",onClick:f=>{f.preventDefault(),f.stopPropagation(),Hn(u,!u.resolved)},disabled:Nn===u.annotation_id,title:u.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный","aria-label":u.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный",children:o.jsx(yc,{resolved:u.resolved})})]})]}),o.jsx("div",{className:"autoruns-run-meta",children:u.run_id}),o.jsxs("div",{className:"autoruns-run-meta",children:["case=",u.case_id," | msg=",u.message_index]}),o.jsxs("div",{className:"autoruns-run-meta",children:["decision=",u.manual_case_decision,u.annotation_author?` | author=${u.annotation_author}`:""]}),u.resolved_at?o.jsxs("div",{className:"autoruns-run-meta",children:["выполнено",": ",ur(u.resolved_at),u.resolved_by?` | by=${u.resolved_by}`:""]}):null,u.context.question_text?o.jsxs("p",{children:["Q: ",u.context.question_text]}):null,u.context.answer_text?o.jsxs("p",{children:["A: ",u.context.answer_text]}):null,o.jsx("p",{children:u.comment})]},u.annotation_id))]}),je?o.jsxs(o.Fragment,{children:[o.jsx("h4",{children:"Тех-контекст брака"}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"trace:"}),o.jsx("strong",{children:je.technical_context.trace_id??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"reply_type:"}),o.jsx("strong",{children:je.technical_context.reply_type??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"domain:"}),o.jsx("strong",{children:je.technical_context.domain??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"query_class:"}),o.jsx("strong",{children:je.technical_context.query_class??"нет данных"})]})]}),o.jsx("h4",{children:"JSON разбор"}),o.jsx(dt,{value:{annotation_id:je.annotation_id,run_id:je.run_id,case_id:je.case_id,message_index:je.message_index,rating:je.rating,comment:je.comment,manual_case_decision:je.manual_case_decision,annotation_author:je.annotation_author,resolved:je.resolved,resolved_at:je.resolved_at,resolved_by:je.resolved_by,context:je.context,technical_context:je.technical_context,case_summary:je.case_summary?{case_id:je.case_summary.case_id,domain:je.case_summary.domain,query_class:je.case_summary.query_class,checks:je.case_summary.checks,metric_subscores:je.case_summary.metric_subscores}:null}})]}):null]}):null]}),de.open?o.jsx("div",{className:"autoruns-comment-modal-backdrop",onClick:u=>{u.target===u.currentTarget&&Rn()},children:o.jsxs("div",{className:"autoruns-comment-modal",children:[o.jsx("h3",{children:"Комментарий к ответу системы"}),o.jsx("p",{className:"muted",children:"Оцените ответ по 5-балльной шкале и добавьте комментарий по браку."}),En?o.jsxs(o.Fragment,{children:[o.jsxs("details",{className:"autoruns-prompt-details",open:!0,children:[o.jsx("summary",{children:"Вопрос пользователя"}),o.jsx("p",{className:"autoruns-comment-quote",children:dr?.text??"Вопрос в диалоге не найден."})]}),o.jsxs("details",{className:"autoruns-prompt-details",open:!0,children:[o.jsx("summary",{children:"Ответ системы"}),o.jsx("p",{className:"autoruns-comment-quote",children:En.text})]})]}):null,o.jsx("div",{className:"autoruns-rating-row",role:"group","aria-label":"Рейтинг ответа",children:[1,2,3,4,5].map(u=>o.jsx("button",{type:"button",className:de.rating>=u?"autoruns-rating-dot active":"autoruns-rating-dot",onClick:()=>We(f=>({...f,rating:u})),disabled:de.saving,"aria-label":`Оценка ${u}`,children:de.rating>=u?"●":"○"},u))}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Решение по кейсу",o.jsx("select",{value:de.manualCaseDecision,onChange:u=>We(f=>({...f,manualCaseDecision:u.target.value})),disabled:de.saving,children:(Ze.length>0?Ze:Ue?.enum??[$o]).map(u=>o.jsx("option",{value:u,children:String(Ue?.labels?.[u]??u)},u))})]}),o.jsxs("label",{children:["Автор комментария",o.jsx("input",{value:de.annotationAuthor,onChange:u=>We(f=>({...f,annotationAuthor:u.target.value})),placeholder:"manual_reviewer",disabled:de.saving})]})]}),o.jsxs("label",{children:["Комментарий",o.jsx("textarea",{value:de.comment,onChange:u=>We(f=>({...f,comment:u.target.value})),placeholder:"Почему ответ бракованный, что именно пошло не так, какие технические детали проверить.",rows:4,disabled:de.saving})]}),de.error?o.jsx("p",{className:"error-text",children:de.error}):null,o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>{Er()},disabled:de.saving,children:de.saving?"Сохраняю...":"Готово"}),o.jsx("button",{type:"button",className:"tab",onClick:()=>Rn(),disabled:de.saving,children:"Отмена"})]})]})}):null]})}const Lf=/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json|debug_payload|technical_breakdown)\b/i,Af=[/\b(?:debug_payload_json|technical_breakdown_json)\b/i,/\b(?:route_summary|semantic_profile|domain_scope|relation_patterns|account_scope)\b/i,/\b(?:coverage_report|retrieval_status|problem_unit_state|candidate_evidence)\b/i,/\b(?:graph_domain_scope|graph_runtime|selection_reason|why_included)\b/i];function Of(i){try{return JSON.stringify(i,null,2)}catch{return String(i)}}function zf(i){const h=String(i??""),p=h.match(Lf);return(p?h.slice(0,p.index):h).replace(/###\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)[\s\S]*?(?:```[\s\S]*?```|$)/gi,"").replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)\b[\s\S]*$/gi,"").split(/\r?\n/g).map(K=>K.trimEnd()).filter(K=>K.trim().length>0).filter(K=>!Af.some(Z=>Z.test(K))).join(` +`).trim()}function Df(i,h,p="default"){const O=p==="technical",E=[];E.push("# Assistant conversation export"),E.push(`session_id: ${i||"n/a"}`),E.push(`export_mode: ${p}`),E.push(`exported_at: ${new Date().toISOString()}`),E.push("");for(let D=0;D{T.current&&(T.current.scrollTop=T.current.scrollHeight)},[h,I]),S.useEffect(()=>()=>{H.current!==null&&window.clearTimeout(H.current)},[]);async function G(se){if(h.length===0)return;const Se=Df(i,h,se),Le=await Uf(Se);X(se==="technical"?"тех":"чат"),ye(Le?"success":"error"),H.current!==null&&window.clearTimeout(H.current),H.current=window.setTimeout(()=>{ye("idle")},2200)}return o.jsxs(Sn,{title:"Режим ассистента",subtitle:"Диалоговый слой поверх normalizer, маршрутизации и factual retrieval.",actions:o.jsxs("div",{className:"assistant-panel-actions",children:[o.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>{G("default")},disabled:h.length===0,title:"Экспорт только user-facing чата",children:"Скопировать чат"}),o.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>{G("technical")},disabled:h.length===0,title:"Технический экспорт с debug payload",children:"Скопировать техчат"}),fe==="success"?o.jsxs("span",{className:"assistant-copy-feedback success",children:["Скопировано (",ne,")"]}):null,fe==="error"?o.jsx("span",{className:"assistant-copy-feedback error",children:"Ошибка копирования"}):null,o.jsx("span",{className:"status-chip",children:i?`session: ${i}`:"новая сессия"})]}),children:[o.jsxs("div",{ref:T,className:"assistant-chat-list",children:[h.length===0?o.jsx("div",{className:"assistant-empty muted",children:"Диалог пуст. Отправьте первый вопрос, чтобы запустить контур ассистента."}):null,h.map(se=>o.jsxs("article",{className:`assistant-msg ${se.role}`,children:[o.jsxs("header",{className:"assistant-msg-head",children:[o.jsx("strong",{children:$f(se.role)}),o.jsx("span",{children:Ff(se.created_at)})]}),o.jsx("div",{className:"assistant-msg-body",children:se.text}),se.role==="assistant"&&se.debug?o.jsxs("details",{className:"assistant-debug",children:[o.jsx("summary",{children:"Показать технический разбор"}),o.jsx(dt,{value:se.debug})]}):null]},se.message_id))]}),o.jsxs("div",{className:"assistant-compose",children:[o.jsxs("label",{className:"full-width",children:["Сообщение",o.jsx("textarea",{value:p,onChange:se=>O(se.target.value),rows:4,placeholder:"Введите вопрос к данным компании..."})]}),o.jsxs("div",{className:"button-row",children:[o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:E,onChange:se=>D(se.target.checked)}),"Mock-режим"]}),o.jsx("button",{type:"button",onClick:()=>K(),disabled:B||!p.trim(),children:B?"Выполняю...":"Отправить"}),o.jsx("button",{type:"button",onClick:()=>Z(),disabled:B&&h.length===0,children:"Сбросить сессию"})]}),I?o.jsx("p",{className:"diff-summary",children:I}):null,q?o.jsx("p",{className:"error-text",children:q}):null]})]})}function xc({value:i,modelOptions:h,modelsBusy:p,onChange:O,onReloadModels:E,onTestConnection:D,onSaveLocalConfig:K,lastStatus:Z,busy:B}){const I=i.llmProvider==="local",q=h.includes(i.model),[T,H]=S.useState(String(i.temperature)),[fe,ye]=S.useState(String(i.maxOutputTokens));S.useEffect(()=>{H(String(i.temperature))},[i.temperature]),S.useEffect(()=>{ye(String(i.maxOutputTokens))},[i.maxOutputTokens]);const ne=G=>{const se=G.replace(",",".").trim();if(!se){H(String(i.temperature));return}const Se=Number(se);if(!Number.isFinite(Se)){H(String(i.temperature));return}O({...i,temperature:Se}),H(String(Se))},X=G=>{const se=G.trim();if(!se){ye(String(i.maxOutputTokens));return}const Se=Number.parseInt(se,10);if(!Number.isFinite(Se)||Se<=0){ye(String(i.maxOutputTokens));return}O({...i,maxOutputTokens:Se}),ye(String(Se))};return o.jsxs(Sn,{title:"LLM Connection",subtitle:"Switch between OpenAI cloud and local OpenAI-compatible server.",actions:o.jsx("span",{className:"status-chip",children:Z||"Status: not checked"}),children:[o.jsxs("div",{className:"grid-two",children:[o.jsxs("label",{children:["Provider",o.jsxs("select",{value:i.llmProvider,onChange:G=>{const se=G.target.value==="local"?"local":"openai";O({...i,llmProvider:se,baseUrl:se==="local"?"http://127.0.0.1:1234/v1":"https://api.openai.com/v1"})},children:[o.jsx("option",{value:"openai",children:"OpenAI (token)"}),o.jsx("option",{value:"local",children:"Local (LM Studio / OpenAI-compatible)"})]})]}),o.jsxs("label",{children:["Model",o.jsxs("select",{value:q?i.model:"__manual__",onChange:G=>{const se=G.target.value;se!=="__manual__"&&O({...i,model:se})},children:[o.jsx("option",{value:"__manual__",children:"Manual input"}),h.map(G=>o.jsx("option",{value:G,children:G},G))]})]}),o.jsxs("label",{children:["Model ID (manual)",o.jsx("input",{value:i.model,onChange:G=>O({...i,model:G.target.value}),placeholder:"qwen2.5-14b-instruct or lmstudio loaded model id"})]}),I?null:o.jsxs("label",{className:"full-width",children:["OpenAI API Key",o.jsx("input",{type:"password",value:i.apiKey,onChange:G=>O({...i,apiKey:G.target.value}),placeholder:"sk-..."})]}),o.jsxs("label",{className:I?"full-width":void 0,children:[I?"Local server base URL":"Base URL",o.jsx("input",{value:i.baseUrl,onChange:G=>O({...i,baseUrl:G.target.value}),placeholder:I?"http://127.0.0.1:1234/v1":"https://api.openai.com/v1"})]}),o.jsxs("label",{children:["Temperature",o.jsx("input",{type:"number",step:"0.1",value:T,onChange:G=>H(G.target.value),onBlur:G=>ne(G.target.value),onKeyDown:G=>{G.key==="Enter"&&ne(G.target.value)}})]}),o.jsxs("label",{children:["Max output tokens",o.jsx("input",{type:"number",value:fe,onChange:G=>ye(G.target.value),onBlur:G=>X(G.target.value),onKeyDown:G=>{G.key==="Enter"&&X(G.target.value)}})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>K(),children:"Save local config"}),o.jsx("button",{type:"button",onClick:()=>E(),disabled:B||p,children:p?"Loading models...":"Load model list"}),o.jsx("button",{type:"button",onClick:()=>D(),disabled:B,children:B?"Checking...":"Test connection"})]})]})}function Hf({items:i,onRefresh:h,onOpenTrace:p}){return o.jsx(Sn,{title:"История нормализаций",subtitle:"Короткий вопрос, confidence, route hint и статус валидации.",actions:o.jsx("button",{type:"button",onClick:()=>h(),children:"Обновить"}),children:o.jsxs("div",{className:"history-list",children:[i.length===0?o.jsx("p",{className:"muted",children:"История пока пустая."}):null,i.map(O=>o.jsxs("button",{type:"button",className:"history-item",onClick:()=>p(O.trace_id),children:[o.jsxs("div",{className:"history-row",children:[o.jsx("strong",{children:O.route_hint??"route: n/a"}),o.jsx("span",{children:O.validation_passed?"schema: ok":"schema: fail"})]}),o.jsx("p",{children:O.question_short}),o.jsxs("div",{className:"history-row",children:[o.jsx("span",{children:O.model}),o.jsx("span",{children:new Date(O.timestamp).toLocaleString("ru-RU")})]})]},O.trace_id))]})})}function $n(i){return i==null||i===""?"—":String(i)}function Vf({result:i}){return o.jsx(Sn,{title:"Runtime метрики",subtitle:"trace_id, токены, latency и статус валидации.",children:o.jsxs("div",{className:"metrics-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"trace_id"}),o.jsx("strong",{children:$n(i?.trace_id)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"request_started_at"}),o.jsx("strong",{children:$n(i?new Date(Date.now()-i.latency_ms).toISOString():null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"request_finished_at"}),o.jsx("strong",{children:$n(i?new Date().toISOString():null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"latency_ms"}),o.jsx("strong",{children:$n(i?.latency_ms)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"input_tokens"}),o.jsx("strong",{children:$n(i?.usage?.input_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"output_tokens"}),o.jsx("strong",{children:$n(i?.usage?.output_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"total_tokens"}),o.jsx("strong",{children:$n(i?.usage?.total_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"validation_status"}),o.jsx("strong",{children:i?.validation?.passed?"passed":"failed"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"prompt_version"}),o.jsx("strong",{children:$n(i?.prompt_version)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"schema_version"}),o.jsx("strong",{children:$n(i?.schema_version)})]})]})})}const Qf={normalized:"Normalized JSON",fragments:"Fragment View",scope:"Scope View",flags:"Flags View",route:"Route Simulation",raw:"Raw model output",validation:"Validation",logs:"Logs"};function Wf(i){return i&&typeof i=="object"?i:null}function Kf({tab:i,onTabChange:h,result:p,appLogs:O}){const E=["normalized","fragments","scope","flags","route","raw","validation","logs"],D=Wf(p?.normalized),K=String(D?.schema_version??""),Z=K==="normalized_query_v2"||K==="normalized_query_v2_0_1"||K==="normalized_query_v2_0_2",B=Z?{fragments:D?.fragments??[],discarded_fragments:D?.discarded_fragments??[]}:{note:"Fragment View доступен для normalized_query_v2."},I=Z?{message_in_scope:D?.message_in_scope??null,scope_confidence:D?.scope_confidence??null,contains_multiple_tasks:D?.contains_multiple_tasks??null,global_notes:D?.global_notes??null}:{note:"Scope View доступен для normalized_query_v2."},q=Z?Array.isArray(D?.fragments)?(D?.fragments).map(T=>({fragment_id:T.fragment_id??null,domain_relevance:T.domain_relevance??null,candidate_labels:T.candidate_labels??[],execution_readiness:T.execution_readiness??null,clarification_reason:T.clarification_reason??null,soft_assumption_used:T.soft_assumption_used??[],route_status:T.route_status??null,no_route_reason:T.no_route_reason??null,flags:T.flags??{}})):[]:{note:"Flags View доступен для normalized_query_v2."};return o.jsxs(Sn,{title:"Выходные данные",subtitle:"Structured output и диагностические вкладки.",children:[o.jsx("div",{className:"tab-row",children:E.map(T=>o.jsx("button",{type:"button",className:i===T?"tab active":"tab",onClick:()=>h(T),children:Qf[T]},T))}),i==="normalized"?o.jsx(dt,{value:p?.normalized??{note:"Нет данных."}}):null,i==="fragments"?o.jsx(dt,{value:B}):null,i==="scope"?o.jsx(dt,{value:I}):null,i==="flags"?o.jsx(dt,{value:q}):null,i==="route"?o.jsx(dt,{value:p?.route_hint_summary??{note:"Нет данных."}}):null,i==="raw"?o.jsx(dt,{value:p?.raw_model_output??{note:"Нет данных."}}):null,i==="validation"?o.jsx(dt,{value:p?.validation??{note:"Нет данных."}}):null,i==="logs"?o.jsx(dt,{value:O}):null]})}function _c({value:i,onChange:h,presets:p,selectedPresetId:O,onSelectPreset:E,onLoadPreset:D,onSavePreset:K,onResetDefaults:Z,onDiffPrevious:B,presetName:I,onPresetNameChange:q,diffSummary:T}){return o.jsxs(Sn,{title:"Prompt Manager",subtitle:"Системный, developer и domain уровни управляются отдельно.",children:[o.jsxs("div",{className:"prompt-manager-grid",children:[o.jsxs("label",{children:["Системный prompt",o.jsx("textarea",{value:i.systemPrompt,onChange:H=>h({...i,systemPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Developer / Instruction prompt",o.jsx("textarea",{value:i.developerPrompt,onChange:H=>h({...i,developerPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Domain prompt",o.jsx("textarea",{value:i.domainPrompt,onChange:H=>h({...i,domainPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Schema notes",o.jsx("textarea",{value:i.schemaNotes,onChange:H=>h({...i,schemaNotes:H.target.value}),rows:6})]}),o.jsxs("label",{className:"full-width",children:["Few-shot examples",o.jsx("textarea",{value:i.fewShotExamples,onChange:H=>h({...i,fewShotExamples:H.target.value}),rows:8})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsxs("select",{value:O,onChange:H=>E(H.target.value),children:[o.jsx("option",{value:"",children:"Выберите preset..."}),p.map(H=>o.jsx("option",{value:H.id,children:H.name},H.id))]}),o.jsx("button",{type:"button",onClick:()=>D(),children:"Загрузить preset"}),o.jsx("input",{value:I,onChange:H=>q(H.target.value),placeholder:"Имя для сохранения"}),o.jsx("button",{type:"button",onClick:()=>K(),children:"Сохранить preset"}),o.jsx("button",{type:"button",onClick:()=>B(),children:"Diff с предыдущим"}),o.jsx("button",{type:"button",onClick:()=>Z(),children:"Сбросить к default"})]}),T?o.jsx("p",{className:"diff-summary",children:T}):null]})}function qf({value:i,onChange:h,onApplyBatchFormat:p,onNormalize:O,busy:E,useMock:D,onUseMockChange:K,errorMessage:Z}){return o.jsxs(Sn,{title:"Запрос пользователя",subtitle:"NDC semantic front-end: нормализуем, но не отвечаем за бухгалтерскую суть.",children:[o.jsxs("div",{className:"grid-two",children:[o.jsxs("label",{className:"full-width",children:["Raw user question",o.jsx("textarea",{value:i.userQuestion,onChange:B=>h({...i,userQuestion:B.target.value}),rows:6,placeholder:"Например: По каким покупателям у нас на конец июня висят отгрузки без оплаты..."})]}),o.jsxs("label",{className:"full-width",children:["Batch queries (`;` separator)",o.jsx("textarea",{value:i.batchQuestionsRaw,onChange:B=>h({...i,batchQuestionsRaw:B.target.value}),onBlur:()=>p(),rows:8,placeholder:"Вопрос 1; Вопрос 2; Вопрос 3"})]}),o.jsxs("label",{children:["Optional period context",o.jsx("input",{value:i.periodHint,onChange:B=>h({...i,periodHint:B.target.value})})]}),o.jsxs("label",{children:["Optional business context",o.jsx("input",{value:i.businessContext,onChange:B=>h({...i,businessContext:B.target.value})})]}),o.jsxs("label",{children:["Optional expected route (eval)",o.jsx("input",{value:i.expectedRoute,onChange:B=>h({...i,expectedRoute:B.target.value})})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:D,onChange:B=>K(B.target.checked)}),"Mock-режим (без вызова OpenAI)"]}),o.jsx("button",{type:"button",onClick:()=>p(),disabled:E||!i.batchQuestionsRaw.trim(),children:"Применить `;` в переносы"}),o.jsx("button",{type:"button",onClick:()=>O(!1),disabled:E||!i.userQuestion.trim(),children:E?"Нормализуем...":"Normalize"}),o.jsx("button",{type:"button",onClick:()=>O(!0),disabled:E||!i.userQuestion.trim(),children:E?"Сохраняем...":"Normalize + Save as test case"})]}),Z?o.jsx("p",{className:"error-text",children:Z}):null]})}function Gf({runs:i,selectedRunId:h,onSelectRun:p,onStartRun:O,onFinishRun:E,onRefreshRuns:D,onRunEval:K,onCopyEvalReport:Z,evalBusy:B,traceItems:I,evalReport:q}){return o.jsxs(Sn,{title:"NDC Run Monitor",subtitle:"Важно: кнопка Запустить run создает только run-сущность. Кнопка eval запускает batch-проверку normalizer v2.0.2.",children:[o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>O(),children:"Запустить run"}),o.jsx("button",{type:"button",onClick:()=>E(),disabled:!h,children:"Завершить выбранный run"}),o.jsx("button",{type:"button",onClick:()=>D(),children:"Обновить runs"}),o.jsx("button",{type:"button",onClick:()=>K(),disabled:B,children:B?"Идет eval v2.0.2...":"Запустить eval v2.0.2"})]}),o.jsxs("div",{className:"runtime-stack",children:[o.jsxs("div",{className:"runtime-runs",children:[i.map(T=>o.jsxs("button",{type:"button",className:h===T.runId?"history-item selected":"history-item",onClick:()=>p(T.runId),children:[o.jsxs("div",{className:"history-row",children:[o.jsx("strong",{children:T.status}),o.jsx("span",{children:T.runId})]}),o.jsxs("div",{className:"history-row",children:[o.jsx("span",{children:T.sessionId}),o.jsx("span",{children:new Date(T.updatedAt).toLocaleString("ru-RU")})]})]},T.runId)),i.length===0?o.jsx("p",{className:"muted",children:"Нет активных запусков."}):null]}),o.jsxs("div",{className:"runtime-details",children:[o.jsx("h3",{children:"Trace выбранного run"}),o.jsx(dt,{value:I}),o.jsxs("div",{className:"eval-report-wrap",children:[o.jsx("h3",{style:{marginTop:12},children:"Отчет eval"}),o.jsx(dt,{value:q??{note:"Eval пока не запускался"}}),o.jsx("button",{type:"button",className:"copy-cube-button",title:"Скопировать отчет eval",onClick:()=>Z(),children:"⧉"})]})]})]})]})}const Jf={llmProvider:"openai",apiKey:"",model:"gpt-4o-mini",baseUrl:"https://api.openai.com/v1",temperature:0,maxOutputTokens:700},Sc={systemPrompt:"Ты semantic-normalizer для бухгалтерского ассистента NDC. Возвращай только JSON по схеме normalized_query_v2_0_2.",developerPrompt:"Сначала делай decomposition сообщения на task fragments, затем определяй domain scope и route-critical flags. Для каждого fragment заполняй execution_readiness + route_status + no_route_reason. Если fragment routable, не оставляй его в no_route.",domainPrompt:"Контур: данные текущего предприятия в 1С/NDC. In-scope: документы, проводки, взаиморасчеты, остатки, периодное закрытие, аномалии и контрольные проверки. Out-of-scope: общая теория, законы и оффтоп.",schemaNotes:"schema_version: normalized_query_v2_0_2. Строгий JSON без дополнительных полей.",fewShotExamples:"Q: Проверь по поставщикам хвосты и разложи цепочку документов/оплат. => fragment in_scope, flags: multi_entity + chain_explanation. Q: Как вообще по ФСБУ? => out_of_scope/generic_accounting."},bf={userQuestion:"",batchQuestionsRaw:"",periodHint:"",businessContext:"",expectedRoute:""},Ei={colors:{backgroundRgb:"18, 18, 18",mainSurfaceRgb:"25, 25, 25",horizontalSurfaceRgb:"30, 30, 30",focusSurfaceRgb:"35, 35, 35",activeRgb:"167, 59, 255",activeTextRgb:"240, 240, 240",textMainRgb:"240, 240, 240",textMutedRgb:"166, 166, 166",dangerRgb:"126, 126, 126",scrollbarTrackRgb:"20, 20, 20",scrollbarThumbRgb:"30, 30, 30",scrollbarThumbHoverRgb:"30, 50, 30"},layout:{modeColumnWidthPx:440,modeToggleWidthPx:188}},wc="ndc_normalizer_session_config_v1",jc="ndc_autoruns_layout_config_v1",Yf="ndc-autoruns-save",Pi=["Анализ запроса","Получение данных","Подготовка ответа"],Xf="assistant",Ri="normalizer_v2_0_2",kc="address_query_runtime_v1",Zf=["normalized","fragments","scope","flags","route","raw","validation","logs"];function ep(i){return`[${new Date().toLocaleTimeString("ru-RU")}] ${i}`}function tp(i,h){if(!h)return"Previous preset is not selected.";const O=["systemPrompt","developerPrompt","domainPrompt","schemaNotes","fewShotExamples"].filter(E=>i[E]!==h[E]).map(E=>`${E}: ${Math.abs(i[E].length-h[E].length)} chars delta`);return O.length===0?"No changes against previous preset.":`Changed fields: ${O.length}. ${O.join(" | ")}`}function np(){const[i,h]=S.useState(Jf),[p,O]=S.useState(Sc),[E,D]=S.useState(bf),[K,Z]=S.useState(null),[B,I]=S.useState([]),[q,T]=S.useState([]),[H,fe]=S.useState("normalized"),[ye,ne]=S.useState(!1),[X,G]=S.useState(!1),[se,Se]=S.useState([]),[Le,Oe]=S.useState(""),[$e,Ue]=S.useState([]),[Ge,Ze]=S.useState(""),[ft,Ct]=S.useState("NDC custom preset"),[rt,ie]=S.useState(null),[Be,Re]=S.useState(""),[xe,me]=S.useState(!1),[A,R]=S.useState([]),[L,g]=S.useState(""),[j,ee]=S.useState([]),[oe,le]=S.useState(!1),[ae,re]=S.useState(null),[he,ce]=S.useState(""),[ke,zt]=S.useState(Xf),[wn,cn]=S.useState(!0),[Nt,Jt]=S.useState(!0),[jn,bt]=S.useState(!0),[kn,Cn]=S.useState(!0),[Et,Dt]=S.useState(!0),[Yt,Fn]=S.useState(!0),[dn,Xt]=S.useState(!0),[fn,Nn]=S.useState(!0),[$t,Un]=S.useState(!0),[Ee,pn]=S.useState(!0),[Pt,cr]=S.useState(!0),[gt,de]=S.useState(!0),[We,mn]=S.useState(!0),[Rt,Zt]=S.useState(!0),[st,Ft]=S.useState(!0),[Je,Ve]=S.useState(""),[je,En]=S.useState([]),[dr,Pn]=S.useState(""),[hn,ge]=S.useState(!1),[en,Ut]=S.useState(""),[vt,Tt]=S.useState(""),Mt=S.useRef(!1),et=S.useRef(!1);S.useEffect(()=>{const x=document.documentElement,{colors:z}=Ei;x.style.setProperty("--rgb-background",z.backgroundRgb),x.style.setProperty("--rgb-surface-main",z.mainSurfaceRgb),x.style.setProperty("--rgb-surface-horizontal",z.horizontalSurfaceRgb),x.style.setProperty("--rgb-surface-focus",z.focusSurfaceRgb),x.style.setProperty("--rgb-active",z.activeRgb),x.style.setProperty("--rgb-active-text",z.activeTextRgb),x.style.setProperty("--rgb-text-main",z.textMainRgb),x.style.setProperty("--rgb-text-muted",z.textMutedRgb),x.style.setProperty("--rgb-danger",z.dangerRgb),x.style.setProperty("--rgb-scrollbar-track",z.scrollbarTrackRgb),x.style.setProperty("--rgb-scrollbar-thumb",z.scrollbarThumbRgb),x.style.setProperty("--rgb-scrollbar-thumb-hover",z.scrollbarThumbHoverRgb),x.style.setProperty("--mode-column-width",`${Ei.layout.modeColumnWidthPx}px`),x.style.setProperty("--mode-toggle-width",`${Ei.layout.modeToggleWidthPx}px`)},[]);const Y=x=>{T(z=>[ep(x),...z].slice(0,300))};function tn(){let x=0;Ut(Pi[0]);const z=window.setInterval(()=>{x=Math.min(x+1,Pi.length-1),Ut(Pi[x])},650);return()=>window.clearInterval(z)}S.useEffect(()=>{const x=localStorage.getItem(wc);if(x)try{const P=JSON.parse(x);h(pe=>({...pe,llmProvider:P.llmProvider==="local"?"local":"openai",model:P.model??pe.model,baseUrl:P.baseUrl??pe.baseUrl,temperature:P.temperature??pe.temperature,maxOutputTokens:P.maxOutputTokens??pe.maxOutputTokens}))}catch{}const z=localStorage.getItem(jc);if(z)try{const P=JSON.parse(z);(P.uiMode==="assistant"||P.uiMode==="decomposition"||P.uiMode==="autoruns")&&zt(P.uiMode),P.activeTab&&Zf.includes(P.activeTab)&&fe(P.activeTab),typeof P.showAutorunsAssistantMode=="boolean"&&cn(P.showAutorunsAssistantMode),typeof P.showAutorunsDecompositionMode=="boolean"&&Jt(P.showAutorunsDecompositionMode),typeof P.showAutorunsProgressMode=="boolean"&&bt(P.showAutorunsProgressMode),typeof P.showAutorunsCommentsMode=="boolean"&&Cn(P.showAutorunsCommentsMode),typeof P.showAssistantConnectionMode=="boolean"&&Dt(P.showAssistantConnectionMode),typeof P.showAssistantPromptMode=="boolean"&&Fn(P.showAssistantPromptMode),typeof P.showAssistantChatMode=="boolean"&&Xt(P.showAssistantChatMode),typeof P.showAssistantSamMode=="boolean"&&Nn(P.showAssistantSamMode),typeof P.showDecompositionConnectionMode=="boolean"&&Un(P.showDecompositionConnectionMode),typeof P.showDecompositionPromptMode=="boolean"&&pn(P.showDecompositionPromptMode),typeof P.showDecompositionQueryMode=="boolean"&&cr(P.showDecompositionQueryMode),typeof P.showDecompositionOutputMode=="boolean"&&de(P.showDecompositionOutputMode),typeof P.showDecompositionMetricsMode=="boolean"&&mn(P.showDecompositionMetricsMode),typeof P.showDecompositionHistoryMode=="boolean"&&Zt(P.showDecompositionHistoryMode),typeof P.showDecompositionRuntimeMode=="boolean"&&Ft(P.showDecompositionRuntimeMode),P.prompts&&(O(pe=>({...pe,...P.prompts})),et.current=!0)}catch{}pt(),gn(),ot()},[]);async function pt(){try{const x=await Ie.loadHistory();I(x.items??[])}catch(x){Y(`History load error: ${x instanceof Error?x.message:String(x)}`)}}async function gn(){try{const z=(await Ie.loadPresets()).presets??[];if(Ue(z),et.current){Mt.current=!0;return}if(Mt.current)return;const P=z.find(pe=>pe.prompt_version===Ri)??z.find(pe=>pe.id==="default-normalizer-v2_0_2");if(!P){Mt.current=!0,Y(`Preset autoload skipped: ${Ri} not found.`);return}Ze(P.id),ie(p),O({systemPrompt:P.systemPrompt,developerPrompt:P.developerPrompt,domainPrompt:P.domainPrompt,schemaNotes:P.schemaNotes??"",fewShotExamples:P.fewShotExamples??""}),Mt.current=!0,Y(`Preset autoloaded: ${P.name} (${P.prompt_version}).`)}catch(x){Y(`Presets load error: ${x instanceof Error?x.message:String(x)}`)}}async function ot(){try{const x=await Ie.listRuns();R(x.items??[])}catch(x){Y(`Runs load error: ${x instanceof Error?x.message:String(x)}`)}}function yt(){localStorage.setItem(wc,JSON.stringify({model:i.model,llmProvider:i.llmProvider,baseUrl:i.baseUrl,temperature:i.temperature,maxOutputTokens:i.maxOutputTokens})),Y("Local config saved (without API key).")}function Nr(){localStorage.setItem(jc,JSON.stringify({uiMode:ke,activeTab:H,showAutorunsAssistantMode:wn,showAutorunsDecompositionMode:Nt,showAutorunsProgressMode:jn,showAutorunsCommentsMode:kn,showAssistantConnectionMode:Et,showAssistantPromptMode:Yt,showAssistantChatMode:dn,showAssistantSamMode:fn,showDecompositionConnectionMode:$t,showDecompositionPromptMode:Ee,showDecompositionQueryMode:Pt,showDecompositionOutputMode:gt,showDecompositionMetricsMode:We,showDecompositionHistoryMode:Rt,showDecompositionRuntimeMode:st,prompts:p})),window.dispatchEvent(new CustomEvent(Yf)),Y("UI layout and prompts saved.")}async function fr(){ne(!0),ce("");try{const x=await Ie.testConnection(i);x.provider==="local"?x.model_found===!0?(Oe(`LOCAL OK - ${x.model}`),Y(`Local model is available: ${x.model} (catalog size=${x.models_count??"n/a"}).`)):x.model_found===!1?(Oe(`LOCAL OK, model not loaded - ${x.model}`),Y(`Local server is reachable, but model '${x.model}' is not in loaded catalog. Use 'Load model list' and select one of loaded models.`)):(Oe(`LOCAL OK (model list unavailable) - ${x.model}`),Y("Local server is reachable, but model catalog could not be verified.")):(Oe(`OPENAI OK - ${x.model}`),Y(`OpenAI connection ok: ${x.model}`))}catch(x){const z=x instanceof Error?x.message:String(x);Oe("Connection error"),ce(`Test connection: ${z}`),Y(`Test connection error: ${z}`)}finally{ne(!1)}}async function Rn(){G(!0);try{const z=(await Ie.listModels(i)).models??[];Se(z),z.length>0&&h(P=>P.model&&z.includes(P.model)?P:{...P,model:z[0]}),Y(`Model catalog loaded (${i.llmProvider}): ${z.length} items.`)}catch(x){const z=x instanceof Error?x.message:String(x);Y(`Load model list error: ${z}`)}finally{G(!1)}}S.useEffect(()=>{Se([])},[i.llmProvider,i.baseUrl]);async function Er(x){ne(!0),ce("");try{const z=await Ie.normalize({connection:i,prompts:p,promptVersion:"normalizer_v2_0_2",query:{userQuestion:E.userQuestion,periodHint:E.periodHint,businessContext:E.businessContext,expectedRoute:E.expectedRoute},saveAsTestCase:x,useMock:xe});Z(z),fe("normalized"),Y(`Normalize done: trace=${z.trace_id}, validation=${z.validation.passed?"passed":"failed"}`),pt()}catch(z){const P=z instanceof Error?z.message:String(z);ce(`Normalize: ${P}`),Y(`Normalize error: ${P}`)}finally{ne(!1)}}function Bn(){const x=$e.find(z=>z.id===Ge);if(!x){Y("Preset is not selected.");return}ie(p),O({systemPrompt:x.systemPrompt,developerPrompt:x.developerPrompt,domainPrompt:x.domainPrompt,schemaNotes:x.schemaNotes??"",fewShotExamples:x.fewShotExamples??""}),Y(`Preset loaded: ${x.name}`)}async function Hn(){try{await Ie.savePreset({name:ft||"NDC preset",prompt_version:"normalizer_v2_0_2",systemPrompt:p.systemPrompt,developerPrompt:p.developerPrompt,domainPrompt:p.domainPrompt,schemaNotes:p.schemaNotes,fewShotExamples:p.fewShotExamples}),Y("Preset saved."),await gn()}catch(x){Y(`Preset save error: ${x instanceof Error?x.message:String(x)}`)}}function Vn(){O(Sc),Y("Prompt panel reset to defaults.")}function pr(){const x=tp(p,rt);Re(x),Y(x)}function u(){const x=E.batchQuestionsRaw.split(";").map(z=>z.trim()).filter(Boolean).join(` + +`);x&&(D(z=>({...z,batchQuestionsRaw:x})),Y("Batch field formatted: `;` converted to blank-line separators."))}async function f(x){try{const P=(await Ie.loadTrace(x)).trace,pe=P.parsed_normalized_json??null;Z({trace_id:String(P.trace_id??x),ok:!!P.validation_result?.passed,normalized:pe,route_hint_summary:P.route_hint_summary??(pe?{route_hint:pe.route_hint??null,confidence:pe.confidence?.route_hint??null}:null),raw_model_output:P.raw_model_response??{},validation:P.validation_result??{passed:!1,errors:["validation not found"]},usage:P.usage??{input_tokens:0,output_tokens:0,total_tokens:0},latency_ms:Number(P.latency_ms??0),prompt_version:String(P.prompt_version??"unknown"),schema_version:String(P.schema_version??"unknown")}),fe("raw"),ce(""),Y(`Trace opened: ${x}`)}catch(z){const P=z instanceof Error?z.message:String(z);ce(`Trace: ${P}`),Y(`Trace open error ${x}: ${P}`)}}async function w(){try{const x=await Ie.startRun();g(x.run.runId),Y(`Run started: ${x.run.runId}`),Y("Tip: start run does not execute normalize by itself. Use 'Run eval v2.0.2' button."),await ot()}catch(x){Y(`Run start error: ${x instanceof Error?x.message:String(x)}`)}}async function F(){if(L)try{await Ie.finishRun(L),Y(`Run finished: ${L}`),await ot()}catch(x){Y(`Run finish error: ${x instanceof Error?x.message:String(x)}`)}}async function ve(){le(!0),ce("");try{Y("Starting eval in v2 contour.");const x=E.batchQuestionsRaw.trim()||E.userQuestion.trim();if(!x)throw new Error("Fill batch field or Raw user question first.");const z=await Ie.runEval({connection:i,prompts:p,promptVersion:"normalizer_v2_0_2",mode:"single-pass-strict",rawQuestions:x,useMock:xe});re(z.report),Y("Eval v2.0.2 run finished.");const P=z.report;if(P.run_id&&Y(`Eval run id: ${P.run_id}`),P.metrics){const pe=P.metrics;Y(`Eval metrics v2.0.2: schema=${pe.schema_validation_pass_rate??"n/a"}%, route_accuracy=${pe.route_resolution_accuracy??"n/a"}%, no_route_precision=${pe.no_route_precision??"n/a"}%, state_consistency=${pe.execution_state_consistency_rate??"n/a"}%`)}await pt()}catch(x){const z=x instanceof Error?x.message:String(x);z.includes("Legacy eval runner supports normalized_query_v1 only")?(re({status:"plan_only",prompt_version:"normalizer_v2",reason:"backend eval runner is still legacy-v1 only",plan_file:"reports/v2_pilot_eval_plan.md",next_steps:["run cheap mock sanity for schema/fragment/scope","run small real batch (10-15 messages, temperature=0)","run challenge-30 replay with v2 metrics"]}),Y("Backend is legacy-only for eval right now. Showing v2 pilot plan.")):(ce(`Eval: ${z}`),Y(`Eval run error: ${z}`))}finally{le(!1)}}async function Bt(){try{const x=JSON.stringify(ae??{},null,2);await navigator.clipboard.writeText(x),Y("Eval report copied to clipboard.")}catch(x){Y(`Eval report copy error: ${x instanceof Error?x.message:String(x)}`)}}function Ht(){Ve(""),En([]),Pn(""),Ut(""),Tt(""),Y("Assistant session reset.")}async function nn(){const x=dr.trim();if(!x)return;ge(!0),Tt(""),Pn(""),En(P=>[...P,{message_id:`local-${Date.now()}`,session_id:Je||"pending",role:"user",text:x,reply_type:null,created_at:new Date().toISOString(),trace_id:null,debug:null}]);const z=tn();try{const P=await Ie.sendAssistantMessage({connection:i,prompts:p,userMessage:x,sessionId:Je||void 0,promptVersion:kc,useMock:xe});Ve(P.session_id),En(P.conversation),Ut("Ответ готов"),Y(`Assistant reply received: trace=${P.debug.trace_id}`)}catch(P){const pe=P instanceof Error?P.message:String(P);Tt(pe),Ut("Ошибка ассистента"),Y(`Assistant error: ${pe}`)}finally{z(),ge(!1)}}return S.useEffect(()=>{if(!L){ee([]);return}Ie.runTrace(L).then(x=>ee(x.items)).catch(x=>Y(`Run trace error: ${x instanceof Error?x.message:String(x)}`))},[L]),o.jsxs("main",{className:`app-root ${ke==="assistant"||ke==="decomposition"||ke==="autoruns"?"app-root-autoruns":""}`,children:[o.jsxs("header",{className:"app-topbar",children:[o.jsxs("div",{className:"mode-switch-row",children:[o.jsx("button",{type:"button",className:ke==="assistant"?"tab active":"tab",onClick:()=>zt("assistant"),children:"Ассистент"}),o.jsx("button",{type:"button",className:ke==="decomposition"?"tab active":"tab",onClick:()=>zt("decomposition"),children:"Декомпозиция"}),o.jsx("button",{type:"button",className:ke==="autoruns"?"tab active":"tab",onClick:()=>zt("autoruns"),children:"История автопрогонов"}),o.jsx("button",{type:"button",className:"tab",onClick:Nr,children:"Сохранить"})]}),ke==="assistant"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:Et?"tab active":"tab",onClick:()=>Dt(x=>!x),children:"LLM Connector"}),o.jsx("button",{type:"button",className:Yt?"tab active":"tab",onClick:()=>Fn(x=>!x),children:"Prompt Manager"}),o.jsx("button",{type:"button",className:dn?"tab active":"tab",onClick:()=>Xt(x=>!x),children:"Режим ассистента"}),o.jsx("button",{type:"button",className:fn?"tab active":"tab",onClick:()=>Nn(x=>!x),children:"SAM"})]}):ke==="decomposition"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:$t?"tab active":"tab",onClick:()=>Un(x=>!x),children:"LLM"}),o.jsx("button",{type:"button",className:Ee?"tab active":"tab",onClick:()=>pn(x=>!x),children:"Prompt"}),o.jsx("button",{type:"button",className:Pt?"tab active":"tab",onClick:()=>cr(x=>!x),children:"Запрос"}),o.jsx("button",{type:"button",className:gt?"tab active":"tab",onClick:()=>de(x=>!x),children:"Выход"}),o.jsx("button",{type:"button",className:We?"tab active":"tab",onClick:()=>mn(x=>!x),children:"Метрики"}),o.jsx("button",{type:"button",className:Rt?"tab active":"tab",onClick:()=>Zt(x=>!x),children:"История"}),o.jsx("button",{type:"button",className:st?"tab active":"tab",onClick:()=>Ft(x=>!x),children:"NDC Run Monitor"})]}):ke==="autoruns"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:wn?"tab active":"tab",onClick:()=>cn(x=>!x),children:"Режим ассистента"}),o.jsx("button",{type:"button",className:Nt?"tab active":"tab",onClick:()=>Jt(x=>!x),children:"Режим декомпозиции"}),o.jsx("button",{type:"button",className:jn?"tab active":"tab",onClick:()=>bt(x=>!x),children:"Прогресс/регресс"}),o.jsx("button",{type:"button",className:kn?"tab active":"tab",onClick:()=>Cn(x=>!x),children:"Комментарии"})]}):null]}),ke==="assistant"?o.jsx("div",{className:"layout-grid layout-grid-mode-columns",children:o.jsxs("div",{className:"mode-columns",children:[Et?o.jsx("div",{className:"mode-col",children:o.jsx(xc,{value:i,modelOptions:se,modelsBusy:X,onChange:h,onReloadModels:Rn,onSaveLocalConfig:yt,onTestConnection:fr,lastStatus:Le,busy:ye||hn})}):null,Yt?o.jsx("div",{className:"mode-col mode-col-wide",children:o.jsx(_c,{value:p,onChange:O,presets:$e,selectedPresetId:Ge,onSelectPreset:Ze,onLoadPreset:Bn,onSavePreset:Hn,onResetDefaults:Vn,onDiffPrevious:pr,presetName:ft,onPresetNameChange:Ct,diffSummary:Be})}):null,dn?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Bf,{sessionId:Je,conversation:je,inputValue:dr,onInputChange:Pn,useMock:xe,onUseMockChange:me,onSend:nn,onClear:Ht,busy:hn,statusText:en,errorMessage:vt})}):null,fn?o.jsx("div",{className:"mode-col",children:o.jsx(Cf,{sessionId:Je,conversation:je,statusText:en,errorMessage:vt,useMock:xe,appLogs:q})}):null,!Et&&!Yt&&!dn&&!fn?o.jsx("div",{className:"mode-columns-empty",children:"Все панели режима ассистента скрыты. Включите нужные блоки справа в шапке."}):null]})}):ke==="decomposition"?o.jsx("div",{className:"layout-grid layout-grid-mode-columns",children:o.jsxs("div",{className:"mode-columns",children:[$t?o.jsx("div",{className:"mode-col",children:o.jsx(xc,{value:i,modelOptions:se,modelsBusy:X,onChange:h,onReloadModels:Rn,onSaveLocalConfig:yt,onTestConnection:fr,lastStatus:Le,busy:ye})}):null,Ee?o.jsx("div",{className:"mode-col mode-col-wide",children:o.jsx(_c,{value:p,onChange:O,presets:$e,selectedPresetId:Ge,onSelectPreset:Ze,onLoadPreset:Bn,onSavePreset:Hn,onResetDefaults:Vn,onDiffPrevious:pr,presetName:ft,onPresetNameChange:Ct,diffSummary:Be})}):null,Pt?o.jsx("div",{className:"mode-col",children:o.jsx(qf,{value:E,onChange:D,onApplyBatchFormat:u,onNormalize:Er,busy:ye,useMock:xe,onUseMockChange:me,errorMessage:he})}):null,gt?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Kf,{tab:H,onTabChange:fe,result:K,appLogs:q})}):null,We?o.jsx("div",{className:"mode-col",children:o.jsx(Vf,{result:K})}):null,Rt?o.jsx("div",{className:"mode-col",children:o.jsx(Hf,{items:B,onRefresh:pt,onOpenTrace:f})}):null,st?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Gf,{runs:A,selectedRunId:L,onSelectRun:g,onStartRun:w,onFinishRun:F,onRefreshRuns:ot,onRunEval:ve,onCopyEvalReport:Bt,evalBusy:oe,traceItems:j,evalReport:ae})}):null,!$t&&!Ee&&!Pt&&!gt&&!We&&!Rt&&!st?o.jsx("div",{className:"mode-columns-empty",children:"Все панели режима декомпозиции скрыты. Включите нужные блоки справа в шапке."}):null]})}):o.jsx("div",{className:"layout-grid layout-grid-autoruns",children:o.jsx(If,{connection:i,prompts:p,assistantPromptVersion:kc,decompositionPromptVersion:Ri,showAssistantMode:wn,showDecompositionMode:Nt,showProgressMode:jn,showCommentsMode:kn,onLog:Y})})]})}wf.createRoot(document.getElementById("root")).render(o.jsx(hf.StrictMode,{children:o.jsx(np,{})})); diff --git a/llm_normalizer/frontend/dist/assets/index-BT0bMOoF.css b/llm_normalizer/frontend/dist/assets/index-qw2gR5-7.css similarity index 55% rename from llm_normalizer/frontend/dist/assets/index-BT0bMOoF.css rename to llm_normalizer/frontend/dist/assets/index-qw2gR5-7.css index c9bde29..6514c42 100644 --- a/llm_normalizer/frontend/dist/assets/index-BT0bMOoF.css +++ b/llm_normalizer/frontend/dist/assets/index-qw2gR5-7.css @@ -1 +1 @@ -@import"https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Space+Grotesk:wght@500;700&display=swap";:root{--rgb-background: 16, 16, 19;--rgb-surface-main: 26, 26, 31;--rgb-surface-horizontal: 32, 32, 38;--rgb-surface-focus: 40, 40, 47;--rgb-active: 228, 142, 92;--rgb-active-text: 18, 18, 18;--rgb-text-main: 240, 240, 240;--rgb-text-muted: 166, 166, 170;--rgb-danger: 255, 126, 126;--rgb-scrollbar-track: 31, 31, 36;--rgb-scrollbar-thumb: 74, 74, 82;--rgb-scrollbar-thumb-hover: 90, 90, 100;--mode-column-width: 440px;--mode-toggle-width: 188px;--bg-main: rgb(var(--rgb-background));--bg-soft: rgb(var(--rgb-surface-main));--bg-panel: rgb(var(--rgb-surface-main));--bg-panel-accent: rgb(var(--rgb-surface-horizontal));--surface-horizontal: rgb(var(--rgb-surface-horizontal));--surface-focus: rgb(var(--rgb-surface-focus));--line: transparent;--line-strong: rgba(var(--rgb-active), .48);--text-main: rgb(var(--rgb-text-main));--text-muted: rgb(var(--rgb-text-muted));--lime-main: rgb(var(--rgb-text-main));--lime-press: rgb(var(--rgb-text-main));--danger: rgb(var(--rgb-danger));--radius-lg: 20px;--radius-md: 14px;--shadow: none;--autoruns-col-width: var(--mode-column-width)}*{box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(var(--rgb-scrollbar-thumb)) rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar{width:10px;height:10px}*::-webkit-scrollbar-track{background:rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar-thumb{background:rgb(var(--rgb-scrollbar-thumb));border-radius:999px;border:2px solid rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar-thumb:hover{background:rgb(var(--rgb-scrollbar-thumb-hover))}html,body,#root{margin:0;min-height:100dvh;font-family:Manrope,Segoe UI,sans-serif;background:var(--bg-main);color:var(--text-main)}.app-root{max-width:1720px;margin:0 auto;padding:12px 16px 16px}.app-root.app-root-autoruns{max-width:none;width:100%;min-height:100dvh;max-height:100dvh;display:flex;flex-direction:column;overflow:hidden}.app-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 12px;padding:0;min-height:38px}.layout-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px}.layout-grid.layout-grid-autoruns,.layout-grid.layout-grid-mode-columns{min-height:0;flex:1 1 auto;grid-template-columns:minmax(0,1fr)}.mode-switch-row{display:flex;gap:8px;margin:0;padding:0}.mode-switch-row.mode-switch-row-right{margin-left:auto;justify-content:flex-end;max-width:72%;overflow-x:auto;overflow-y:hidden;padding-bottom:2px}.mode-switch-row .tab{white-space:nowrap}.mode-switch-row.mode-switch-row-right .tab{width:var(--mode-toggle-width);min-width:var(--mode-toggle-width);text-align:center}.mode-columns{display:flex;gap:12px;width:100%;min-height:0;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;padding-bottom:4px}.mode-col{flex:0 0 var(--mode-column-width);width:var(--mode-column-width);min-height:0;height:100%;display:flex}.mode-col.mode-col-wide,.mode-col.mode-col-xwide{flex-basis:var(--mode-column-width);width:var(--mode-column-width)}.mode-col .panel-frame{width:100%;height:100%}.mode-col .panel-body{min-height:0;overflow:auto}.mode-columns-empty{min-width:360px;border-radius:14px;background:rgb(var(--rgb-surface-main));color:var(--text-muted);padding:14px}.panel-frame{grid-column:span 12;border:none;border-radius:var(--radius-lg);background:var(--bg-panel);overflow:hidden;box-shadow:none;animation:rise .4s ease-out;display:flex;flex-direction:column;min-height:0}.panel-header{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:14px 18px 10px;border-bottom:none;background:var(--bg-panel-accent)}.panel-header h2{margin:0;font-size:1.02rem;letter-spacing:.02em}.panel-header p{margin:6px 0 0;font-size:.85rem;color:var(--text-muted)}.panel-body{padding:10px 12px 12px;min-height:0}.app-root-autoruns .autoruns-frame{height:100%}.app-root-autoruns .autoruns-frame .panel-body{flex:1 1 auto;overflow:hidden;display:flex;flex-direction:column;gap:10px;padding:10px 12px 12px;background:rgb(var(--rgb-background))}.status-chip{border:none;border-radius:999px;padding:4px 10px;color:var(--lime-main);font-size:.78rem;background:rgb(var(--rgb-surface-focus))}.assistant-panel-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.assistant-copy-btn{background:transparent;border-color:transparent;color:var(--text-main);box-shadow:none;transform:none}.assistant-copy-btn:hover{background:rgb(var(--rgb-surface-focus));filter:none;box-shadow:none;transform:none}.assistant-copy-feedback{font-size:.76rem;color:var(--text-muted)}.assistant-copy-feedback.success{color:var(--lime-main)}.assistant-copy-feedback.error{color:var(--danger)}input,select,textarea,button{font-family:Manrope,sans-serif}label{display:flex;flex-direction:column;gap:6px;color:var(--text-muted);font-size:.84rem}input,select,textarea{border:none;border-radius:var(--radius-md);background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:10px 12px;outline:none;transition:background-color .18s ease}input:focus,select:focus,textarea:focus{border-color:transparent;box-shadow:none;outline:none;background:rgb(var(--rgb-surface-focus))}textarea{resize:vertical;min-height:86px}button{border:none;border-radius:999px;background:rgb(var(--rgb-surface-horizontal));color:rgb(var(--rgb-text-main));font-weight:700;font-size:.83rem;letter-spacing:.02em;cursor:pointer;padding:9px 14px;transition:background .2s ease,color .2s ease;outline:none;box-shadow:none}button:hover{border-color:transparent;background:rgb(var(--rgb-surface-focus))}button:disabled{opacity:.52;cursor:not-allowed}.button-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:12px}.checkbox-row{flex-direction:row;align-items:center;gap:8px;color:var(--text-main)}.grid-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.prompt-manager-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.full-width{grid-column:1 / -1}.tab-row{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px}.tab{background:rgb(var(--rgb-surface-main));color:var(--text-main);border:none}.tab.active{border-color:transparent;background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.assistant-chat-list{max-height:420px;overflow:auto;display:grid;gap:10px;padding:4px;border:none;border-radius:var(--radius-md);background:rgb(var(--rgb-surface-horizontal))}.assistant-empty{padding:18px;text-align:center}.assistant-msg{border:none;border-radius:12px;background:rgb(var(--rgb-surface-main));padding:10px}.assistant-msg.user{border-color:transparent;background:rgb(var(--rgb-surface-focus))}.assistant-msg.assistant{border-color:transparent}.assistant-msg-head{display:flex;justify-content:space-between;gap:8px;margin-bottom:6px;font-size:.78rem;color:var(--text-muted)}.assistant-msg-body{white-space:pre-wrap;line-height:1.45;font-size:.9rem}.assistant-trace{margin-top:6px;color:var(--text-muted);font-size:.75rem}.assistant-debug{margin-top:8px}.assistant-debug summary{cursor:pointer;color:var(--lime-main);font-size:.8rem}.assistant-compose{margin-top:12px;display:grid;gap:10px}.json-view{margin:0;width:100%;min-height:180px;max-height:420px;overflow:auto;background:rgb(var(--rgb-surface-horizontal));border:none;border-radius:var(--radius-md);padding:12px;color:rgb(var(--rgb-text-main));font-family:JetBrains Mono,Consolas,monospace;font-size:.78rem;line-height:1.45}.metrics-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px}.metrics-grid div{background:rgba(var(--rgb-surface-main),.8);border:none;border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:4px}.metrics-grid span{color:var(--text-muted);font-size:.75rem}.metrics-grid strong{font-size:.84rem;color:var(--lime-main)}.history-list{display:grid;gap:8px;max-height:340px;overflow:auto}.history-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-main));color:var(--text-main);padding:10px}.history-item p{margin:8px 0;color:var(--text-muted);font-size:.82rem}.history-item.selected{border-color:var(--line-strong)}.history-row{display:flex;justify-content:space-between;gap:8px;font-size:.76rem;color:var(--text-muted)}.runtime-grid{display:grid;grid-template-columns:1.2fr 1fr;gap:12px}.runtime-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.runtime-details{display:grid;gap:12px}.runtime-runs{max-height:360px;overflow:auto;display:grid;gap:8px}.eval-report-wrap{position:relative}.copy-cube-button{position:absolute;right:10px;bottom:10px;width:34px;height:34px;border-radius:10px;padding:0;min-width:34px;display:grid;place-items:center;font-size:.92rem;line-height:1}.muted{color:var(--text-muted)}.diff-summary{margin-top:10px;font-size:.82rem;color:var(--lime-main)}.error-text{margin-top:10px;color:var(--danger);font-size:.84rem}.autoruns-columns{display:flex;gap:12px;width:100%;min-height:0;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;padding-bottom:4px}.autoruns-col{flex:0 0 var(--mode-column-width);width:var(--mode-column-width);height:100%;min-height:0;overflow:auto;border:none;border-radius:14px;background:rgb(var(--rgb-surface-main));padding:12px;scrollbar-gutter:stable}.autoruns-col h3{margin:0;font-size:.95rem}.autoruns-col h4{margin:12px 0 8px;font-size:.82rem;color:var(--text-muted)}.autoruns-col-header{position:sticky;top:-12px;z-index:8;margin:-12px -12px 10px;padding:12px 12px 10px;background:rgb(var(--rgb-surface-main))}.autoruns-col-header .tab-row{margin:8px 0 0}.autoruns-col-header .autoruns-dialog-toolbar{margin-top:8px}.autoruns-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.autoruns-meta-list{display:grid;gap:8px}.autoruns-meta-list>div{display:flex;justify-content:space-between;gap:8px;border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px 9px;font-size:.79rem}.autoruns-meta-list span{color:var(--text-muted)}.autoruns-prompt-details summary{cursor:pointer;color:var(--text-main);font-size:.8rem;margin-bottom:8px}.autoruns-prompt-details textarea{min-height:68px}.autoruns-stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:10px}.autoruns-stats-grid>div{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:3px}.autoruns-stats-grid span{color:var(--text-muted);font-size:.74rem}.autoruns-stats-grid strong{color:var(--lime-main);font-size:.84rem}.autoruns-run-list{display:grid;gap:8px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;padding-right:2px}.autoruns-run-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:10px;display:grid;gap:5px}.autoruns-run-item.selected{border-color:var(--line-strong)}.autoruns-run-head,.autoruns-run-foot{display:flex;justify-content:space-between;gap:8px;font-size:.76rem}.autoruns-run-meta{color:var(--text-muted);font-size:.75rem;word-break:break-word}.autoruns-dialog-toolbar{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.autoruns-case-list{margin-top:8px;display:grid;gap:6px;max-height:180px;overflow:auto}.autoruns-case-item{width:100%;text-align:left;border-radius:10px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:7px 8px;display:flex;justify-content:space-between;gap:6px;font-size:.76rem}.autoruns-case-item.selected{border-color:var(--line-strong)}.autoruns-dialog-view{margin-top:10px;border:none;border-radius:12px;background:rgb(var(--rgb-surface-horizontal));padding:10px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;display:grid;gap:8px}.autoruns-msg{border:none;border-radius:12px;background:rgb(var(--rgb-surface-focus));padding:8px 10px;display:grid;gap:6px}.autoruns-msg header,.autoruns-msg footer{display:flex;justify-content:space-between;gap:8px;font-size:.74rem;color:var(--text-muted)}.autoruns-msg-head-actions{display:flex;align-items:center;gap:8px}.autoruns-msg p{margin:0;white-space:pre-wrap;line-height:1.35;font-size:.84rem}.autoruns-comment-icon{border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);border-radius:999px;min-width:28px;min-height:28px;padding:0 8px;line-height:1;box-shadow:none;transform:none}.autoruns-comment-icon:hover{border-color:var(--line-strong);box-shadow:none;transform:none}.autoruns-comment-icon.commented{border-color:var(--line-strong);color:var(--lime-main);background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.autoruns-msg-annotation{display:grid;gap:4px;border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:7px 8px;font-size:.78rem}.autoruns-comments-list{display:grid;gap:8px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;padding-right:2px}.autoruns-autogen-list{display:grid;gap:8px;max-height:none;min-height:0;overflow:auto;padding-right:2px}.autoruns-autogen-item{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:5px}.autoruns-autogen-item header{display:flex;justify-content:space-between;gap:8px;font-size:.76rem}.autoruns-autogen-item p{margin:0;color:var(--text-muted);white-space:pre-wrap;font-size:.8rem}.autoruns-comment-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:9px;display:grid;gap:6px}.autoruns-comment-item p{margin:0;white-space:pre-wrap;color:var(--text-muted);font-size:.79rem}.autoruns-comment-item.selected{border-color:var(--line-strong)}.autoruns-comment-head{display:flex;justify-content:space-between;gap:8px;font-size:.75rem}.autoruns-msg.assistant{margin-right:12%}.autoruns-msg.user{margin-left:12%;border-color:transparent;background:rgb(var(--rgb-surface-focus))}.autoruns-decomposition-list{margin:0;padding-left:18px;display:grid;gap:7px;font-size:.8rem}.autoruns-comment-modal-backdrop{position:fixed;inset:0;background:rgba(var(--rgb-background),.74);display:grid;place-items:center;z-index:1800;padding:12px}.autoruns-comment-modal{width:min(660px,100%);border:none;border-radius:16px;background:rgb(var(--rgb-surface-horizontal));box-shadow:var(--shadow);padding:14px;display:grid;gap:10px}.autoruns-comment-modal h3{margin:0;font-size:.95rem}.autoruns-comment-quote{margin:0;border:none;border-radius:10px;background:rgb(var(--rgb-surface-focus));padding:8px;white-space:pre-wrap;max-height:150px;overflow:auto;font-size:.82rem}.autoruns-rating-row{display:flex;gap:8px}.autoruns-rating-dot{width:34px;height:34px;border-radius:999px;padding:0;border:none;background:rgb(var(--rgb-surface-focus));color:var(--text-muted);font-size:.95rem;box-shadow:none;transform:none}.autoruns-rating-dot:hover{border-color:var(--line-strong);box-shadow:none;transform:none}.autoruns-rating-dot.active{border-color:var(--line-strong);color:rgb(var(--rgb-active-text));background:rgb(var(--rgb-active))}.autoruns-coverage-list{display:grid;gap:8px}.autoruns-coverage-item{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px}.autoruns-coverage-head{display:flex;justify-content:space-between;gap:8px;font-size:.76rem;margin-bottom:5px}.autoruns-coverage-head span{color:var(--text-muted)}.autoruns-coverage-bar{height:7px;border-radius:999px;background:rgb(var(--rgb-surface-focus));overflow:hidden}.autoruns-coverage-bar>div{height:100%;border-radius:999px;background:rgb(var(--rgb-active))}@media(max-width:1200px){:root{--mode-column-width: 400px}.metrics-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:920px){:root{--mode-column-width: 360px}.grid-two,.runtime-grid,.runtime-stack{grid-template-columns:1fr}.metrics-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.autoruns-form-grid,.autoruns-dialog-toolbar,.autoruns-stats-grid{grid-template-columns:1fr}}@media(max-width:640px){:root{--mode-column-width: 320px}.app-root{padding:18px 12px 24px}.metrics-grid{grid-template-columns:1fr}}@keyframes rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} +@import"https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Space+Grotesk:wght@500;700&display=swap";:root{--rgb-background: 16, 16, 19;--rgb-surface-main: 26, 26, 31;--rgb-surface-horizontal: 32, 32, 38;--rgb-surface-focus: 40, 40, 47;--rgb-active: 228, 142, 92;--rgb-active-text: 18, 18, 18;--rgb-text-main: 240, 240, 240;--rgb-text-muted: 166, 166, 170;--rgb-danger: 255, 126, 126;--rgb-scrollbar-track: 31, 31, 36;--rgb-scrollbar-thumb: 74, 74, 82;--rgb-scrollbar-thumb-hover: 90, 90, 100;--mode-column-width: 440px;--mode-toggle-width: 188px;--bg-main: rgb(var(--rgb-background));--bg-soft: rgb(var(--rgb-surface-main));--bg-panel: rgb(var(--rgb-surface-main));--bg-panel-accent: rgb(var(--rgb-surface-horizontal));--surface-horizontal: rgb(var(--rgb-surface-horizontal));--surface-focus: rgb(var(--rgb-surface-focus));--line: transparent;--line-strong: rgba(var(--rgb-active), .48);--text-main: rgb(var(--rgb-text-main));--text-muted: rgb(var(--rgb-text-muted));--lime-main: rgb(var(--rgb-text-main));--lime-press: rgb(var(--rgb-text-main));--danger: rgb(var(--rgb-danger));--radius-lg: 20px;--radius-md: 14px;--shadow: none;--autoruns-col-width: var(--mode-column-width)}*{box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(var(--rgb-scrollbar-thumb)) rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar{width:10px;height:10px}*::-webkit-scrollbar-track{background:rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar-thumb{background:rgb(var(--rgb-scrollbar-thumb));border-radius:999px;border:2px solid rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar-thumb:hover{background:rgb(var(--rgb-scrollbar-thumb-hover))}html,body,#root{margin:0;min-height:100dvh;font-family:Manrope,Segoe UI,sans-serif;background:var(--bg-main);color:var(--text-main)}.app-root{max-width:1720px;margin:0 auto;padding:12px 16px 16px}.app-root.app-root-autoruns{max-width:none;width:100%;min-height:100dvh;max-height:100dvh;display:flex;flex-direction:column;overflow:hidden}.app-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 12px;padding:0;min-height:38px}.layout-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px}.layout-grid.layout-grid-autoruns,.layout-grid.layout-grid-mode-columns{min-height:0;flex:1 1 auto;grid-template-columns:minmax(0,1fr)}.mode-switch-row{display:flex;gap:8px;margin:0;padding:0}.mode-switch-row.mode-switch-row-right{margin-left:auto;justify-content:flex-end;max-width:72%;overflow-x:auto;overflow-y:hidden;padding-bottom:2px}.mode-switch-row .tab{white-space:nowrap}.mode-switch-row.mode-switch-row-right .tab{width:var(--mode-toggle-width);min-width:var(--mode-toggle-width);text-align:center}.mode-columns{display:flex;gap:12px;width:100%;min-height:0;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;padding-bottom:4px}.mode-col{flex:0 0 var(--mode-column-width);width:var(--mode-column-width);min-height:0;height:100%;display:flex}.mode-col.mode-col-wide,.mode-col.mode-col-xwide{flex-basis:var(--mode-column-width);width:var(--mode-column-width)}.mode-col .panel-frame{width:100%;height:100%}.mode-col .panel-body{min-height:0;overflow:auto}.mode-columns-empty{min-width:360px;border-radius:14px;background:rgb(var(--rgb-surface-main));color:var(--text-muted);padding:14px}.panel-frame{grid-column:span 12;border:none;border-radius:var(--radius-lg);background:var(--bg-panel);overflow:hidden;box-shadow:none;animation:rise .4s ease-out;display:flex;flex-direction:column;min-height:0}.panel-header{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:14px 18px 10px;border-bottom:none;background:var(--bg-panel-accent)}.panel-header h2{margin:0;font-size:1.02rem;letter-spacing:.02em}.panel-header p{margin:6px 0 0;font-size:.85rem;color:var(--text-muted)}.panel-body{padding:10px 12px 12px;min-height:0}.app-root-autoruns .autoruns-frame{height:100%}.app-root-autoruns .autoruns-frame .panel-body{flex:1 1 auto;overflow:hidden;display:flex;flex-direction:column;gap:10px;padding:10px 12px 12px;background:rgb(var(--rgb-background))}.status-chip{border:none;border-radius:999px;padding:4px 10px;color:var(--lime-main);font-size:.78rem;background:rgb(var(--rgb-surface-focus))}.assistant-panel-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.assistant-copy-btn{background:transparent;border-color:transparent;color:var(--text-main);box-shadow:none;transform:none}.assistant-copy-btn:hover{background:rgb(var(--rgb-surface-focus));filter:none;box-shadow:none;transform:none}.assistant-copy-feedback{font-size:.76rem;color:var(--text-muted)}.assistant-copy-feedback.success{color:var(--lime-main)}.assistant-copy-feedback.error{color:var(--danger)}input,select,textarea,button{font-family:Manrope,sans-serif}label{display:flex;flex-direction:column;gap:6px;color:var(--text-muted);font-size:.84rem}input,select,textarea{border:none;border-radius:var(--radius-md);background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:10px 12px;outline:none;transition:background-color .18s ease}input:focus,select:focus,textarea:focus{border-color:transparent;box-shadow:none;outline:none;background:rgb(var(--rgb-surface-focus))}textarea{resize:vertical;min-height:86px}button{border:none;border-radius:999px;background:rgb(var(--rgb-surface-horizontal));color:rgb(var(--rgb-text-main));font-weight:700;font-size:.83rem;letter-spacing:.02em;cursor:pointer;padding:9px 14px;transition:background .2s ease,color .2s ease;outline:none;box-shadow:none}button:hover{border-color:transparent;background:rgb(var(--rgb-surface-focus))}button:disabled{opacity:.52;cursor:not-allowed}.button-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:12px}.checkbox-row{flex-direction:row;align-items:center;gap:8px;color:var(--text-main)}.grid-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.prompt-manager-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.full-width{grid-column:1 / -1}.tab-row{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px}.tab{background:rgb(var(--rgb-surface-main));color:var(--text-main);border:none}.tab.active{border-color:transparent;background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.assistant-chat-list{max-height:420px;overflow:auto;display:grid;gap:10px;padding:4px;border:none;border-radius:var(--radius-md);background:rgb(var(--rgb-surface-horizontal))}.assistant-empty{padding:18px;text-align:center}.assistant-msg{border:none;border-radius:12px;background:rgb(var(--rgb-surface-main));padding:10px}.assistant-msg.user{border-color:transparent;background:rgb(var(--rgb-surface-focus))}.assistant-msg.assistant{border-color:transparent}.assistant-msg-head{display:flex;justify-content:space-between;gap:8px;margin-bottom:6px;font-size:.78rem;color:var(--text-muted)}.assistant-msg-body{white-space:pre-wrap;line-height:1.45;font-size:.9rem}.assistant-trace{margin-top:6px;color:var(--text-muted);font-size:.75rem}.assistant-debug{margin-top:8px}.assistant-debug summary{cursor:pointer;color:var(--lime-main);font-size:.8rem}.assistant-compose{margin-top:12px;display:grid;gap:10px}.json-view{margin:0;width:100%;min-height:180px;max-height:420px;overflow:auto;background:rgb(var(--rgb-surface-horizontal));border:none;border-radius:var(--radius-md);padding:12px;color:rgb(var(--rgb-text-main));font-family:JetBrains Mono,Consolas,monospace;font-size:.78rem;line-height:1.45}.metrics-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px}.metrics-grid div{background:rgba(var(--rgb-surface-main),.8);border:none;border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:4px}.metrics-grid span{color:var(--text-muted);font-size:.75rem}.metrics-grid strong{font-size:.84rem;color:var(--lime-main)}.history-list{display:grid;gap:8px;max-height:340px;overflow:auto}.history-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-main));color:var(--text-main);padding:10px}.history-item p{margin:8px 0;color:var(--text-muted);font-size:.82rem}.history-item.selected{border-color:var(--line-strong)}.history-row{display:flex;justify-content:space-between;gap:8px;font-size:.76rem;color:var(--text-muted)}.runtime-grid{display:grid;grid-template-columns:1.2fr 1fr;gap:12px}.runtime-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.runtime-details{display:grid;gap:12px}.runtime-runs{max-height:360px;overflow:auto;display:grid;gap:8px}.eval-report-wrap{position:relative}.copy-cube-button{position:absolute;right:10px;bottom:10px;width:34px;height:34px;border-radius:10px;padding:0;min-width:34px;display:grid;place-items:center;font-size:.92rem;line-height:1}.muted{color:var(--text-muted)}.diff-summary{margin-top:10px;font-size:.82rem;color:var(--lime-main)}.error-text{margin-top:10px;color:var(--danger);font-size:.84rem}.autoruns-columns{display:flex;gap:12px;width:100%;min-height:0;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;padding-bottom:4px}.autoruns-col{flex:0 0 var(--mode-column-width);width:var(--mode-column-width);height:100%;min-height:0;overflow:auto;border:none;border-radius:14px;background:rgb(var(--rgb-surface-main));padding:12px;scrollbar-gutter:stable}.autoruns-col h3{margin:0;font-size:.95rem}.autoruns-col h4{margin:12px 0 8px;font-size:.82rem;color:var(--text-muted)}.autoruns-col-header{position:sticky;top:-12px;z-index:8;margin:-12px -12px 10px;padding:12px 12px 10px;background:rgb(var(--rgb-surface-main))}.autoruns-col-header .tab-row{margin:8px 0 0}.autoruns-col-header .autoruns-dialog-toolbar{margin-top:8px}.autoruns-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.autoruns-meta-list{display:grid;gap:8px}.autoruns-meta-list>div{display:flex;justify-content:space-between;gap:8px;border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px 9px;font-size:.79rem}.autoruns-meta-list span{color:var(--text-muted)}.autoruns-prompt-details summary{cursor:pointer;color:var(--text-main);font-size:.8rem;margin-bottom:8px}.autoruns-prompt-details textarea{min-height:68px}.autoruns-stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:10px}.autoruns-stats-grid>div{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:3px}.autoruns-stats-grid span{color:var(--text-muted);font-size:.74rem}.autoruns-stats-grid strong{color:var(--lime-main);font-size:.84rem}.autoruns-run-list{display:grid;gap:8px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;padding-right:2px}.autoruns-run-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:10px;display:grid;gap:5px;transition:background-color .2s ease,box-shadow .2s ease}.autoruns-run-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text));box-shadow:none}.autoruns-run-item.selected .autoruns-run-meta{color:rgba(var(--rgb-active-text),.95)}.autoruns-run-head,.autoruns-run-foot{display:flex;justify-content:space-between;gap:8px;font-size:.76rem}.autoruns-run-meta{color:var(--text-muted);font-size:.75rem;word-break:break-word}.autoruns-dialog-toolbar{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.autoruns-case-list{margin-top:8px;display:grid;gap:6px;max-height:180px;overflow:auto}.autoruns-case-item{width:100%;text-align:left;border-radius:10px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:7px 8px;display:flex;justify-content:space-between;gap:6px;font-size:.76rem}.autoruns-case-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text));box-shadow:none}.autoruns-dialog-view{margin-top:10px;border:none;border-radius:12px;background:rgb(var(--rgb-surface-horizontal));padding:10px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;display:grid;gap:8px}.autoruns-msg{border:none;border-radius:12px;background:rgb(var(--rgb-surface-focus));padding:8px 10px;display:grid;gap:6px}.autoruns-msg header,.autoruns-msg footer{display:flex;justify-content:space-between;gap:8px;font-size:.74rem;color:var(--text-muted)}.autoruns-msg-head-actions{display:flex;align-items:center;gap:8px}.autoruns-msg-case-tag{display:inline-flex;align-items:center;border-radius:999px;padding:2px 8px;font-size:.7rem;line-height:1;color:rgb(var(--rgb-active-text));background:rgba(var(--rgb-active),.24)}.autoruns-msg p{margin:0;white-space:pre-wrap;line-height:1.35;font-size:.84rem}.autoruns-comment-icon{border:none;background:transparent;color:rgb(var(--rgb-text-main));border-radius:0;min-width:20px;min-height:20px;width:20px;height:20px;padding:0;line-height:1;box-shadow:none;transform:none;display:inline-flex;align-items:center;justify-content:center}.autoruns-comment-icon:hover{background:transparent;color:rgb(var(--rgb-active));box-shadow:none;transform:none}.autoruns-comment-icon.commented{color:rgb(var(--rgb-active-text));background:transparent;box-shadow:none}.comment-icon-svg{width:20px;height:20px;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;fill:none}.comment-icon-svg .comment-icon-dot{fill:currentColor}.comment-icon-svg.commented{fill:rgb(var(--rgb-active));stroke:rgb(var(--rgb-active))}.autoruns-comment-icon.commented .comment-icon-dot{fill:rgb(var(--rgb-active-text))}.autoruns-resolve-toggle{border:none;background:rgb(var(--rgb-surface-focus));color:rgb(var(--rgb-text-main));border-radius:999px;width:22px;height:22px;min-width:22px;min-height:22px;padding:0;display:inline-flex;align-items:center;justify-content:center;box-shadow:none;transform:none}.autoruns-resolve-toggle:hover{background:rgb(var(--rgb-surface-focus));color:rgb(var(--rgb-active));box-shadow:none;transform:none}.autoruns-resolve-toggle:disabled{opacity:.55;cursor:wait}.autoruns-resolve-toggle.resolved{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.resolve-icon-svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.resolve-icon-svg.resolved{fill:currentColor}.autoruns-msg-annotation{display:grid;gap:4px;border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:7px 8px;font-size:.78rem}.autoruns-comments-list{display:grid;gap:8px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;padding-right:2px;margin-top:6px}.autoruns-autogen-list{display:grid;gap:8px;max-height:none;min-height:0;overflow:auto;padding-right:2px}.autoruns-autogen-item{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:5px;cursor:pointer}.autoruns-autogen-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.autoruns-autogen-item.selected .autoruns-run-meta,.autoruns-autogen-item.selected p{color:rgba(var(--rgb-active-text),.95)}.autoruns-autogen-item header{display:flex;justify-content:space-between;gap:8px;font-size:.76rem}.autoruns-autogen-item p{margin:0;color:var(--text-muted);white-space:pre-wrap;font-size:.8rem}.autoruns-generated-questions{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:8px}.autoruns-generated-questions-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.autoruns-generated-questions-list{display:grid;gap:6px;max-height:220px;overflow:auto;padding-right:2px}.autoruns-generated-question-item{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;border:none;border-radius:9px;background:rgb(var(--rgb-surface-focus));padding:6px 8px;font-size:.78rem}.autoruns-generated-question-item span{white-space:pre-wrap}.autoruns-remove-question-btn{flex:0 0 auto;border:none;border-radius:7px;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);min-width:24px;height:24px;font-size:.75rem;line-height:1}.autoruns-comment-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:9px;display:grid;gap:6px;cursor:pointer}.autoruns-comment-item p{margin:0;white-space:pre-wrap;color:var(--text-muted);font-size:.79rem}.autoruns-comment-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.autoruns-comment-item.selected p,.autoruns-comment-item.selected .autoruns-run-meta,.autoruns-comment-item.selected .muted{color:rgba(var(--rgb-active-text),.94)}.autoruns-comment-item.selected .autoruns-resolve-toggle{background:rgba(var(--rgb-active-text),.18);color:rgb(var(--rgb-active-text))}.autoruns-comment-head{display:flex;justify-content:space-between;align-items:center;gap:8px;font-size:.75rem}.autoruns-comment-head-actions{display:inline-flex;align-items:center;gap:8px}.autoruns-comment-filter-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:10px;margin-bottom:8px}.autoruns-resolved-filter-toggle{min-height:38px;white-space:nowrap}.autoruns-msg.assistant{margin-right:12%}.autoruns-msg.user{margin-left:12%;border-color:transparent;background:rgb(var(--rgb-surface-focus))}.autoruns-decomposition-list{margin:0;padding-left:18px;display:grid;gap:7px;font-size:.8rem}.autoruns-comment-modal-backdrop{position:fixed;inset:0;background:rgba(var(--rgb-background),.74);display:grid;place-items:center;z-index:1800;padding:12px}.autoruns-comment-modal{width:min(660px,100%);border:none;border-radius:16px;background:rgb(var(--rgb-surface-horizontal));box-shadow:var(--shadow);padding:14px;display:grid;gap:10px}.autoruns-comment-modal h3{margin:0;font-size:.95rem}.autoruns-comment-quote{margin:0;border:none;border-radius:10px;background:rgb(var(--rgb-surface-focus));padding:8px;white-space:pre-wrap;max-height:150px;overflow:auto;font-size:.82rem}.autoruns-rating-row{display:flex;gap:8px}.autoruns-rating-dot{width:34px;height:34px;border-radius:999px;padding:0;border:none;background:rgb(var(--rgb-surface-focus));color:var(--text-muted);font-size:.95rem;box-shadow:none;transform:none}.autoruns-rating-dot:hover{border-color:var(--line-strong);box-shadow:none;transform:none}.autoruns-rating-dot.active{border-color:var(--line-strong);color:rgb(var(--rgb-active-text));background:rgb(var(--rgb-active))}.autoruns-coverage-list{display:grid;gap:8px}.autoruns-coverage-item{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px}.autoruns-coverage-head{display:flex;justify-content:space-between;gap:8px;font-size:.76rem;margin-bottom:5px}.autoruns-coverage-head span{color:var(--text-muted)}.autoruns-coverage-bar{height:7px;border-radius:999px;background:rgb(var(--rgb-surface-focus));overflow:hidden}.autoruns-coverage-bar>div{height:100%;border-radius:999px;background:rgb(var(--rgb-active))}@media(max-width:1200px){:root{--mode-column-width: 400px}.metrics-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:920px){:root{--mode-column-width: 360px}.grid-two,.runtime-grid,.runtime-stack{grid-template-columns:1fr}.metrics-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.autoruns-form-grid,.autoruns-dialog-toolbar,.autoruns-stats-grid,.autoruns-comment-filter-row{grid-template-columns:1fr}}@media(max-width:640px){:root{--mode-column-width: 320px}.app-root{padding:18px 12px 24px}.metrics-grid{grid-template-columns:1fr}}@keyframes rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} diff --git a/llm_normalizer/frontend/dist/index.html b/llm_normalizer/frontend/dist/index.html index 39f7156..aebbd33 100644 --- a/llm_normalizer/frontend/dist/index.html +++ b/llm_normalizer/frontend/dist/index.html @@ -4,8 +4,8 @@ NDC AI Normalizer Playground - - + +
diff --git a/llm_normalizer/frontend/src/api/client.ts b/llm_normalizer/frontend/src/api/client.ts index d505042..4223466 100644 --- a/llm_normalizer/frontend/src/api/client.ts +++ b/llm_normalizer/frontend/src/api/client.ts @@ -1,4 +1,7 @@ import type { + AsyncEvalRunStartResponse, + AsyncEvalRunStatusResponse, + AutoGenPersonalityCatalogResponse, AutoGenHistoryResponse, AutoGenMode, AutoRunAnnotationsResponse, @@ -155,6 +158,8 @@ export const apiClient = { mode?: "standard" | "single-pass-strict"; caseSetFile?: string; rawQuestions?: string; + evalTarget?: "normalizer" | "assistant_stage1" | "assistant_stage2" | "assistant_p0"; + compareWithReportFile?: string; }): Promise<{ ok: boolean; report: unknown }> { return request("/eval/run", { method: "POST", @@ -176,11 +181,58 @@ export const apiClient = { useMock: Boolean(input.useMock), mode: input.mode ?? "standard", caseSetFile: input.caseSetFile, - rawQuestions: input.rawQuestions + rawQuestions: input.rawQuestions, + eval_target: input.evalTarget, + compare_with_report_file: input.compareWithReportFile }) }); }, + async startEvalRunAsync(input: { + connection: ConnectionState; + prompts: PromptState; + promptVersion?: string; + caseIds?: string[]; + useMock?: boolean; + mode?: "standard" | "single-pass-strict"; + caseSetFile?: string; + rawQuestions?: string; + evalTarget?: "normalizer" | "assistant_stage1" | "assistant_stage2" | "assistant_p0"; + compareWithReportFile?: string; + questions?: string[]; + }): Promise { + return request("/eval/run-async/start", { + method: "POST", + body: JSON.stringify({ + normalizeConfig: { + llmProvider: input.connection.llmProvider, + apiKey: input.connection.apiKey, + model: input.connection.model, + baseUrl: input.connection.baseUrl, + temperature: input.connection.temperature, + maxOutputTokens: input.connection.maxOutputTokens, + promptVersion: input.promptVersion, + systemPrompt: input.prompts.systemPrompt, + developerPrompt: input.prompts.developerPrompt, + domainPrompt: input.prompts.domainPrompt, + fewShotExamples: input.prompts.fewShotExamples + }, + caseIds: input.caseIds, + useMock: Boolean(input.useMock), + mode: input.mode ?? "standard", + caseSetFile: input.caseSetFile, + rawQuestions: input.rawQuestions, + eval_target: input.evalTarget, + compare_with_report_file: input.compareWithReportFile, + questions: input.questions + }) + }); + }, + + async loadEvalRunAsyncStatus(jobId: string): Promise { + return request(`/eval/run-async/${encodeURIComponent(jobId)}`); + }, + async startRun(): Promise<{ ok: boolean; run: RuntimeRun; runId: string; sessionId: string; status: string }> { return request("/accounting-agent/v1/runs/start", { method: "POST", @@ -321,6 +373,20 @@ export const apiClient = { }); }, + async updateAutoRunAnnotation(input: { + annotation_id: string; + resolved: boolean; + resolved_by?: string; + }): Promise<{ ok: boolean; annotation: AutoRunAnnotationRecord; case_annotation_stats: { count: number; latest_at: string | null; avg_rating: number | null } | null }> { + return request(`/autoruns/annotations/${encodeURIComponent(input.annotation_id)}`, { + method: "PATCH", + body: JSON.stringify({ + resolved: input.resolved, + resolved_by: input.resolved_by + }) + }); + }, + async loadAutoRunPostAnalysis(input?: { run_id?: string; limit_per_queue?: number; @@ -359,12 +425,24 @@ export const apiClient = { return request(`/autoruns/autogen/history${query ? `?${query}` : ""}`); }, + async loadAutoRunAutogenPersonalityCatalog(): Promise { + return request("/autoruns/autogen/personality-catalog"); + }, + async generateAutoRunQuestions(input: { mode: AutoGenMode; count: number; domain?: string; persist_to_eval_cases?: boolean; generated_by?: string; + llm?: { + llm_provider?: "openai" | "local"; + api_key?: string; + model?: string; + base_url?: string; + temperature?: number; + max_output_tokens?: number; + }; context?: { llm_provider?: string; model?: string; diff --git a/llm_normalizer/frontend/src/components/AutoRunsHistoryPanel.tsx b/llm_normalizer/frontend/src/components/AutoRunsHistoryPanel.tsx index 8e8c63b..023c27c 100644 --- a/llm_normalizer/frontend/src/components/AutoRunsHistoryPanel.tsx +++ b/llm_normalizer/frontend/src/components/AutoRunsHistoryPanel.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { apiClient } from "../api/client"; import type { + AsyncEvalRunJob, AutoGenHistoryRecord, AutoGenMode, AutoRunAnnotationListItem, @@ -33,7 +34,7 @@ interface AutoRunsHistoryPanelProps { } type UseMockFilter = "any" | "true" | "false"; -type AutoGenPersonalityId = "general" | "settlements_60_62" | "month_close_costs_20_44" | "vat_document_register_book"; +type AutoGenPersonalityId = string; interface AutoGenPersonalityDefinition { id: AutoGenPersonalityId; @@ -54,6 +55,8 @@ interface AutoRunsFilters { interface CommentModalState { open: boolean; + caseId: string; + caseMessageIndex: number; messageIndex: number; rating: number; comment: string; @@ -67,7 +70,7 @@ interface AutoGenSettingsState { mode: AutoGenMode; count: number; personalityId: AutoGenPersonalityId; - personalityPrompts: Record; + personalityPrompts: Record; persistToEvalCases: boolean; generatedBy: string; } @@ -83,6 +86,8 @@ const DEFAULT_FILTERS: AutoRunsFilters = { }; const DEFAULT_MANUAL_DECISION: ManualCaseDecision = "needs_dialog_policy_fix"; +const ALL_CASES_ID = "__all__"; +const LIVE_RUN_ID_PREFIX = "__live__:"; const AUTORUNS_UI_CONFIG_KEY = "ndc_autoruns_ui_config_v1"; const AUTORUNS_SAVE_EVENT = "ndc-autoruns-save"; @@ -94,50 +99,30 @@ const AUTOGEN_PERSONALITIES: AutoGenPersonalityDefinition[] = [ domain: "", defaultPrompt: "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл." - }, - { - id: "settlements_60_62", - label: "Расчеты 60/62", - domain: "settlements_60_62", - defaultPrompt: - "Генерируй вопросы по расчетам с контрагентами (счета 60/62): закрытие задолженности, авансы, сверки, переносы остатков, цепочки документов." - }, - { - id: "month_close_costs_20_44", - label: "Закрытие месяца 20/44", - domain: "month_close_costs_20_44", - defaultPrompt: - "Генерируй вопросы по закрытию месяца и затратам на счетах 20/44: распределение, закрытие, остатки, аномалии и разницы по периодам." - }, - { - id: "vat_document_register_book", - label: "НДС и регистры", - domain: "vat_document_register_book", - defaultPrompt: - "Генерируй вопросы по НДС: начисление, вычет, книги покупок/продаж, счета-фактуры, прогноз обязательств и сверка цепочки документов." } ]; -function buildDefaultPersonalityPrompts(): Record { - return AUTOGEN_PERSONALITIES.reduce((acc, item) => { +function buildDefaultPersonalityPrompts( + personalities: AutoGenPersonalityDefinition[] = AUTOGEN_PERSONALITIES +): Record { + return personalities.reduce((acc, item) => { acc[item.id] = item.defaultPrompt; return acc; - }, {} as Record); + }, {} as Record); } -const AUTOGEN_PERSONALITY_IDS = new Set(AUTOGEN_PERSONALITIES.map((item) => item.id)); - interface AutoRunsUiConfig { filters?: Partial; autoGenSettings?: { mode?: AutoGenMode; count?: number; - personalityId?: AutoGenPersonalityId; - personalityPrompts?: Partial>; + personalityId?: string; + personalityPrompts?: Record; persistToEvalCases?: boolean; generatedBy?: string; }; annotationDecisionFilter?: ManualCaseDecision | "all"; + hideResolvedAnnotations?: boolean; } const DEFAULT_AUTOGEN_SETTINGS: AutoGenSettingsState = { @@ -237,6 +222,180 @@ function renderCoverageRows(items: AutoRunDomainCoverage[]) { ); } +function toLiveRunId(jobId: string): string { + return `${LIVE_RUN_ID_PREFIX}${jobId}`; +} + +function isLiveRunId(runId: string): boolean { + return runId.startsWith(LIVE_RUN_ID_PREFIX); +} + +function jobIdFromLiveRunId(runId: string): string { + return runId.startsWith(LIVE_RUN_ID_PREFIX) ? runId.slice(LIVE_RUN_ID_PREFIX.length) : ""; +} + +function buildLiveRunSummary(job: AsyncEvalRunJob): AutoRunSummary { + const timestamp = job.report_summary?.run_timestamp ?? job.created_at; + const openCases = Math.max(0, job.total_cases - job.completed_cases); + const liveRunId = toLiveRunId(job.job_id); + return { + run_id: liveRunId, + eval_target: job.eval_target, + run_timestamp: timestamp, + mode: "single-pass-strict", + llm_provider: null, + model: null, + use_mock: null, + prompt_version: null, + schema_version: null, + suite_id: job.case_set_file, + cases_total: job.total_cases, + requests_total: null, + report_path: `async_job:${job.job_id}`, + score_index: job.report_summary?.score_index ?? null, + blocking_failures: 0, + quality_failures: 0, + closed_cases: job.completed_cases, + open_cases: openCases, + domain_coverage: [ + { + domain: "runtime", + total_cases: job.total_cases, + closed_cases: job.completed_cases + } + ] + }; +} + +function buildLiveRunDetail(job: AsyncEvalRunJob, requestedCaseId: string): { + detail: AutoRunDetailResponse; + dialog: AutoRunDialogResponse; + caseId: string; +} { + const summary = buildLiveRunSummary(job); + const cases = job.cases.map((item) => ({ + case_id: item.case_id, + domain: null, + query_class: null, + status: item.status === "completed" ? "closed" : item.status === "failed" ? "open" : "unknown", + score_index: null, + trace_id: null, + reply_type: null, + session_id: `${job.run_id}-${item.case_id}`, + dialog_available: item.messages.length > 0, + commented_count: 0, + latest_annotation_at: null, + avg_rating: null, + checks: null, + metric_subscores: null + })); + + const hasCase = requestedCaseId !== ALL_CASES_ID && cases.some((item) => item.case_id === requestedCaseId); + const caseId = hasCase ? requestedCaseId : cases.length > 0 ? ALL_CASES_ID : ""; + + const detail: AutoRunDetailResponse = { + ok: true, + run: summary, + coverage: { + closed_cases: job.completed_cases, + open_cases: Math.max(0, job.total_cases - job.completed_cases), + domain_coverage: [ + { + domain: "runtime", + total_cases: job.total_cases, + closed_cases: job.completed_cases + } + ] + }, + cases, + annotations_summary: { total: 0 }, + report: job.report_summary + ? { + run_id: job.report_summary.run_id, + run_timestamp: job.report_summary.run_timestamp, + score_index: job.report_summary.score_index, + cases_total: job.report_summary.cases_total + } + : {} + }; + + const messages: AutoRunDialogMessage[] = []; + let globalIndex = 0; + if (caseId === ALL_CASES_ID) { + for (const caseItem of job.cases) { + for (let index = 0; index < caseItem.messages.length; index += 1) { + const message = caseItem.messages[index]; + messages.push({ + ...message, + message_index: globalIndex, + case_id: caseItem.case_id, + case_message_index: index, + commented: false, + annotation: null + }); + globalIndex += 1; + } + } + } else if (caseId) { + const selected = job.cases.find((item) => item.case_id === caseId) ?? null; + for (let index = 0; index < (selected?.messages.length ?? 0); index += 1) { + const message = selected?.messages[index]; + if (!message) continue; + messages.push({ + ...message, + message_index: index, + case_id: caseId, + case_message_index: index, + commented: false, + annotation: null + }); + } + } + + const dialog: AutoRunDialogResponse = { + ok: true, + run_id: summary.run_id, + case_id: caseId, + source: "assistant_session", + session_id: caseId === ALL_CASES_ID ? `${job.run_id}::__all__` : `${job.run_id}-${caseId}`, + messages, + decomposition: [], + assistant_mode: { + status: job.status, + completed_cases: job.completed_cases, + total_cases: job.total_cases + }, + annotations: [] + }; + + return { + detail, + dialog, + caseId + }; +} + +function CommentBubbleIcon({ commented }: { commented: boolean }) { + const className = commented ? "comment-icon-svg commented" : "comment-icon-svg"; + return ( + + ); +} + +function CommentResolvedIcon({ resolved }: { resolved: boolean }) { + return ( + + ); +} + export function AutoRunsHistoryPanel({ connection, prompts, @@ -257,24 +416,35 @@ export function AutoRunsHistoryPanel({ const [dialog, setDialog] = useState(null); const [annotations, setAnnotations] = useState([]); const [annotationDecisionFilter, setAnnotationDecisionFilter] = useState("all"); + const [hideResolvedAnnotations, setHideResolvedAnnotations] = useState(false); const [manualDecisionSchema, setManualDecisionSchema] = useState | null>(null); const [availableManualDecisions, setAvailableManualDecisions] = useState([]); const [selectedAnnotationId, setSelectedAnnotationId] = useState(""); const [selectedRunId, setSelectedRunId] = useState(""); const [selectedCaseId, setSelectedCaseId] = useState(""); + const [autogenPersonalities, setAutogenPersonalities] = useState(AUTOGEN_PERSONALITIES); const [autoGenSettings, setAutoGenSettings] = useState(DEFAULT_AUTOGEN_SETTINGS); const [autoGenHistory, setAutoGenHistory] = useState([]); + const [selectedAutogenGenerationId, setSelectedAutogenGenerationId] = useState(""); + const [editableGeneratedQuestions, setEditableGeneratedQuestions] = useState([]); + const [activeAsyncJob, setActiveAsyncJob] = useState(null); const [postAnalysis, setPostAnalysis] = useState(null); const [autoGenBusy, setAutoGenBusy] = useState(false); + const [autogenRunBusy, setAutogenRunBusy] = useState(false); const [postAnalysisBusy, setPostAnalysisBusy] = useState(false); const [autogenHistoryBusy, setAutogenHistoryBusy] = useState(false); const [historyBusy, setHistoryBusy] = useState(false); const [detailBusy, setDetailBusy] = useState(false); const [dialogBusy, setDialogBusy] = useState(false); const [annotationsBusy, setAnnotationsBusy] = useState(false); + const [annotationResolutionBusyId, setAnnotationResolutionBusyId] = useState(""); const [errorText, setErrorText] = useState(""); + const [limitInput, setLimitInput] = useState(String(DEFAULT_FILTERS.limit)); + const [autogenCountInput, setAutogenCountInput] = useState(String(DEFAULT_AUTOGEN_SETTINGS.count)); const [commentModal, setCommentModal] = useState({ open: false, + caseId: "", + caseMessageIndex: -1, messageIndex: -1, rating: 3, comment: "", @@ -285,30 +455,52 @@ export function AutoRunsHistoryPanel({ }); const initialLoadDoneRef = useRef(false); + const asyncJobPollTimerRef = useRef(null); const selectedPersonality = useMemo( - () => AUTOGEN_PERSONALITIES.find((item) => item.id === autoGenSettings.personalityId) ?? AUTOGEN_PERSONALITIES[0], - [autoGenSettings.personalityId] + () => autogenPersonalities.find((item) => item.id === autoGenSettings.personalityId) ?? autogenPersonalities[0] ?? AUTOGEN_PERSONALITIES[0], + [autoGenSettings.personalityId, autogenPersonalities] + ); + const selectedAutogenGeneration = useMemo( + () => autoGenHistory.find((item) => item.generation_id === selectedAutogenGenerationId) ?? autoGenHistory[0] ?? null, + [autoGenHistory, selectedAutogenGenerationId] ); const activeRunSummary: AutoRunSummary | null = history?.items.find((item) => item.run_id === selectedRunId) ?? runDetail?.run ?? null; const activeCase = runDetail ? getSelectedCase(runDetail.cases, selectedCaseId) : null; - const selectedAnnotation = annotations.find((item) => item.annotation_id === selectedAnnotationId) ?? null; + const visibleAnnotations = useMemo( + () => (hideResolvedAnnotations ? annotations.filter((item) => !item.resolved) : annotations), + [annotations, hideResolvedAnnotations] + ); + const selectedAnnotation = visibleAnnotations.find((item) => item.annotation_id === selectedAnnotationId) ?? null; const modalMessage = dialog?.messages.find((item) => item.message_index === commentModal.messageIndex) ?? null; + const modalQuestion = useMemo(() => { + if (!dialog || commentModal.messageIndex < 0) return null; + for (let index = commentModal.messageIndex - 1; index >= 0; index -= 1) { + const candidate = dialog.messages[index]; + if (candidate?.role === "user") { + return candidate; + } + } + return null; + }, [commentModal.messageIndex, dialog]); const annotationsAverageRating = useMemo(() => { - if (annotations.length === 0) return null; - const avg = annotations.reduce((acc, item) => acc + item.rating, 0) / annotations.length; + if (visibleAnnotations.length === 0) return null; + const avg = visibleAnnotations.reduce((acc, item) => acc + item.rating, 0) / visibleAnnotations.length; return Number(avg.toFixed(2)); - }, [annotations]); + }, [visibleAnnotations]); const runSelectItems = useMemo(() => { const list = [...(history?.items ?? [])]; + if (activeAsyncJob) { + list.unshift(buildLiveRunSummary(activeAsyncJob)); + } if (selectedRunId && !list.some((item) => item.run_id === selectedRunId) && runDetail?.run) { list.unshift(runDetail.run); } return list; - }, [history?.items, runDetail?.run, selectedRunId]); + }, [activeAsyncJob, history?.items, runDetail?.run, selectedRunId]); const log = useCallback( (message: string) => { @@ -317,6 +509,56 @@ export function AutoRunsHistoryPanel({ [onLog] ); + const commitLimitInput = useCallback( + (raw: string) => { + const normalized = raw.trim(); + if (!normalized) { + setLimitInput(String(filters.limit)); + return; + } + if (!/^\d+$/.test(normalized)) { + setLimitInput(String(filters.limit)); + return; + } + const parsed = Number.parseInt(normalized, 10); + if (!Number.isFinite(parsed)) { + setLimitInput(String(filters.limit)); + return; + } + const next = Math.max(1, Math.min(500, parsed)); + if (next !== filters.limit) { + setFilters((prev) => ({ ...prev, limit: next })); + } + setLimitInput(String(next)); + }, + [filters.limit] + ); + + const commitAutogenCountInput = useCallback( + (raw: string) => { + const normalized = raw.trim(); + if (!normalized) { + setAutogenCountInput(String(autoGenSettings.count)); + return; + } + if (!/^\d+$/.test(normalized)) { + setAutogenCountInput(String(autoGenSettings.count)); + return; + } + const parsed = Number.parseInt(normalized, 10); + if (!Number.isFinite(parsed)) { + setAutogenCountInput(String(autoGenSettings.count)); + return; + } + const next = Math.max(1, Math.min(200, parsed)); + if (next !== autoGenSettings.count) { + setAutoGenSettings((prev) => ({ ...prev, count: next })); + } + setAutogenCountInput(String(next)); + }, + [autoGenSettings.count] + ); + const loadAnnotations = useCallback(async () => { setAnnotationsBusy(true); try { @@ -351,11 +593,42 @@ export function AutoRunsHistoryPanel({ } }, [log]); + const loadAutoGenPersonalityCatalog = useCallback(async () => { + try { + const payload = await apiClient.loadAutoRunAutogenPersonalityCatalog(); + const normalized = payload.items + .map((item) => ({ + id: String(item.id ?? "").trim(), + label: String(item.label ?? "").trim(), + domain: typeof item.domain === "string" ? item.domain.trim() : "", + defaultPrompt: String(item.default_prompt ?? "").trim() + })) + .filter((item) => item.id.length > 0 && item.label.length > 0); + + if (normalized.length === 0) { + return; + } + + setAutogenPersonalities( + normalized.map((item) => ({ + id: item.id, + label: item.label, + domain: item.domain || "", + defaultPrompt: + item.defaultPrompt || + "Генерируй реалистичные вопросы бухгалтера по выбранному профилю. Не выдумывай непокрытые возможности." + })) + ); + } catch (error) { + log(`Autogen personality catalog load error: ${error instanceof Error ? error.message : String(error)}`); + } + }, [log]); + const loadPostAnalysis = useCallback(async () => { setPostAnalysisBusy(true); try { const payload = await apiClient.loadAutoRunPostAnalysis({ - run_id: selectedRunId || undefined, + run_id: selectedRunId && !isLiveRunId(selectedRunId) ? selectedRunId : undefined, limit_per_queue: 30, annotation_limit: 1500, from: localInputToIso(filters.fromLocal), @@ -394,6 +667,14 @@ export function AutoRunsHistoryPanel({ domain: selectedPersonality.domain || undefined, persist_to_eval_cases: autoGenSettings.persistToEvalCases, generated_by: autoGenSettings.generatedBy.trim() || undefined, + llm: { + llm_provider: connection.llmProvider, + api_key: connection.apiKey, + model: connection.model, + base_url: connection.baseUrl, + temperature: connection.temperature, + max_output_tokens: connection.maxOutputTokens + }, context: { llm_provider: connection.llmProvider, model: connection.model, @@ -408,6 +689,8 @@ export function AutoRunsHistoryPanel({ `Generated ${payload.generation.count} questions (${payload.generation.mode}) id=${payload.generation.generation_id}` + (payload.generation.saved_case_set_file ? ` saved=${payload.generation.saved_case_set_file}` : "") ); + setSelectedAutogenGenerationId(payload.generation.generation_id); + setEditableGeneratedQuestions([...(payload.generation.questions ?? [])]); await loadAutoGenHistory(); } catch (error) { const message = error instanceof Error ? error.message : String(error); @@ -424,8 +707,12 @@ export function AutoRunsHistoryPanel({ autoGenSettings.personalityId, autoGenSettings.personalityPrompts, autoGenSettings.persistToEvalCases, + connection.apiKey, + connection.baseUrl, connection.llmProvider, + connection.maxOutputTokens, connection.model, + connection.temperature, decompositionPromptVersion, loadAutoGenHistory, log, @@ -440,6 +727,18 @@ export function AutoRunsHistoryPanel({ const loadCaseDialog = useCallback( async (runId: string, caseId: string) => { + if (isLiveRunId(runId)) { + const liveJobId = jobIdFromLiveRunId(runId); + if (activeAsyncJob && activeAsyncJob.job_id === liveJobId) { + const live = buildLiveRunDetail(activeAsyncJob, caseId); + setSelectedRunId(runId); + setSelectedCaseId(live.caseId); + setDialog(live.dialog); + return; + } + setDialog(null); + return; + } setDialogBusy(true); try { const payload = await apiClient.loadAutoRunCaseDialog(runId, caseId); @@ -453,18 +752,37 @@ export function AutoRunsHistoryPanel({ setDialogBusy(false); } }, - [log] + [activeAsyncJob, log] ); const loadRunDetail = useCallback( async (runId: string, preferredCaseId?: string) => { + if (isLiveRunId(runId)) { + const liveJobId = jobIdFromLiveRunId(runId); + if (activeAsyncJob && activeAsyncJob.job_id === liveJobId) { + const live = buildLiveRunDetail(activeAsyncJob, preferredCaseId ?? ALL_CASES_ID); + setSelectedRunId(runId); + setSelectedCaseId(live.caseId); + setRunDetail(live.detail); + setDialog(live.dialog); + return; + } + setSelectedRunId(runId); + setSelectedCaseId(""); + setRunDetail(null); + setDialog(null); + return; + } setDetailBusy(true); try { const payload = await apiClient.loadAutoRunDetail(runId); setRunDetail(payload); const nextCaseId = - (preferredCaseId && payload.cases.some((item) => item.case_id === preferredCaseId) ? preferredCaseId : "") || - payload.cases[0]?.case_id || + (preferredCaseId && + (preferredCaseId === ALL_CASES_ID || payload.cases.some((item) => item.case_id === preferredCaseId)) + ? preferredCaseId + : "") || + (payload.cases.length > 0 ? ALL_CASES_ID : "") || ""; setSelectedRunId(runId); setSelectedCaseId(nextCaseId); @@ -483,7 +801,7 @@ export function AutoRunsHistoryPanel({ setDetailBusy(false); } }, - [loadCaseDialog, log] + [activeAsyncJob, loadCaseDialog, log] ); const loadHistory = useCallback( @@ -540,10 +858,131 @@ export function AutoRunsHistoryPanel({ ] ); + const stopAsyncJobPolling = useCallback(() => { + if (asyncJobPollTimerRef.current !== null) { + window.clearTimeout(asyncJobPollTimerRef.current); + asyncJobPollTimerRef.current = null; + } + }, []); + + const pollAsyncJobStatus = useCallback( + async (jobId: string) => { + try { + const payload = await apiClient.loadEvalRunAsyncStatus(jobId); + setActiveAsyncJob(payload.job); + const liveRunId = toLiveRunId(jobId); + if (selectedRunId === liveRunId) { + const live = buildLiveRunDetail(payload.job, selectedCaseId || ALL_CASES_ID); + setRunDetail(live.detail); + setDialog(live.dialog); + setSelectedCaseId(live.caseId); + } + + if (payload.job.status === "completed") { + stopAsyncJobPolling(); + setAutogenRunBusy(false); + const finalRunId = payload.job.report_summary?.run_id ?? payload.job.run_id; + await loadHistory({ + keepSelection: true, + preferredRunId: finalRunId || selectedRunId, + preferredCaseId: ALL_CASES_ID + }); + await loadAutoGenHistory(); + setActiveAsyncJob(null); + return; + } + + if (payload.job.status === "failed") { + stopAsyncJobPolling(); + setAutogenRunBusy(false); + setErrorText(`Запуск прогонов: ${payload.job.error ?? "неизвестная ошибка"}`); + log(`Autogen async run failed: ${payload.job.error ?? "unknown error"}`); + return; + } + + stopAsyncJobPolling(); + asyncJobPollTimerRef.current = window.setTimeout(() => { + void pollAsyncJobStatus(jobId); + }, 500); + } catch (error) { + stopAsyncJobPolling(); + setAutogenRunBusy(false); + const message = error instanceof Error ? error.message : String(error); + setErrorText(`Запуск прогонов: ${message}`); + log(`Autogen async status error: ${message}`); + } + }, + [loadAutoGenHistory, loadHistory, log, selectedCaseId, selectedRunId, stopAsyncJobPolling] + ); + + const runAutogenCampaign = useCallback(async () => { + stopAsyncJobPolling(); + setAutogenRunBusy(true); + setErrorText(""); + try { + const generation = selectedAutogenGeneration; + if (!generation) { + throw new Error("История автогенерации пуста. Сначала сгенерируйте пачку вопросов."); + } + + const questionsForRun = editableGeneratedQuestions + .map((item) => item.trim()) + .filter((item) => item.length > 0); + if (questionsForRun.length === 0) { + throw new Error("Нет вопросов для запуска: список пустой после ручного редактирования."); + } + + const useMockForRun = filters.useMock === "true"; + const payload = await apiClient.startEvalRunAsync({ + connection, + prompts, + promptVersion: assistantPromptVersion, + mode: "single-pass-strict", + caseSetFile: generation.saved_case_set_file ?? undefined, + useMock: useMockForRun, + evalTarget: "assistant_stage1", + questions: questionsForRun + }); + + const liveJob = payload.job; + setActiveAsyncJob(liveJob); + const liveRunId = toLiveRunId(liveJob.job_id); + const live = buildLiveRunDetail(liveJob, ALL_CASES_ID); + setSelectedRunId(liveRunId); + setSelectedCaseId(live.caseId); + setRunDetail(live.detail); + setDialog(live.dialog); + + log( + `Запущен async-прогон job=${liveJob.job_id}, run_id=${liveJob.run_id}, вопросов=${questionsForRun.length}` + + (generation.saved_case_set_file ? `, base_case_set=${generation.saved_case_set_file}` : "") + ); + void pollAsyncJobStatus(liveJob.job_id); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + setErrorText(`Запуск прогонов: ${message}`); + log(`Autogen run error: ${message}`); + setAutogenRunBusy(false); + } + }, [ + assistantPromptVersion, + connection, + editableGeneratedQuestions, + filters.useMock, + log, + pollAsyncJobStatus, + prompts, + selectedAutogenGeneration, + stopAsyncJobPolling + ]); const openCommentModal = useCallback((message: AutoRunDialogMessage) => { if (message.role !== "assistant") return; + const resolvedCaseId = message.case_id ?? selectedCaseId; + const resolvedCaseMessageIndex = message.case_message_index ?? message.message_index; setCommentModal({ open: true, + caseId: resolvedCaseId, + caseMessageIndex: resolvedCaseMessageIndex, messageIndex: message.message_index, rating: message.annotation?.rating ?? 3, comment: message.annotation?.comment ?? "", @@ -552,13 +991,15 @@ export function AutoRunsHistoryPanel({ saving: false, error: "" }); - }, [autoGenSettings.generatedBy]); + }, [autoGenSettings.generatedBy, selectedCaseId]); - const closeCommentModal = useCallback(() => { + const closeCommentModal = useCallback((options?: { force?: boolean }) => { setCommentModal((prev) => { - if (prev.saving) return prev; + if (prev.saving && !options?.force) return prev; return { open: false, + caseId: "", + caseMessageIndex: -1, messageIndex: -1, rating: 3, comment: "", @@ -571,7 +1012,14 @@ export function AutoRunsHistoryPanel({ }, [autoGenSettings.generatedBy]); const submitCommentModal = useCallback(async () => { - if (!selectedRunId || !selectedCaseId || commentModal.messageIndex < 0) return; + const targetRunId = selectedRunId; + const targetCaseId = commentModal.caseId; + const targetCaseMessageIndex = commentModal.caseMessageIndex; + if (!targetRunId || !targetCaseId || targetCaseMessageIndex < 0) return; + if (isLiveRunId(targetRunId)) { + setCommentModal((prev) => ({ ...prev, error: "Комментарий можно сохранить после завершения прогона." })); + return; + } if (!commentModal.comment.trim()) { setCommentModal((prev) => ({ ...prev, error: "Добавьте комментарий." })); return; @@ -579,17 +1027,21 @@ export function AutoRunsHistoryPanel({ setCommentModal((prev) => ({ ...prev, saving: true, error: "" })); try { await apiClient.saveAutoRunAnnotation({ - run_id: selectedRunId, - case_id: selectedCaseId, - message_index: commentModal.messageIndex, + run_id: targetRunId, + case_id: targetCaseId, + message_index: targetCaseMessageIndex, rating: commentModal.rating, comment: commentModal.comment.trim(), manual_case_decision: commentModal.manualCaseDecision, annotation_author: commentModal.annotationAuthor.trim() || undefined }); - await Promise.all([loadRunDetail(selectedRunId, selectedCaseId), loadAnnotations(), loadPostAnalysis()]); - closeCommentModal(); + closeCommentModal({ force: true }); + void Promise.all([loadRunDetail(targetRunId, selectedCaseId), loadAnnotations(), loadPostAnalysis()]).catch((error) => { + const message = error instanceof Error ? error.message : String(error); + setErrorText(`Обновление после комментария: ${message}`); + log(`Comment refresh error: ${message}`); + }); } catch (error) { setCommentModal((prev) => ({ ...prev, @@ -600,17 +1052,76 @@ export function AutoRunsHistoryPanel({ }, [ closeCommentModal, commentModal.annotationAuthor, + commentModal.caseId, + commentModal.caseMessageIndex, commentModal.comment, commentModal.manualCaseDecision, - commentModal.messageIndex, commentModal.rating, loadAnnotations, loadPostAnalysis, loadRunDetail, + log, selectedCaseId, selectedRunId ]); + const applyLocalAnnotationPatch = useCallback((annotation: AutoRunAnnotationRecord) => { + setAnnotations((prev) => + prev.map((item) => + item.annotation_id === annotation.annotation_id + ? { + ...item, + ...annotation + } + : item + ) + ); + setDialog((prev) => { + if (!prev) return prev; + return { + ...prev, + annotations: prev.annotations.map((item) => (item.annotation_id === annotation.annotation_id ? annotation : item)), + messages: prev.messages.map((item) => { + if (!item.annotation || item.annotation.annotation_id !== annotation.annotation_id) { + return item; + } + return { + ...item, + commented: true, + annotation + }; + }) + }; + }); + }, []); + + const toggleAnnotationResolved = useCallback( + async (annotation: AutoRunAnnotationRecord, nextResolved: boolean) => { + if (!annotation.annotation_id) return; + if (isLiveRunId(annotation.run_id)) { + setErrorText("\u0421\u0442\u0430\u0442\u0443\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u044b\u0445 \u043f\u0440\u043e\u0433\u043e\u043d\u043e\u0432."); + return; + } + setAnnotationResolutionBusyId(annotation.annotation_id); + try { + const payload = await apiClient.updateAutoRunAnnotation({ + annotation_id: annotation.annotation_id, + resolved: nextResolved, + resolved_by: autoGenSettings.generatedBy || undefined + }); + applyLocalAnnotationPatch(payload.annotation); + void loadPostAnalysis(); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + setErrorText(`\u0421\u043c\u0435\u043d\u0430 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u043a\u0435\u0439\u0441\u0430: ${message}`); + log(`Annotation resolve toggle error: ${message}`); + } finally { + setAnnotationResolutionBusyId(""); + } + }, + [applyLocalAnnotationPatch, autoGenSettings.generatedBy, loadPostAnalysis, log] + ); + const openAnnotationContext = useCallback( async (annotation: AutoRunAnnotationRecord) => { setSelectedAnnotationId(annotation.annotation_id); @@ -627,14 +1138,90 @@ export function AutoRunsHistoryPanel({ initialLoadDoneRef.current = true; void loadHistory({ keepSelection: false }); void loadAutoGenHistory(); + void loadAutoGenPersonalityCatalog(); void loadPostAnalysis(); - }, [loadAutoGenHistory, loadHistory, loadPostAnalysis]); + }, [loadAutoGenHistory, loadAutoGenPersonalityCatalog, loadHistory, loadPostAnalysis]); useEffect(() => { if (!initialLoadDoneRef.current) return; void loadAnnotations(); }, [annotationDecisionFilter, loadAnnotations]); + useEffect(() => { + setSelectedAnnotationId((prev) => { + if (visibleAnnotations.length === 0) return ""; + if (visibleAnnotations.some((item) => item.annotation_id === prev)) return prev; + return visibleAnnotations[0].annotation_id; + }); + }, [visibleAnnotations]); + + useEffect(() => { + setSelectedAutogenGenerationId((prev) => { + if (autoGenHistory.length === 0) return ""; + if (prev && autoGenHistory.some((item) => item.generation_id === prev)) return prev; + return autoGenHistory[0].generation_id; + }); + }, [autoGenHistory]); + + useEffect(() => { + if (!selectedAutogenGeneration) { + setEditableGeneratedQuestions([]); + return; + } + setEditableGeneratedQuestions([...selectedAutogenGeneration.questions]); + }, [selectedAutogenGeneration?.generation_id]); + + useEffect(() => { + setLimitInput(String(filters.limit)); + }, [filters.limit]); + + useEffect(() => { + setAutogenCountInput(String(autoGenSettings.count)); + }, [autoGenSettings.count]); + + useEffect(() => { + if (!activeAsyncJob) return; + const liveRunId = toLiveRunId(activeAsyncJob.job_id); + if (selectedRunId !== liveRunId) return; + const live = buildLiveRunDetail(activeAsyncJob, selectedCaseId || ALL_CASES_ID); + setRunDetail(live.detail); + setDialog(live.dialog); + setSelectedCaseId(live.caseId); + }, [activeAsyncJob, selectedCaseId, selectedRunId]); + + useEffect(() => { + return () => { + stopAsyncJobPolling(); + }; + }, [stopAsyncJobPolling]); + + useEffect(() => { + if (autogenPersonalities.length === 0) return; + setAutoGenSettings((prev) => { + let changed = false; + const nextPrompts: Record = { ...prev.personalityPrompts }; + for (const item of autogenPersonalities) { + if (typeof nextPrompts[item.id] !== "string" || nextPrompts[item.id].trim().length === 0) { + nextPrompts[item.id] = item.defaultPrompt; + changed = true; + } + } + + let nextPersonalityId = prev.personalityId; + if (!autogenPersonalities.some((item) => item.id === prev.personalityId)) { + nextPersonalityId = autogenPersonalities[0].id; + changed = true; + } + + if (!changed) return prev; + return { + ...prev, + personalityId: nextPersonalityId, + personalityPrompts: nextPrompts + }; + }); + }, [autogenPersonalities]); + useEffect(() => { const raw = localStorage.getItem(AUTORUNS_UI_CONFIG_KEY); if (!raw) return; @@ -650,18 +1237,18 @@ export function AutoRunsHistoryPanel({ } if (parsed.autoGenSettings) { setAutoGenSettings((prev) => { - const nextPrompts = { + const nextPrompts: Record = { ...prev.personalityPrompts }; - for (const item of AUTOGEN_PERSONALITIES) { - const incoming = parsed.autoGenSettings?.personalityPrompts?.[item.id]; - if (typeof incoming === "string") { - nextPrompts[item.id] = incoming; + const incomingPrompts = parsed.autoGenSettings?.personalityPrompts ?? {}; + for (const [key, value] of Object.entries(incomingPrompts)) { + if (typeof value === "string" && key.trim().length > 0) { + nextPrompts[key.trim()] = value; } } const nextPersonalityId = - parsed.autoGenSettings?.personalityId && AUTOGEN_PERSONALITY_IDS.has(parsed.autoGenSettings.personalityId) - ? parsed.autoGenSettings.personalityId + typeof parsed.autoGenSettings?.personalityId === "string" && parsed.autoGenSettings.personalityId.trim().length > 0 + ? parsed.autoGenSettings.personalityId.trim() : prev.personalityId; return { ...prev, @@ -692,6 +1279,9 @@ export function AutoRunsHistoryPanel({ ) { setAnnotationDecisionFilter(parsed.annotationDecisionFilter as ManualCaseDecision | "all"); } + if (typeof parsed.hideResolvedAnnotations === "boolean") { + setHideResolvedAnnotations(parsed.hideResolvedAnnotations); + } } catch { // ignore broken local cache } @@ -708,10 +1298,11 @@ export function AutoRunsHistoryPanel({ persistToEvalCases: autoGenSettings.persistToEvalCases, generatedBy: autoGenSettings.generatedBy }, - annotationDecisionFilter + annotationDecisionFilter, + hideResolvedAnnotations }; localStorage.setItem(AUTORUNS_UI_CONFIG_KEY, JSON.stringify(payload)); - }, [annotationDecisionFilter, autoGenSettings, filters]); + }, [annotationDecisionFilter, autoGenSettings, filters, hideResolvedAnnotations]); useEffect(() => { const onSave = () => { @@ -793,8 +1384,19 @@ export function AutoRunsHistoryPanel({ type="number" min={1} max={500} - value={filters.limit} - onChange={(event) => setFilters((prev) => ({ ...prev, limit: Number(event.target.value || 120) }))} + value={limitInput} + onChange={(event) => { + const raw = event.target.value; + if (raw === "" || /^\d+$/.test(raw)) { + setLimitInput(raw); + } + }} + onBlur={(event) => commitLimitInput(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + commitLimitInput((event.target as HTMLInputElement).value); + } + }} />