559 lines
77 KiB
HTML
559 lines
77 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>SHD MCP</title>
|
||
<style>
|
||
:root{color-scheme:light dark;--bg:#f8fafc;--surface:#e8edf2;--hover:#e1e8ef;--line:rgba(31,41,55,.16);--ink:#1d1f25;--muted:#616670;--accent:#535963;--soft:rgba(75,85,99,.12);--bad:#b42318;--good:#2f855a;--button:#fff;--shadow:0 10px 26px rgba(31,41,55,.1)}
|
||
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.shell{max-width:980px;margin:auto;padding:22px}.card{padding:20px;border:1px solid var(--line);border-radius:18px;background:var(--surface);box-shadow:var(--shadow)}
|
||
.eyebrow{color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase}h1{margin:4px 0 5px;font-size:25px;line-height:1.15}.lede{margin:0;color:var(--muted)}.summary{display:flex;gap:8px;flex-wrap:wrap;margin:18px 0 14px}.summary span{padding:5px 9px;border:1px solid var(--line);border-radius:999px;background:var(--hover);color:var(--muted)}.summary strong{color:var(--ink)}
|
||
.toolbar{display:flex;gap:8px;margin-bottom:10px}.search{min-width:0;flex:1;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--bg);color:inherit;outline:0}.search:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--soft)}button{cursor:pointer}.refresh{padding:10px 14px;border:1px solid var(--accent);border-radius:10px;background:var(--accent);color:var(--button);font-weight:650}.refresh:disabled{opacity:.6;cursor:wait}.status{min-height:22px;color:var(--muted)}.error{color:var(--bad)}
|
||
.table-wrap{max-height:460px;overflow:auto;border:1px solid var(--line);border-radius:12px;background:var(--bg)}table{width:100%;border-collapse:collapse;min-width:560px}th,td{padding:11px 13px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}th{position:sticky;top:0;background:var(--hover);color:var(--muted);font-size:12px;font-weight:650;z-index:1}tbody tr:last-child td{border-bottom:0}.record{cursor:pointer;outline:0}.record:hover,.record:focus{background:var(--soft)}.title{font-weight:650}.sub{display:block;color:var(--muted);font-size:12px;margin-top:2px}.pill{display:inline-block;padding:3px 8px;border-radius:999px;background:var(--soft);color:var(--accent);font-size:12px;white-space:nowrap}.pill.up,.pill.ok,.pill.active{color:var(--good);background:rgba(47,133,90,.14)}.pill.down,.pill.error,.pill.overdue{color:var(--bad);background:rgba(180,35,24,.12)}.empty{padding:24px;text-align:center;color:var(--muted)}
|
||
.visual[hidden]{display:none}.visual{max-height:460px;overflow:auto;border:1px solid var(--line);border-radius:12px;background:var(--bg);padding:12px}.visual-card{display:block;width:100%;padding:11px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface);color:inherit;text-align:left;cursor:pointer}.visual-card:hover,.visual-card:focus{background:var(--hover);border-color:var(--accent);outline:0}.visual-title{display:block;font-weight:650}.visual-date,.visual-value{display:block;color:var(--muted);font-size:12px;margin-top:4px}.visual-line{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:7px}.kanban{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(190px,1fr);gap:10px;overflow-x:auto;align-items:start}.kanban-col{min-width:190px;padding:10px;border:1px solid var(--line);border-radius:12px;background:var(--surface)}.kanban-head{display:flex;justify-content:space-between;gap:8px;margin-bottom:8px;font-weight:650}.kanban-list{display:grid;gap:8px}.metric-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(145px,1fr));gap:10px;margin-bottom:12px}.metric{padding:12px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}.metric-label{display:block;color:var(--muted);font-size:12px}.metric-value{display:block;font-size:22px;font-weight:700;margin-top:4px}.timeline{display:grid;gap:0}.timeline-item{display:grid;grid-template-columns:18px minmax(0,1fr);gap:10px;position:relative;padding-bottom:10px}.timeline-rail{position:absolute;left:8px;top:16px;bottom:0;width:2px;background:var(--line)}.timeline-item:last-child .timeline-rail{display:none}.timeline-dot{width:16px;height:16px;border-radius:50%;background:var(--accent);border:3px solid var(--soft);z-index:1}.timeline-content{min-width:0}.timeline-meta{color:var(--muted);font-size:12px;margin-bottom:5px}.funnel{display:grid;gap:9px}.funnel-row{display:grid;grid-template-columns:minmax(100px,1fr) minmax(120px,2fr) auto;gap:10px;align-items:center}.funnel-fill{width:100%;height:10px;accent-color:var(--accent)}.progress-card{display:grid;gap:7px;padding:11px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}.progress{width:100%;height:10px;accent-color:var(--accent)}.calendar{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.calendar-day{padding:10px;border:1px solid var(--line);border-radius:12px;background:var(--surface);display:grid;gap:8px;align-content:start}.calendar-head{font-weight:650}.matrix-wrap{overflow:auto}.matrix-table{width:100%;border-collapse:collapse;min-width:560px}.matrix-table th,.matrix-table td{padding:9px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.matrix-table th{color:var(--muted);font-size:12px;background:var(--hover)}.tree{display:grid;gap:7px}.tree-children{display:grid;gap:7px;padding-left:18px;border-left:1px solid var(--line)}.preview-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px}.preview-card{padding:12px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}.preview-fields{display:grid;gap:5px;margin-top:8px}.preview-field{font-size:12px;overflow-wrap:anywhere}.preview-field strong{color:var(--muted);font-weight:500}@media(max-width:640px){.funnel-row{grid-template-columns:1fr auto}.funnel-fill{grid-column:1/-1}}
|
||
.details{margin-top:14px;padding:16px;border:1px solid var(--line);border-radius:12px;background:var(--bg)}.details[hidden]{display:none}.detail-head{display:flex;justify-content:space-between;gap:12px;align-items:start}.details h2{margin:0;font-size:18px}.close{border:0;background:transparent;color:var(--muted);font-size:20px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:14px}.detail{padding:9px;border-radius:8px;background:var(--surface)}.label{display:block;color:var(--muted);font-size:11px;margin-bottom:3px}.value{overflow-wrap:anywhere;white-space:pre-wrap}@media(max-width:640px){.shell{padding:12px}.card{padding:15px}.toolbar{display:grid;grid-template-columns:minmax(0,1fr) auto}}
|
||
@media(prefers-color-scheme:dark){:root{--bg:#202020;--surface:#292929;--hover:#303336;--line:#43474c;--ink:#eaecf0;--muted:#96999d;--accent:#c7b07a;--soft:rgba(199,176,122,.18);--bad:#ff9a93;--good:#83c995;--button:#241f14;--shadow:0 10px 26px rgba(0,0,0,.34)}}
|
||
</style>
|
||
<style data-shd-widget-write-actions="css">
|
||
.shd-widget-actions{margin-top:14px;padding:14px;border:1px solid var(--border,var(--line,rgba(31,41,55,.16)));border-radius:11px;background:var(--surface,var(--panel,#e8edf2));color:var(--text,var(--ink,#1d1f25))}
|
||
.shd-widget-actions[hidden]{display:none}
|
||
.shd-widget-actions-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
|
||
.shd-widget-actions-selection{overflow:hidden;color:var(--muted,#616670);font-size:12px;text-overflow:ellipsis;white-space:nowrap}
|
||
.shd-widget-actions-select,.shd-widget-actions-input{width:100%;border:1px solid var(--border,var(--line,rgba(31,41,55,.16)));border-radius:8px;padding:8px 10px;background:var(--bg,#f8fafc);color:var(--text,var(--ink,#1d1f25))}
|
||
.shd-widget-actions-select:focus,.shd-widget-actions-input:focus{border-color:var(--accent,#535963);outline:0;box-shadow:0 0 0 3px var(--accent-soft,var(--soft,rgba(75,85,99,.12)))}
|
||
.shd-widget-actions-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-top:10px}
|
||
.shd-widget-actions-field{display:grid;gap:4px;color:var(--muted,#616670);font-size:11px}
|
||
.shd-widget-actions-field:has(textarea){grid-column:span 2}
|
||
.shd-widget-actions-field input[type=checkbox]{width:18px;height:18px;accent-color:var(--accent,#535963)}
|
||
.shd-widget-actions-confirm{display:flex;grid-column:1/-1;align-items:center;gap:7px;color:var(--text,var(--ink,#1d1f25));font-size:12px}
|
||
.shd-widget-actions-warning,.shd-widget-actions-hint{grid-column:1/-1;color:var(--muted,#616670);font-size:12px}
|
||
.shd-widget-actions-danger{color:var(--danger,var(--bad,#b42318))}
|
||
.shd-widget-actions-status{min-height:18px;margin-top:9px;color:var(--muted,#616670);font-size:12px}
|
||
.shd-widget-actions-status-error{color:var(--danger,var(--bad,#b42318))}
|
||
.shd-widget-actions-buttons{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}
|
||
.shd-widget-actions-buttons button{border:1px solid var(--border,var(--line,rgba(31,41,55,.16)));border-radius:8px;padding:8px 11px;background:var(--bg,#f8fafc);color:var(--text,var(--ink,#1d1f25));cursor:pointer}
|
||
.shd-widget-actions-buttons button:hover{background:var(--surface-hover,var(--hover,#e1e8ef))}
|
||
.shd-widget-actions-buttons button:disabled{cursor:wait;opacity:.62}
|
||
@media(max-width:600px){.shd-widget-actions-field:has(textarea){grid-column:auto}.shd-widget-actions-header{display:block}.shd-widget-actions-selection{display:block;margin-top:3px}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main class="shell" data-shd-widget-engine="shd-universal-v1" data-widget-module="notes-tree"><section class="card">
|
||
<div class="eyebrow">SHD MCP</div><h1 id="title">SHD</h1><p class="lede" id="desc"></p><div class="summary" id="summary"></div>
|
||
<div class="toolbar"><input class="search" id="search" type="search" autocomplete="off"><button class="refresh" id="refresh" type="button"></button></div>
|
||
<div class="status" id="status" role="status" aria-live="polite"></div>
|
||
<div class="table-wrap"><table><thead><tr><th id="head-title"></th><th id="head-status"></th><th id="head-date"></th><th id="head-value"></th></tr></thead><tbody id="rows"></tbody></table><div class="empty" id="empty" hidden></div></div><div class="visual" id="visual" hidden></div>
|
||
<section class="details" id="details" hidden aria-live="polite"><div class="detail-head"><div><h2 id="detail-title"></h2><div class="sub" id="detail-sub"></div></div><button class="close" id="close" type="button">×</button></div><div class="grid" id="grid"></div></section>
|
||
</section></main>
|
||
<script>
|
||
(function(){
|
||
'use strict';
|
||
var DEFAULT_MODULE='notes-tree';
|
||
var C={ru:{refresh:'Обновить',search:'Поиск по названию, статусу или ID',loading:'Загрузка…',empty:'Нет данных для отображения.',records:'записей',status:'Статус',date:'Дата',value:'Детали',id:'ID',details:'Детали',close:'Закрыть',load:'Не удалось загрузить данные.',detail:'Не удалось загрузить детали.',context:'Для обновления нужен контекст источника',total:'Всего',amount:'Сумма',planned:'План',capacity:'Ёмкость',utilization:'Загрузка',overload:'Перегрузка',warnings:'Предупреждения',complete:'Готово',pending:'Ожидают',approved:'Согласовано',rejected:'Отклонено',progress:'Прогресс',score:'Оценка',member:'Участник',role:'Роль',permissions:'Разрешения',preview:'Предпросмотр',noWarnings:'Предупреждений нет.',health:'Состояние',yes:'Да',no:'Нет'},en:{refresh:'Refresh',search:'Search by name, status or ID',loading:'Loading…',empty:'No data to display.',records:'records',status:'Status',date:'Date',value:'Details',id:'ID',details:'Details',close:'Close',load:'Could not load data.',detail:'Could not load details.',context:'A source context is required to refresh.',total:'Total',amount:'Amount',planned:'Planned',capacity:'Capacity',utilization:'Utilization',overload:'Overload',warnings:'Warnings',complete:'Complete',pending:'Pending',approved:'Approved',rejected:'Rejected',progress:'Progress',score:'Score',member:'Member',role:'Role',permissions:'Permissions',preview:'Preview',noWarnings:'No warnings.',health:'Health',yes:'Yes',no:'No'}};
|
||
var FIELD_LABELS={ru:{id:'ID',key:'Ключ',project_id:'ID проекта',task_project_id:'ID проекта задачи',linked_project_ids:'Связанные проекты',project_code:'Код проекта',base_reference:'Ссылка на базу',base_public_id:'Публичный ID базы',table_public_id:'Публичный ID таблицы',record_public_id:'Публичный ID записи',title:'Название',name:'Название',description:'Описание',status:'Статус',status_id:'ID статуса',is_done:'Завершено',is_complete:'Завершено',section:'Раздел',sort_order:'Порядок сортировки',created_at:'Создано',updated_at:'Обновлено',due_date:'Срок',deadline:'Дедлайн',starts_at:'Начало',ends_at:'Окончание',url:'Ссылка',diagnostic:'Диагностика',message:'Сообщение',type:'Тип',state:'Состояние',phase:'Этап',health:'Состояние',email:'Электронная почта',username:'Имя пользователя',role:'Роль',permissions:'Разрешения',source:'Источник',module:'Модуль',table:'Таблица',fields:'Поля',value:'Значение',stage:'Стадия',approval_status:'Статус согласования',approval_state:'Состояние согласования',priority:'Приоритет',assignee:'Исполнитель',owner:'Владелец',type:'Тип',category:'Категория',permissions:'Разрешения',permission:'Разрешение',read:'Чтение',write:'Запись',manage:'Управление',admin:'Администратор',view:'Просмотр',create:'Создание',update:'Изменение',delete:'Удаление',error:'Ошибка',warning:'Предупреждение',message:'Сообщение',path:'Путь',link:'Ссылка',amount:'Сумма',total:'Итого',quantity:'Количество',available_quantity:'Доступное количество',stock:'Остаток',planned:'План',planned_hours:'Плановые часы',capacity:'Ёмкость',capacity_hours:'Часы ёмкости',utilization:'Загрузка',utilization_percent:'Загрузка, %',overload:'Перегрузка',overload_hours:'Часы перегрузки',score:'Оценка',progress:'Прогресс',completeness_score:'Оценка полноты',completion_percent:'Полнота, %',availability_percent:'Доступность, %',record_count:'Количество записей',revision:'Ревизия',pages:'Страницы',page_count:'Количество страниц',date:'Дата',is_archived:'В архиве',low_stock:'Мало на складе',critical:'Критический',meta:'Метаданные'},en:{id:'ID',key:'Key',project_id:'Project ID',task_project_id:'Task project ID',linked_project_ids:'Linked projects',project_code:'Project code',base_reference:'Base reference',base_public_id:'Public base ID',table_public_id:'Public table ID',record_public_id:'Public record ID',title:'Title',name:'Name',description:'Description',status:'Status',status_id:'Status ID',is_done:'Completed',is_complete:'Completed',section:'Section',sort_order:'Sort order',created_at:'Created',updated_at:'Updated',due_date:'Due date',deadline:'Deadline',starts_at:'Starts',ends_at:'Ends',url:'URL',diagnostic:'Diagnostics',message:'Message',type:'Type',state:'State',phase:'Phase',health:'Health',email:'Email',username:'Username',role:'Role',permissions:'Permissions',source:'Source',module:'Module',table:'Table',fields:'Fields',value:'Value',stage:'Stage',approval_status:'Approval status',approval_state:'Approval state',priority:'Priority',assignee:'Assignee',owner:'Owner',type:'Type',category:'Category',permissions:'Permissions',permission:'Permission',read:'Read',write:'Write',manage:'Manage',admin:'Admin',view:'View',create:'Create',update:'Update',delete:'Delete',error:'Error',warning:'Warning',message:'Message',path:'Path',link:'Link',amount:'Amount',total:'Total',quantity:'Quantity',available_quantity:'Available quantity',stock:'Stock',planned:'Planned',planned_hours:'Planned hours',capacity:'Capacity',capacity_hours:'Capacity hours',utilization:'Utilization',utilization_percent:'Utilization, %',overload:'Overload',overload_hours:'Overload hours',score:'Score',progress:'Progress',completeness_score:'Completeness score',completion_percent:'Completeness, %',availability_percent:'Availability, %',record_count:'Record count',revision:'Revision',pages:'Pages',page_count:'Page count',date:'Date',is_archived:'Archived',low_stock:'Low stock',critical:'Critical',meta:'Metadata'}};
|
||
var VALUE_LABELS={ru:{Tasks_Status_Todo:'К выполнению',Tasks_Status_Done:'Завершено',Tasks_Status_In_Progress:'В работе',Tasks_Status_Review:'На проверке',Modules_Done:'Завершено',todo:'К выполнению',to_do:'К выполнению','to do':'К выполнению',in_progress:'В работе','in progress':'В работе','in-progress':'В работе',in_progress_status:'В работе',done:'Завершено',completed:'Завершено',complete:'Завершено',closed:'Закрыто',active:'Активно',pending:'Ожидает',review:'На проверке',approved:'Согласовано',rejected:'Отклонено',archived:'В архиве',not_set:'Не задано'},en:{Tasks_Status_Todo:'To do',Tasks_Status_Done:'Done',Tasks_Status_In_Progress:'In progress',Tasks_Status_Review:'In review',Modules_Done:'Done',todo:'To do',to_do:'To do','to do':'To do',in_progress:'In progress','in progress':'In progress','in-progress':'In progress',in_progress_status:'In progress',done:'Done',completed:'Completed',complete:'Complete',closed:'Closed',active:'Active',pending:'Pending',review:'In review',approved:'Approved',rejected:'Rejected',archived:'Archived',not_set:'Not set'}};
|
||
var MODULE_CONFIG={"copy":{"discussions":{"ru":["Обсуждения","Темы обсуждений с поиском и просмотром сообщений.","Тема","Обновлено","Сообщение"],"en":["Discussions","Discussion topics with search and message details.","Topic","Updated","Message"]},"notifications":{"ru":["Уведомления","Последние уведомления и события модулей.","Уведомление","Время","Источник"],"en":["Notifications","Recent notifications and module events.","Notification","Time","Source"]},"scheduling":{"ru":["Расписание","Ближайшие события и записи из расписания.","Событие","Начало","Участник"],"en":["Scheduling","Upcoming events and bookings from scheduling.","Event","Starts","Guest"]},"inventory":{"ru":["Склад","Складские позиции, остатки и статусы.","Позиция","Обновлено","Количество"],"en":["Inventory","Stock items, quantities and statuses.","Item","Updated","Quantity"]},"agents":{"ru":["Агенты и контроллеры","Доступные агенты, контроллеры и их текущее состояние.","Агент или контроллер","Проверено","Тип"],"en":["Agents & Controllers","Available agents, controllers and their current state.","Agent or controller","Checked","Type"]},"status-page":{"ru":["Status Page","Состояние доступности сервисов и мониторов.","Монитор","Проверено","Доступность"],"en":["Status Page","Service availability and monitor state.","Monitor","Checked","Availability"]},"airbase":{"ru":["AirBase","Каталог таблиц AirBase с просмотром схемы.","Таблица","Обновлено","Записей"],"en":["AirBase","AirBase table catalog with schema details.","Table","Updated","Records"]},"notes":{"ru":["Заметки","Страницы Notes с поиском и просмотром содержимого.","Страница","Обновлено","Пространство"],"en":["Notes","Notes pages with search and content details.","Page","Updated","Space"]},"project-overview":{"ru":["Обзор проекта","Сводные данные по разделам выбранного проекта.","Раздел","Обновлено","Элементы"],"en":["Project overview","Cross-module records for the selected project.","Section","Updated","Items"]},"files":{"ru":["Файлы","Файлы выбранного проекта с поиском и безопасным просмотром ссылок.","Файл","Обновлено","Размер или тип"],"en":["Files","Files from the selected project with search and safe links.","File","Updated","Size or type"]},"proposals":{"ru":["Предложения","Предложения и сметы с поиском, статусами и деталями.","Предложение","Обновлено","Сумма или номер"],"en":["Proposals","Proposals with search, statuses and details.","Proposal","Updated","Amount or number"]},"terms":{"ru":["Terms / ТЗ","Документы технического задания и их ревизии.","Документ","Обновлено","Страницы или ревизия"],"en":["Terms documents","Terms of Reference documents and revisions.","Document","Updated","Pages or revision"]},"activity":{"ru":["Активность","Авторизованные realtime-события SHD по выбранному scope.","Событие","Время","Модуль или тип"],"en":["Activity","Authenticated SHD realtime events for the selected scope.","Event","Time","Module or type"]},"organizations":{"ru":["Организации и доступ","Организации, роли и безопасный просмотр участников.","Организация","Обновлено","Роль или тип"],"en":["Organizations & access","Organizations, roles and safe membership details.","Organization","Updated","Role or type"]},"gitea":{"ru":["Gitea Issues","Открытые задачи разрешённого site или project репозитория.","Issue","Обновлено","Состояние"],"en":["Gitea Issues","Open issues for an allowed site or project repository.","Issue","Updated","State"]},"schemes":{"ru":["Схемы","Документы PDF Scheme Editor и их текущее состояние.","Схема","Обновлено","Ревизия или страницы"],"en":["Schemes","PDF Scheme Editor documents and their current state.","Scheme","Updated","Revision or pages"]},"task-kanban":{"ru":["Задачи Kanban","Задачи по колонкам статусов с поиском и деталями.","Задача","Срок","Исполнитель"],"en":["Task Kanban","Tasks grouped by workflow status with search and details.","Task","Due","Assignee"]},"team-workload":{"ru":["Загрузка команды","Плановая нагрузка, ёмкость и утилизация команды.","Участник","Период","Часы или процент"],"en":["Team workload","Planned workload, capacity and team utilization.","Member","Period","Hours or percent"]},"project-timeline":{"ru":["Timeline проекта","Задачи проекта на временной ленте.","Задача","Срок","Статус"],"en":["Project timeline","Project tasks on a chronological timeline.","Task","Due","Status"]},"crm-funnel":{"ru":["CRM-воронка","Сделки CRM, сгруппированные по стадиям.","Стадия","Сделки","Сумма"],"en":["CRM funnel","CRM deals grouped by pipeline stage.","Stage","Deals","Amount"]},"finance-dashboard":{"ru":["Финансовый дашборд","Суммы, статусы и документы финансового модуля.","Документ","Обновлено","Сумма"],"en":["Finance dashboard","Finance amounts, statuses and documents.","Document","Updated","Amount"]},"proposal-approvals":{"ru":["Согласования предложений","Очередь предложений и текущие approval-статусы.","Предложение","Обновлено","Approval"],"en":["Proposal approvals","Proposal queue and current approval states.","Proposal","Updated","Approval"]},"documents-completeness":{"ru":["Полнота документов","Score, ревизии и состояние Terms-документов.","Документ","Обновлено","Полнота"],"en":["Documents completeness","Scores, revisions and Terms document state.","Document","Updated","Completeness"]},"agents-health":{"ru":["Здоровье агентов","Состояние агентов и контроллеров.","Агент или контроллер","Проверено","Health"],"en":["Agents health","Agent and controller health state.","Agent or controller","Checked","Health"]},"schedule-calendar":{"ru":["Календарь расписания","Ближайшие записи, сгруппированные по дням.","Событие","День","Участник"],"en":["Schedule calendar","Upcoming bookings grouped by day.","Event","Day","Guest"]},"acl-matrix":{"ru":["ACL-матрица","Участники организации, роли и permissions.","Участник","Роль","Права"],"en":["ACL matrix","Organization members, roles and permissions.","Member","Role","Permissions"]},"inventory-warnings":{"ru":["Предупреждения склада","Низкие остатки и критические складские позиции.","Позиция","Обновлено","Остаток"],"en":["Inventory warnings","Low-stock and critical inventory items.","Item","Updated","Stock"]},"gitea-board":{"ru":["Gitea-доска","Issues по состояниям разрешённого репозитория.","Issue","Состояние","Ссылка"],"en":["Gitea board","Issues grouped by state for an allowed repository.","Issue","State","Link"]},"schemes-progress":{"ru":["Прогресс схем","Документы схем, ревизии, страницы и completeness.","Схема","Обновлено","Прогресс"],"en":["Schemes progress","Scheme documents, revisions, pages and completeness.","Scheme","Updated","Progress"]},"notes-tree":{"ru":["Дерево Notes","Пространства Notes и их иерархия.","Узел","Обновлено","Тип"],"en":["Notes tree","Notes spaces and their hierarchy.","Node","Updated","Type"]},"airbase-preview":{"ru":["AirBase preview","Предпросмотр записей выбранной таблицы AirBase.","Запись","Обновлено","Поля"],"en":["AirBase preview","Preview of records from the selected AirBase table.","Record","Updated","Fields"]},"documents":{"ru":["Документы","Реестр документов с поиском и просмотром деталей.","Документ","Обновлено","Сумма"],"en":["Documents","Document register with search and details.","Document","Updated","Amount"]},"tasks":{"ru":["Задачи","Реестр задач с поиском и просмотром деталей.","Задача","Срок","Приоритет"],"en":["Tasks","Task register with search and details.","Task","Due date","Priority"]},"finance":{"ru":["Финансы","Реестр финансовых документов с поиском и просмотром деталей.","Документ","Срок","Сумма"],"en":["Finance","Finance document register with search and details.","Document","Due date","Amount"]},"crm":{"ru":["CRM","Реестр CRM-сущностей с поиском и просмотром деталей.","Сущность","Обновлено","Стадия"],"en":["CRM","CRM entity register with search and details.","Entity","Updated","Stage"]},"pdf-scheme-editor":{"ru":["PDF Scheme Editor","Документы схем и их текущее состояние.","Документ","Обновлено","Ревизия или страницы"],"en":["PDF Scheme Editor","Scheme documents and their current state.","Document","Updated","Revision or pages"]}},"defaults":{"discussions":{"listTool":"shd_list_discussion_topics","listArgs":{"limit":100},"readTool":"shd_list_discussion_messages","detailKey":"topic_id","detailArgs":{"limit":50},"paths":["data"],"sort":"desc"},"notifications":{"listTool":"shd_list_notifications","listArgs":{"limit":50,"offset":0},"paths":["data"],"sort":"desc"},"scheduling":{"listTool":"shd_get_scheduling_overview","listArgs":{"limit":100},"readTool":"shd_list_scheduling_bookings","detailKey":"booking_id","detailArgs":{"limit":1},"paths":["data.bookings","data.upcoming_bookings","data"],"sort":"asc"},"inventory":{"listTool":"shd_list_inventory_items","listArgs":{"limit":100},"readTool":"shd_get_inventory_item","detailKey":"item_id","paths":["data"],"sort":"desc"},"agents":{"listTool":"shd_agents_overview","listArgs":{},"paths":["data.agents","data.user_agents","data"],"sort":"asc"},"status-page":{"listTool":"shd_get_status_page_monitors","listArgs":{"environment":"dev"},"readTool":"shd_get_status_page_monitor_history","detailKey":"monitor_id","detailArgs":{"limit":20},"paths":["data"],"nested":"monitors","sort":"asc"},"airbase":{"listTool":"shd_airbase_tables","listArgs":{},"readTool":"shd_airbase_schema","detailKey":"table","paths":["data.tables","data"],"contextRequired":true,"sort":"asc"},"notes":{"listTool":"shd_list_note_pages","listArgs":{"limit":100},"readTool":"shd_get_note_page","detailKey":"page_id","paths":["data"],"sort":"desc"},"project-overview":{"listTool":"shd_get_project_context","listArgs":{"include":["summary","tasks","discussions","documents","finance","terms","agents"],"limit_per_section":100},"paths":["data.sections.tasks","data.sections.discussions","data.sections.documents","data.sections.finance","data.sections.terms"],"combinePaths":true,"contextRequired":true,"sort":"desc"},"files":{"listTool":"shd_list_files","readTool":"shd_get_file_link","detailKey":"path","detailArgs":{"download":false},"detailSourceArgs":["project_code"],"paths":["data"],"contextRequired":true,"sort":"desc"},"proposals":{"listTool":"shd_list_proposals","listArgs":{"limit":100},"readTool":"shd_get_proposal","detailKey":"proposal_id","detailSourceArgs":[],"paths":["data"],"sort":"desc"},"terms":{"listTool":"shd_list_terms_documents","readTool":"shd_get_terms_document","detailKey":"document_id","detailSourceArgs":["project_code"],"paths":["data"],"contextRequired":true,"sort":"desc"},"activity":{"listTool":"shd_get_realtime_events","listArgs":{"module":"notifications","limit":100},"paths":["data.events","events","data"],"sort":"desc"},"organizations":{"listTool":"shd_admin_list_organizations","listArgs":{"limit":100},"readTool":"shd_admin_get_organization_members","detailKey":"organization_id","detailSourceArgs":[],"paths":["data"],"sort":"asc"},"gitea":{"listTool":"shd_list_gitea_issues","listArgs":{"target":"site","limit":100},"paths":["data"],"sort":"desc"},"schemes":{"listTool":"shd_list_pdf_scheme_editor_documents","readTool":"shd_get_pdf_scheme_editor_document","detailKey":"document","detailSourceArgs":["project"],"paths":["data"],"contextRequired":true,"sort":"desc"},"task-kanban":{"listTool":"shd_list_tasks","listArgs":{"limit":100},"readTool":"shd_get_task","detailKey":"task_id","detailSourceArgs":[],"paths":["data"],"view":"kanban","groupBy":"status","sort":"asc"},"team-workload":{"listTool":"shd_get_task_workload","listArgs":{"period":"month"},"paths":["data.rows","data.workload","data.assignees","data"],"view":"workload","sort":"desc"},"project-timeline":{"listTool":"shd_list_tasks","listArgs":{"limit":100},"paths":["data"],"view":"timeline","sort":"asc"},"crm-funnel":{"listTool":"shd_list_crm","listArgs":{"section":"deals","limit":100},"readTool":"shd_get_crm_entity","detailKey":"entity_id","detailSourceArgs":["section"],"paths":["data"],"view":"funnel","groupBy":"stage","sort":"desc"},"finance-dashboard":{"listTool":"shd_list_finance_documents","listArgs":{"limit":100},"paths":["data"],"view":"dashboard","sort":"desc"},"proposal-approvals":{"listTool":"shd_list_proposals","listArgs":{"limit":100},"readTool":"shd_get_proposal_approval","detailKey":"proposal_id","detailSourceArgs":[],"paths":["data"],"view":"approval","sort":"desc"},"documents-completeness":{"listTool":"shd_list_pdf_scheme_editor_documents","readTool":"shd_get_pdf_scheme_editor_document_completeness","detailKey":"document_id","detailSourceArgs":["project_code"],"paths":["data"],"view":"completeness","contextRequired":true,"contextKeys":["project_code"],"sort":"desc"},"agents-health":{"listTool":"shd_agents_overview","paths":["data.agents","data.user_agents","data"],"view":"health","sort":"asc"},"schedule-calendar":{"listTool":"shd_get_scheduling_overview","listArgs":{"limit":100},"readTool":"shd_list_scheduling_bookings","detailKey":"booking_id","detailArgs":{"limit":1},"paths":["data.bookings","data.upcoming_bookings","data"],"view":"calendar","sort":"asc"},"acl-matrix":{"listTool":"shd_list_organization_members","listArgs":{"limit":100},"paths":["data"],"view":"matrix","contextRequired":true,"contextKeys":["organization_id"],"contextAll":true,"sort":"asc"},"inventory-warnings":{"listTool":"shd_list_inventory_items","listArgs":{"limit":100},"readTool":"shd_get_inventory_item","detailKey":"item_id","detailSourceArgs":[],"paths":["data"],"view":"warnings","sort":"asc"},"gitea-board":{"listTool":"shd_list_gitea_issues","listArgs":{"target":"site","limit":100},"paths":["data"],"view":"kanban","groupBy":"state","sort":"asc"},"schemes-progress":{"listTool":"shd_list_pdf_scheme_editor_documents","readTool":"shd_get_pdf_scheme_editor_document","detailKey":"document","detailSourceArgs":["project"],"paths":["data"],"view":"progress","contextRequired":true,"contextKeys":["project"],"sort":"desc"},"notes-tree":{"listTool":"shd_list_note_spaces","listArgs":{"limit":100},"readTool":"shd_get_note_space_tree","detailKey":"space_id","detailSourceArgs":[],"paths":["data"],"view":"tree","sort":"asc"},"airbase-preview":{"listTool":"shd_airbase_records","listArgs":{"limit":100,"include_fields":true,"include_view_meta":true},"paths":["data.records","data"],"view":"preview","contextRequired":true,"contextSets":[["base","project_code"],["table"]],"sort":"asc"},"documents":{"listTool":"shd_list_contracts","listArgs":{"archived":false,"limit":100},"readTool":"shd_get_contract","detailKey":"contract_id","paths":["data"],"sort":"desc"},"tasks":{"listTool":"shd_list_tasks","listArgs":{"archived":false,"limit":100},"readTool":"shd_get_task","detailKey":"task_id","paths":["data"],"sort":"asc"},"finance":{"listTool":"shd_list_finance_documents","listArgs":{"archived":false,"limit":100},"readTool":"shd_get_finance_document","detailKey":"document_id","paths":["data"],"sort":"desc"},"crm":{"listTool":"shd_list_crm","listArgs":{"limit":100},"readTool":"shd_get_crm_entity","detailKey":"entity_id","detailSourceArgs":["section"],"paths":["data"],"sort":"desc"},"pdf-scheme-editor":{"listTool":"shd_list_pdf_scheme_editor_documents","readTool":"shd_get_pdf_scheme_editor_document","detailKey":"document","detailSourceArgs":["project"],"paths":["data"],"contextRequired":true,"sort":"desc"}}};
|
||
var M=MODULE_CONFIG.copy;
|
||
var S=MODULE_CONFIG.defaults;
|
||
var pending=new Map(),nextId=1,rows=[],state={module:DEFAULT_MODULE,config:Object.assign({},S[DEFAULT_MODULE]),lang:'ru',busy:false};
|
||
var e={title:document.getElementById('title'),desc:document.getElementById('desc'),search:document.getElementById('search'),refresh:document.getElementById('refresh'),status:document.getElementById('status'),summary:document.getElementById('summary'),tableWrap:document.querySelector('.table-wrap'),visual:document.getElementById('visual'),rows:document.getElementById('rows'),empty:document.getElementById('empty'),details:document.getElementById('details'),detailTitle:document.getElementById('detail-title'),detailSub:document.getElementById('detail-sub'),grid:document.getElementById('grid'),close:document.getElementById('close'),ht:document.getElementById('head-title'),hs:document.getElementById('head-status'),hd:document.getElementById('head-date'),hv:document.getElementById('head-value')};
|
||
function lang(){var x=typeof window!=='undefined'&&window.openai&&window.openai.locale;x=String(x||document.documentElement.lang||'ru').toLowerCase();return x.indexOf('en')===0?'en':'ru'}
|
||
function t(k){return(C[state.lang]||C.ru)[k]||k}
|
||
function obj(x){return x&&typeof x==='object'&&!Array.isArray(x)}
|
||
function merge(a,b){return Object.assign({},a||{},b||{})}
|
||
function payload(x){if(!x)return null;if(Object.prototype.hasOwnProperty.call(x,'structuredContent'))return x.structuredContent;if(Object.prototype.hasOwnProperty.call(x,'toolOutput'))return x.toolOutput;return x}
|
||
function unwrap(x){if(x&&x.isError)throw Error(t('load'));var p=payload(x)||{};if(p.error)throw Error(String(p.error.message||p.error.detail||p.error));if(p.ok===false&&p.message)throw Error(String(p.message));return p}
|
||
function applyConfig(p){p=payload(p)||{};var m=obj(p.meta)?p.meta:{},w=obj(m.widget)?m.widget:{},mod=String(w.module||m.module||state.module||DEFAULT_MODULE);state.module=M[mod]?mod:DEFAULT_MODULE;state.config=merge(S[state.module],w);if(!obj(state.config.sourceArgs)&&obj(m.sourceArgs))state.config.sourceArgs=m.sourceArgs;state.lang=lang();labels()}
|
||
function labels(){var m=(M[state.module]||M.discussions)[state.lang]||M.discussions.ru;document.documentElement.lang=state.lang;e.title.textContent=m[0];e.desc.textContent=m[1];e.search.placeholder=t('search');e.refresh.textContent=t('refresh');e.empty.textContent=t('empty');e.close.setAttribute('aria-label',t('close'));e.ht.textContent=m[2];e.hs.textContent=t('status');e.hd.textContent=m[3];e.hv.textContent=m[4]}
|
||
function req(method,params){var o=typeof window!=='undefined'&&window.openai;if(method==='tools/call'&&o&&typeof o.callTool==='function')return Promise.resolve(o.callTool(params.name,params.arguments||{}));var id=nextId++;window.parent.postMessage({jsonrpc:'2.0',id:id,method:method,params:params||{}},'*');return new Promise(function(resolve,reject){var timeout=window.setTimeout(function(){pending.delete(id);reject(Error(t('load')))},12000);pending.set(id,{resolve:resolve,reject:reject,timeout:timeout})})}
|
||
function notify(method,params){window.parent.postMessage({jsonrpc:'2.0',method:method,params:params||{}},'*')}
|
||
function path(x,p){return String(p||'').split('.').reduce(function(v,k){return v&&typeof v==='object'?v[k]:undefined},x)}
|
||
function findArray(x,d){if(Array.isArray(x))return x;if(!obj(x)||d>2)return null;var keys=['items','records','bookings','upcoming_bookings','topics','notifications','pages','spaces','controllers','agents','user_agents','tables','monitors','data'];for(var i=0;i<keys.length;i++)if(Array.isArray(x[keys[i]]))return x[keys[i]];for(var k in x)if(Object.prototype.hasOwnProperty.call(x,k)){var a=findArray(x[k],d+1);if(a)return a}return null}
|
||
function flatten(a){var n=state.config.nested||state.config.nestedCollection;if(!n)return a;var out=[];a.forEach(function(parent){if(parent&&Array.isArray(parent[n]))parent[n].forEach(function(child){var x=Object.assign({},child);if(parent.name&&!x.group_name)x.group_name=parent.name;out.push(x)});else out.push(parent)});return out}
|
||
function list(x){var p=payload(x)||{};if(Array.isArray(p.items))return p.items;var ps=Array.isArray(state.config.paths)?state.config.paths:(Array.isArray(state.config.arrayPaths)?state.config.arrayPaths:[]),combined=[];for(var i=0;i<ps.length;i++){var c=path(p,ps[i]),a=Array.isArray(c)?c:findArray(c,0);if(a){if(state.config.combinePaths)combined=combined.concat(a);else return flatten(a)}}if(state.config.combinePaths&&combined.length)return flatten(combined);var d=p.data===undefined?p:p.data;return flatten(Array.isArray(d)?d:(findArray(d,0)||[]))}
|
||
function str(x){if(x===null||x===undefined||x==='')return'';if(typeof x==='object'){try{return JSON.stringify(x)}catch(err){return''}}return String(x)}
|
||
function id(x,i){var keys=['id','key','code','uuid','page_id','topic_id','booking_id','item_id','monitor_id','table'];for(var n=0;n<keys.length;n++)if(x&&x[keys[n]]!==undefined&&x[keys[n]]!==null&&x[keys[n]]!=='')return str(x[keys[n]]);return String(i+1)}
|
||
function status(x){var v=x&&x.status;if(v&&typeof v==='object')v=v.name||v.label||v.code||v.id;return str(v||(x&&(x.state||x.phase||x.health))||'')}
|
||
function title(x,i){return str(x&&(x.title||x.name||x.subject||x.display_name||x.label||x.table_name||x.table||x.key))||(t('records')+' '+(i+1))}
|
||
function date(x){return str(x&&(x.starts_at||x.start_at||x.due_date||x.checked_at||x.updated_at||x.created_at||x.sent_at||x.published_at||x.date))}
|
||
function value(x){if(!x)return'';if(x.availability_percent!==undefined&&x.availability_percent!==null)return str(x.availability_percent)+'%';if(x.quantity!==undefined&&x.quantity!==null)return str(x.quantity);if(x.record_count!==undefined&&x.record_count!==null)return str(x.record_count);return str(x.value!==undefined?x.value:(x.guest_name||x.space_name||x.group_name||x.controller_type||x.type||x.source||x.module||''))}
|
||
function sub(x){return str(x&&(x.subtitle||x.project_code||x.space_name||x.channel_name||x.group_name||x.code||x.email))}
|
||
function normalize(x,i){var y=obj(x)?Object.assign({},x):{value:x};y.id=id(x,i);y.title=title(x,i);y.subtitle=sub(x);y.status=status(x);y.date=date(x);y.value=value(x);return y}
|
||
function dateText(x){if(!x)return'—';var n=Date.parse(x);if(Number.isNaN(n))return String(x);try{return new Intl.DateTimeFormat(state.lang,{dateStyle:'medium',timeStyle:'short'}).format(new Date(n))}catch(err){return String(x)}}
|
||
function make(tag,v,cls){var n=document.createElement(tag);if(cls)n.className=cls;if(v!==undefined)n.textContent=v;return n}
|
||
function fieldLabel(key){var raw=String(key||'');var labels=FIELD_LABELS[state.lang]||FIELD_LABELS.ru;return labels[raw]||raw.replace(/[_-]+/g,' ')}
|
||
function detailText(x,key){if(x===null||x===undefined||x==='')return'—';if(typeof x==='boolean')return x?t('yes'):t('no');if(Array.isArray(x))return'['+x.map(function(v){return detailText(v,key)}).join(', ')+']';if(obj(x))return'{'+Object.keys(x).map(function(k){return fieldLabel(k)+': '+detailText(x[k],k)}).join(', ')+'}';var raw=String(x),labels=VALUE_LABELS[state.lang]||VALUE_LABELS.ru;return labels[raw]||labels[raw.toLowerCase()]||raw}
|
||
function renderSummary(){e.summary.replaceChildren();var n=make('span');n.append(make('strong',String(rows.length)),' '+t('records'));e.summary.append(n);var c={};rows.forEach(function(x){if(x.status)c[x.status]=(c[x.status]||0)+1});Object.keys(c).slice(0,3).forEach(function(k){var n=make('span');n.append(make('strong',String(c[k])),' '+detailText(k,'status'));e.summary.append(n)})}
|
||
function field(x,keys){for(var i=0;i<keys.length;i++){var v=x&&x[keys[i]];if(v!==undefined&&v!==null&&v!=='')return v}return''}
|
||
function numeric(x,keys){var v=field(x,keys);if(v===null||v===undefined||v==='')return null;if(typeof v==='number')return Number.isFinite(v)?v:null;var n=Number(String(v).replace(/,/g,'.').replace(/[^0-9.-]/g,''));return Number.isFinite(n)?n:null}
|
||
function groupValue(x,key){var v=key?field(x,[key]):status(x);if(obj(v))v=v.name||v.label||v.code||v.id;return str(v)||'—'}
|
||
function visualCard(x,extra){var b=make('button',undefined,'visual-card'+(extra?' '+extra:''));b.type='button';b.append(make('span',x.title||'SHD','visual-title'));if(x.subtitle)b.append(make('span',x.subtitle,'sub'));var line=make('span',undefined,'visual-line');line.append(make('span',detailText(x.status,'status'),'pill'));if(x.date)line.append(make('span',dateText(x.date),'visual-date'));b.append(line);var v=field(x,['total_amount','amount','planned_hours','utilization_percent','score','quantity','revision','pages','value']);if(v!==''&&v!==null)b.append(make('span',detailText(v),'visual-value'));b.onclick=function(){details(x)};return b}
|
||
function visualEmpty(){e.visual.replaceChildren(make('div',t('empty'),'empty'))}
|
||
function renderKanban(a){if(!a.length){visualEmpty();return}var groups={};a.forEach(function(x){var g=groupValue(x,state.config.groupBy||'status');(groups[g]||(groups[g]=[])).push(x)});var board=make('div',undefined,'kanban');Object.keys(groups).forEach(function(g){var col=make('section',undefined,'kanban-col');var head=make('div',undefined,'kanban-head');head.append(make('span',detailText(g,state.config.groupBy||'status')),make('span',String(groups[g].length),'pill'));col.append(head);var list=make('div',undefined,'kanban-list');groups[g].forEach(function(x){list.append(visualCard(x))});col.append(list);board.append(col)});e.visual.append(board)}
|
||
function renderTimeline(a){if(!a.length){visualEmpty();return}var wrap=make('div',undefined,'timeline');a.slice().sort(function(x,y){return String(x.date||'').localeCompare(String(y.date||''))}).forEach(function(x){var item=make('article',undefined,'timeline-item'),rail=make('span',undefined,'timeline-rail'),dot=make('span',undefined,'timeline-dot'),content=make('div',undefined,'timeline-content');content.append(make('div',dateText(x.date),'timeline-meta'),visualCard(x,'timeline-card'));item.append(rail,dot,content);wrap.append(item)});e.visual.append(wrap)}
|
||
function renderFunnel(a){if(!a.length){visualEmpty();return}var groups={},amounts={};a.forEach(function(x){var g=groupValue(x,state.config.groupBy||'stage');groups[g]=(groups[g]||0)+1;amounts[g]=(amounts[g]||0)+(numeric(x,['total_amount','amount','value','sum'])||0)});var max=Math.max.apply(null,Object.values(groups));var wrap=make('div',undefined,'funnel');Object.keys(groups).forEach(function(g){var row=make('div',undefined,'funnel-row'),bar=document.createElement('progress');bar.className='funnel-fill';bar.max=max||1;bar.value=groups[g];row.append(make('span',detailText(g,state.config.groupBy||'stage')),bar,make('span',String(groups[g])+(amounts[g]?' · '+String(amounts[g]):'')));wrap.append(row)});e.visual.append(wrap)}
|
||
function metric(label,value){var d=make('div',undefined,'metric');d.append(make('span',label,'metric-label'),make('span',value===null||value===undefined?'—':str(value),'metric-value'));return d}
|
||
function renderDashboard(a,mode){var grid=make('div',undefined,'metric-grid'),total=a.length,amount=0,planned=0,capacity=0,util=0,overload=0;a.forEach(function(x){amount+=numeric(x,['total_amount','amount','total','sum','value'])||0;planned+=numeric(x,['planned_hours','planned','hours'])||0;capacity+=numeric(x,['capacity_hours','capacity'])||0;util+=numeric(x,['utilization_percent','utilization'])||0;overload+=numeric(x,['overload_hours','overload'])||0});grid.append(metric(t('total'),total));if(mode==='workload'){grid.append(metric(t('planned'),planned||null),metric(t('capacity'),capacity||null),metric(t('utilization'),a.length?Math.round(util/a.length)+'%':null),metric(t('overload'),overload||0))}else{grid.append(metric(t('amount'),amount||null),metric(t('status'),Object.keys(a.reduce(function(m,x){m[groupValue(x,'status')]=1;return m},{})).length))}e.visual.append(grid);if(!a.length){visualEmpty();return}var list=make('div',undefined,'kanban-list');a.slice(0,60).forEach(function(x){list.append(visualCard(x))});e.visual.append(list)}
|
||
function approvalValue(x){return groupValue(x,'approval_status')!=='—'?groupValue(x,'approval_status'):groupValue(x,'approval_state')!=='—'?groupValue(x,'approval_state'):groupValue(x,'status')}
|
||
function renderStatusSummary(a,keys){if(!a.length){visualEmpty();return}var counts={};a.forEach(function(x){var g=groupValue(x,keys[0]);if(g==='—')g=groupValue(x,keys[1]);if(g==='—')g=groupValue(x,'status');counts[g]=(counts[g]||0)+1});var grid=make('div',undefined,'metric-grid');Object.keys(counts).slice(0,6).forEach(function(g){grid.append(metric(detailText(g,keys[0]),counts[g]))});e.visual.append(grid);var list=make('div',undefined,'kanban-list');a.slice(0,60).forEach(function(x){list.append(visualCard(x))});e.visual.append(list)}
|
||
function progressValue(x){var n=numeric(x,['score','completeness_score','completion_percent','progress','availability_percent']);if(n!==null)return Math.max(0,Math.min(100,n));if(x.complete===true||x.is_complete===true||String(status(x)).toLowerCase()==='complete')return 100;return 0}
|
||
function renderProgress(a){if(!a.length){visualEmpty();return}var grid=make('div',undefined,'preview-grid');a.forEach(function(x){var card=make('article',undefined,'progress-card');card.append(make('strong',x.title||'SHD','visual-title'));var meta=make('span',x.subtitle||dateText(x.date),'visual-date');card.append(meta);var p=document.createElement('progress');p.className='progress';p.max=100;p.value=progressValue(x);card.append(p,make('span',t('progress')+': '+String(p.value)+'%','visual-value'));card.onclick=function(){details(x)};grid.append(card)});e.visual.append(grid)}
|
||
function isWarning(x){var s=String(status(x)).toLowerCase();if(x.low_stock||x.is_low_stock||x.critical||x.warning)return true;if(/low|critical|out|error|overdue|maintenance/.test(s))return true;var q=numeric(x,['quantity','available_quantity','stock']);var min=numeric(x,['min_quantity','minimum_quantity','reorder_level']);return q!==null&&min!==null&&q<=min}
|
||
function renderWarnings(a){var warnings=a.filter(isWarning),grid=make('div',undefined,'metric-grid');grid.append(metric(t('warnings'),warnings.length),metric(t('total'),a.length));e.visual.append(grid);if(!warnings.length){e.visual.append(make('div',t('noWarnings'),'empty'));return}var list=make('div',undefined,'kanban-list');warnings.forEach(function(x){list.append(visualCard(x,'warning'))});e.visual.append(list)}
|
||
function renderCalendar(a){if(!a.length){visualEmpty();return}var days={};a.forEach(function(x){var d=x.date?String(x.date).slice(0,10):'—';(days[d]||(days[d]=[])).push(x)});var cal=make('div',undefined,'calendar');Object.keys(days).forEach(function(d){var day=make('section',undefined,'calendar-day');day.append(make('div',d,'calendar-head'));days[d].forEach(function(x){day.append(visualCard(x))});cal.append(day)});e.visual.append(cal)}
|
||
function memberName(x){var v=field(x,['member_name','username','email','name','display_name','user']);if(obj(v))return str(v.name||v.username||v.email||v.id);return str(v)||x.id}
|
||
function permissionText(x){var p=x.permissions||x.permission||x.scopes||'';if(obj(p))return Object.keys(p).filter(function(k){return p[k]===true||p[k]===1||typeof p[k]==='string'}).map(function(k){return fieldLabel(k)+(typeof p[k]==='string'?'='+detailText(p[k],k):'')}).join(', ')||'—';if(Array.isArray(p))return p.map(function(k){return detailText(k,'permissions')}).join(', ')||'—';return detailText(p,'permissions')||'—'}
|
||
function renderMatrix(a){if(!a.length){visualEmpty();return}var wrap=make('div',undefined,'matrix-wrap'),table=make('table',undefined,'matrix-table'),head=make('tr');[t('member'),t('role'),t('status'),t('permissions')].forEach(function(x){head.append(make('th',x))});table.append(make('thead').appendChild(head).parentNode);var body=make('tbody');a.forEach(function(x){var row=make('tr');row.append(make('td',memberName(x)),make('td',detailText(field(x,['role','organization_role','type']),'role')||'—'),make('td',detailText(status(x),'status')||'—'),make('td',permissionText(x)));row.onclick=function(){details(x)};row.className='record';body.append(row)});table.append(body);wrap.append(table);e.visual.append(wrap)}
|
||
function treeId(x,i){return str(field(x,['id','space_id','page_id','collection_id','key']))||String(i)}
|
||
function treeParent(x){return str(field(x,['parent_id','parentId','parent_space_id','parent_collection_id']))}
|
||
function renderTree(a){if(!a.length){visualEmpty();return}var by={};a.forEach(function(x,i){var p=treeParent(x);(by[p]||(by[p]=[])).push({x:x,i:i})});var tree=make('div',undefined,'tree');function branch(parent,host,seen){(by[parent]||[]).forEach(function(entry){var id=treeId(entry.x,entry.i);if(seen[id])return;var wrap=make('div',undefined,'tree-node');wrap.append(visualCard(entry.x));host.append(wrap);var child=make('div',undefined,'tree-children');branch(id,child,Object.assign({},seen,(function(){var o={};o[id]=true;return o})()));if(child.childNodes.length)wrap.append(child)})}branch('',tree,{});if(!tree.childNodes.length)a.forEach(function(x){tree.append(visualCard(x))});e.visual.append(tree)}
|
||
function renderPreview(a){if(!a.length){visualEmpty();return}var grid=make('div',undefined,'preview-grid');a.forEach(function(x){var card=make('article',undefined,'preview-card');card.append(make('strong',x.title||'SHD','visual-title'));var fields=make('div',undefined,'preview-fields');Object.keys(x).filter(function(k){return['id','title','subtitle','status','date','value','meta'].indexOf(k)===-1}).slice(0,10).forEach(function(k){var cell=make('div',undefined,'preview-field');cell.append(make('strong',fieldLabel(k)+': '),make('span',detailText(x[k],k)));fields.append(cell)});card.append(fields);card.onclick=function(){details(x)};grid.append(card)});e.visual.append(grid)}
|
||
function renderVisual(a){e.visual.replaceChildren();var v=state.config.view||'table';if(v==='kanban')renderKanban(a);else if(v==='workload'||v==='dashboard')renderDashboard(a,v);else if(v==='timeline')renderTimeline(a);else if(v==='funnel')renderFunnel(a);else if(v==='approval')renderStatusSummary(a,['approval_status','approval_state']);else if(v==='completeness'||v==='progress')renderProgress(a);else if(v==='health')renderStatusSummary(a,['health','status']);else if(v==='calendar')renderCalendar(a);else if(v==='matrix')renderMatrix(a);else if(v==='warnings')renderWarnings(a);else if(v==='tree')renderTree(a);else if(v==='preview')renderPreview(a);else renderDashboard(a,'dashboard')}
|
||
function renderTable(a){e.rows.replaceChildren();e.empty.hidden=a.length!==0;a.forEach(function(x){var r=document.createElement('tr');r.className='record';r.tabIndex=0;r.setAttribute('role','button');r.setAttribute('aria-label',x.title);var n=make('td');n.append(make('span',x.title,'title'));if(x.subtitle)n.append(make('span',x.subtitle,'sub'));var s=make('span',detailText(x.status,'status'),'pill');if(x.status)s.classList.add(String(x.status).toLowerCase().replace(/[^a-z0-9_-]+/g,'-'));var sc=make('td');sc.append(s);r.append(n,sc,make('td',dateText(x.date)),make('td',detailText(x.value,'value')));r.onclick=function(){details(x)};r.onkeydown=function(ev){if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();details(x)}};e.rows.append(r)});renderSummary()}
|
||
function renderRows(){var q=e.search.value.trim().toLowerCase(),a=rows.filter(function(x){return!q||[x.title,x.subtitle,x.status,x.id,x.value].map(str).join(' ').toLowerCase().indexOf(q)!==-1});if(state.config.view&&state.config.view!=='table'){e.tableWrap.hidden=true;e.visual.hidden=false;e.empty.hidden=true;renderVisual(a);renderSummary();return}e.tableWrap.hidden=false;e.visual.hidden=true;renderTable(a)}
|
||
function render(x){var p=unwrap(x);applyConfig(p);rows=list(p).map(normalize);var dir=state.config.sort==='asc'?1:-1;rows.sort(function(a,b){var x=Date.parse(a.date),y=Date.parse(b.date);if(!Number.isNaN(x)&&!Number.isNaN(y)&&x!==y)return(x-y)*dir;return a.title.localeCompare(b.title,state.lang)*dir});renderRows()}
|
||
function setStatus(x,bad){e.status.textContent=x||'';e.status.className=bad?'status error':'status'}
|
||
function add(k,v,key){var d=make('div',undefined,'detail');d.append(make('span',k,'label'),make('span',detailText(v,key),'value'));e.grid.append(d)}
|
||
function show(x){e.detailTitle.textContent=x.title||'SHD';e.detailSub.textContent=x.subtitle||x.id||'';e.grid.replaceChildren();add(t('id'),x.id,'id');if(x.status)add(t('status'),x.status,'status');if(x.date)add(t('date'),dateText(x.date),'date');if(x.value)add(t('value'),x.value,'value');Object.keys(x).filter(function(k){return['id','title','subtitle','status','date','value','meta'].indexOf(k)===-1}).slice(0,8).forEach(function(k){add(fieldLabel(k),x[k],k)});e.details.hidden=false}
|
||
function details(x){show(x);if(!state.config.readTool)return;var k=state.config.detailKey;if(!k)return;var v=x[k]===undefined?x.id:x[k];if(v===undefined||v===null||v==='')return;if(state.module==='status-page'&&!/^\d+$/.test(String(v)))return;var a={};if(Array.isArray(state.config.detailSourceArgs)){var ctx=merge(state.config.listArgs,state.config.sourceArgs);state.config.detailSourceArgs.forEach(function(name){if(ctx[name]!==undefined)a[name]=ctx[name]})}else a=merge(state.config.sourceArgs,state.config.detailArgs);a=merge(a,state.config.detailArgs);a[k]=k==='table'?String(v):(Number(v)||v);req('tools/call',{name:state.config.readTool,arguments:a}).then(function(result){var p=unwrap(result),d=p.data===undefined?p:p.data;e.grid.replaceChildren();if(Array.isArray(d)){add(t('records'),d.length);d.slice(0,6).forEach(function(q,i){add(String(i+1),q&&(q.message||q.body||q.content||q.title||q))})}else if(obj(d))Object.keys(d).slice(0,12).forEach(function(q){add(fieldLabel(q),d[q],q)});else add(t('details'),d)}).catch(function(err){setStatus(err.message||t('detail'),true)})}
|
||
function contextReady(a){if(!state.config.contextRequired)return true;if(Array.isArray(state.config.contextSets)&&state.config.contextSets.length)return state.config.contextSets.every(function(set){return set.some(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''})});var keys=Array.isArray(state.config.contextKeys)&&state.config.contextKeys.length?state.config.contextKeys:['base','project_code','project'];var ready=state.config.contextAll?keys.every(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''}):keys.some(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''});return ready}
|
||
function refresh(){if(state.busy)return;var a=merge(state.config.listArgs,state.config.sourceArgs);if(!contextReady(a)){setStatus(t('context'),true);return}if(!state.config.listTool)return;state.busy=true;e.refresh.disabled=true;setStatus(t('loading'),false);req('tools/call',{name:state.config.listTool,arguments:a}).then(function(x){render(x);setStatus('',false)}).catch(function(err){setStatus(err.message||t('load'),true)}).finally(function(){state.busy=false;e.refresh.disabled=false})}
|
||
function init(){return req('ui/initialize',{protocolVersion:'2025-11-25',appInfo:{name:'SHD '+(M[state.module]||M.discussions).ru[0],title:'SHD MCP',version:'1.0.0',websiteUrl:'https://shd.xyz.su'},appCapabilities:{}}).then(function(){notify('ui/notifications/initialized',{})}).catch(function(){})}
|
||
window.addEventListener('message',function(ev){if(ev.source!==window.parent)return;var m=ev.data;if(!m||m.jsonrpc!=='2.0')return;if(m.id!==undefined&&pending.has(m.id)){var q=pending.get(m.id);pending.delete(m.id);window.clearTimeout(q.timeout);if(m.error)q.reject(Error(m.error.message||t('load')));else q.resolve(m.result);return}if(m.method==='ui/notifications/tool-input'&&!rows.length){try{render(m.params||{})}catch(err){setStatus(err.message||t('load'),true)}}if(m.method==='ui/notifications/tool-result'){try{render(m.params&&m.params.structuredContent)}catch(err){setStatus(err.message||t('load'),true)}}},{passive:true});
|
||
e.search.oninput=renderRows;e.refresh.onclick=refresh;e.close.onclick=function(){e.details.hidden=true};state.lang=lang();labels();void init();var out=typeof window!=='undefined'&&window.openai?window.openai.toolOutput:null;if(out){try{render(out)}catch(err){setStatus(err.message||t('load'),true)}}else{var input=typeof window!=='undefined'&&window.openai?window.openai.toolInput:null;if(input){try{render(input)}catch(err){setStatus(err.message||t('load'),true)}}else refresh()}
|
||
}());
|
||
</script>
|
||
<script data-shd-widget-write-actions="js">
|
||
(function () {
|
||
'use strict';
|
||
|
||
if (window.__SHD_WIDGET_WRITE_ACTIONS__) return;
|
||
window.__SHD_WIDGET_WRITE_ACTIONS__ = true;
|
||
|
||
var copy = {
|
||
ru: {
|
||
actions: 'Действия',
|
||
chooseAction: 'Выберите действие',
|
||
selectRecord: 'Сначала выберите запись.',
|
||
submit: 'Сохранить',
|
||
cancel: 'Отмена',
|
||
confirm: 'Подтверждаю действие',
|
||
warning: 'Проверьте данные перед записью.',
|
||
destructive: 'Операция может изменить или удалить данные.',
|
||
required: 'Заполните обязательное поле.',
|
||
invalidJson: 'Введите корректный JSON.',
|
||
success: 'Изменения сохранены.',
|
||
failure: 'Не удалось сохранить изменения.',
|
||
loading: 'Сохраняю…',
|
||
noActions: 'Для этого виджета нет доступных действий.',
|
||
refreshed: 'Список обновлён.',
|
||
fieldValue: 'Значение',
|
||
},
|
||
en: {
|
||
actions: 'Actions',
|
||
chooseAction: 'Choose an action',
|
||
selectRecord: 'Select a record first.',
|
||
submit: 'Save',
|
||
cancel: 'Cancel',
|
||
confirm: 'I confirm this action',
|
||
warning: 'Review the values before writing.',
|
||
destructive: 'This operation may change or remove data.',
|
||
required: 'Fill in the required field.',
|
||
invalidJson: 'Enter valid JSON.',
|
||
success: 'Changes saved.',
|
||
failure: 'Could not save changes.',
|
||
loading: 'Saving…',
|
||
noActions: 'No actions are available for this widget.',
|
||
refreshed: 'List refreshed.',
|
||
fieldValue: 'Value',
|
||
},
|
||
};
|
||
|
||
var state = {
|
||
widget: {},
|
||
records: [],
|
||
selected: null,
|
||
action: null,
|
||
inputs: {},
|
||
busy: false,
|
||
lang: String(document.documentElement.lang || 'ru').toLowerCase().indexOf('ru') === 0 ? 'ru' : 'en',
|
||
};
|
||
var pending = new Map();
|
||
var nextRequestId = 1;
|
||
var elements = {};
|
||
|
||
function t(key) {
|
||
return (copy[state.lang] && copy[state.lang][key]) || copy.en[key] || key;
|
||
}
|
||
|
||
function object(value) {
|
||
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
||
}
|
||
|
||
function normalize(value) {
|
||
if (!value) return null;
|
||
if (value.structuredContent) return normalize(value.structuredContent);
|
||
if (value.toolOutput) return normalize(value.toolOutput);
|
||
if (value.toolInput) return normalize(value.toolInput);
|
||
if (value.arguments && !value.data && !value.items && !value.projects) return normalize(value.arguments);
|
||
return value;
|
||
}
|
||
|
||
function widgetFrom(value) {
|
||
var payload = normalize(value) || {};
|
||
var meta = object(payload.meta) ? payload.meta : {};
|
||
return object(meta.widget) ? meta.widget : {};
|
||
}
|
||
|
||
function recordsFrom(value) {
|
||
var payload = normalize(value) || {};
|
||
var data = Array.isArray(payload.items) ? payload.items : payload.projects;
|
||
if (!Array.isArray(data)) data = payload.data;
|
||
if (data && !Array.isArray(data) && Array.isArray(data.data)) data = data.data;
|
||
return Array.isArray(data) ? data.filter(function (item) { return object(item); }) : [];
|
||
}
|
||
|
||
function readPath(value, path) {
|
||
if (!value || path === undefined || path === null) return undefined;
|
||
var parts = String(path).split('.');
|
||
var current = value;
|
||
for (var index = 0; index < parts.length; index += 1) {
|
||
if (current === undefined || current === null) return undefined;
|
||
current = current[parts[index]];
|
||
}
|
||
return current;
|
||
}
|
||
|
||
function firstValue(paths) {
|
||
var list = Array.isArray(paths) ? paths : [paths];
|
||
var sources = [state.selected, state.widget.sourceArgs, state.widget.listArgs];
|
||
for (var sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) {
|
||
var sourceObject = sources[sourceIndex];
|
||
if (!sourceObject) continue;
|
||
for (var pathIndex = 0; pathIndex < list.length; pathIndex += 1) {
|
||
var value = readPath(sourceObject, list[pathIndex]);
|
||
if (value !== undefined && value !== null && value !== '') return value;
|
||
}
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
function text(value) {
|
||
if (value === undefined || value === null) return '';
|
||
if (typeof value === 'object') return JSON.stringify(value);
|
||
return String(value);
|
||
}
|
||
|
||
function label(value) {
|
||
if (object(value)) return value[state.lang] || value.ru || value.en || t('fieldValue');
|
||
return String(value || t('fieldValue'));
|
||
}
|
||
|
||
function actionLabel(action) {
|
||
return label(action && action.label) || action.id || action.tool || t('actions');
|
||
}
|
||
|
||
function hasTargets(action) {
|
||
return Boolean(action && ((Array.isArray(action.targets) && action.targets.length) || action.target));
|
||
}
|
||
|
||
function actionList() {
|
||
return Array.isArray(state.widget.actions) ? state.widget.actions.filter(function (action) {
|
||
return action && action.tool;
|
||
}) : [];
|
||
}
|
||
|
||
function request(method, params) {
|
||
var openai = typeof window !== 'undefined' ? window.openai : undefined;
|
||
if (method === 'tools/call' && openai && typeof openai.callTool === 'function') {
|
||
return openai.callTool(params.name, params.arguments || {});
|
||
}
|
||
var id = nextRequestId++;
|
||
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params || {} }, '*');
|
||
return new Promise(function (resolve, reject) {
|
||
var timeout = window.setTimeout(function () {
|
||
pending.delete(id);
|
||
reject(new Error(t('failure')));
|
||
}, 15000);
|
||
pending.set(id, { resolve: resolve, reject: reject, timeout: timeout });
|
||
});
|
||
}
|
||
|
||
function unwrap(value) {
|
||
var payload = normalize(value);
|
||
if (value && value.isError) throw new Error(payload && payload.error ? text(payload.error) : t('failure'));
|
||
if (payload && payload.error) throw new Error(text(payload.error));
|
||
return payload || value;
|
||
}
|
||
|
||
function make(tag, value, className) {
|
||
var node = document.createElement(tag);
|
||
if (className) node.className = className;
|
||
if (value !== undefined) node.textContent = value;
|
||
return node;
|
||
}
|
||
|
||
function ensurePanel() {
|
||
if (elements.panel) return;
|
||
var root = document.querySelector('main') || document.body;
|
||
var panel = make('section', undefined, 'shd-widget-actions');
|
||
panel.hidden = true;
|
||
panel.setAttribute('aria-live', 'polite');
|
||
var header = make('div', undefined, 'shd-widget-actions-header');
|
||
header.append(make('strong', t('actions')), make('span', '', 'shd-widget-actions-selection'));
|
||
var select = document.createElement('select');
|
||
select.className = 'shd-widget-actions-select';
|
||
select.setAttribute('aria-label', t('actions'));
|
||
var form = make('form', undefined, 'shd-widget-actions-form');
|
||
var status = make('div', '', 'shd-widget-actions-status');
|
||
var buttons = make('div', undefined, 'shd-widget-actions-buttons');
|
||
var submit = make('button', t('submit'), 'shd-widget-actions-submit');
|
||
submit.type = 'submit';
|
||
var cancel = make('button', t('cancel'), 'shd-widget-actions-cancel');
|
||
cancel.type = 'button';
|
||
buttons.append(submit, cancel);
|
||
panel.append(header, select, form, status, buttons);
|
||
root.append(panel);
|
||
elements = { panel: panel, selection: header.lastChild, select: select, form: form, status: status, submit: submit, cancel: cancel };
|
||
select.addEventListener('change', function () {
|
||
var selected = actionList().find(function (action) { return action.id === select.value; });
|
||
state.action = selected || null;
|
||
renderForm();
|
||
});
|
||
cancel.addEventListener('click', function () {
|
||
state.action = null;
|
||
select.value = '';
|
||
renderForm();
|
||
});
|
||
form.addEventListener('submit', function (event) {
|
||
event.preventDefault();
|
||
submitAction();
|
||
});
|
||
}
|
||
|
||
function inputValue(fieldConfig) {
|
||
var value = firstValue(fieldConfig.source || []);
|
||
if (value === undefined && fieldConfig.default !== undefined) value = fieldConfig.default;
|
||
return value;
|
||
}
|
||
|
||
function createInput(fieldConfig) {
|
||
var type = fieldConfig.type || 'text';
|
||
var input;
|
||
if (type === 'textarea' || type === 'json') {
|
||
input = document.createElement('textarea');
|
||
input.rows = type === 'json' ? 4 : 3;
|
||
if (type === 'json') input.placeholder = '{ }';
|
||
} else if (type === 'select') {
|
||
input = document.createElement('select');
|
||
(fieldConfig.options || []).forEach(function (option) {
|
||
var item = document.createElement('option');
|
||
item.value = String(option[0]);
|
||
item.textContent = state.lang === 'ru' ? String(option[1]) : String(option[2] || option[1]);
|
||
input.append(item);
|
||
});
|
||
} else {
|
||
input = document.createElement('input');
|
||
input.type = type === 'number' || type === 'email' ? type : 'text';
|
||
}
|
||
input.className = 'shd-widget-actions-input';
|
||
input.name = fieldConfig.name;
|
||
var value = inputValue(fieldConfig);
|
||
if (type === 'checkbox') {
|
||
input.type = 'checkbox';
|
||
input.checked = value === true || value === 'true' || value === 1 || value === '1';
|
||
} else if (value !== undefined && value !== null) {
|
||
input.value = type === 'json' && typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value);
|
||
}
|
||
if (fieldConfig.required) input.required = true;
|
||
return input;
|
||
}
|
||
|
||
function renderForm() {
|
||
ensurePanel();
|
||
var selectedActionId = state.action ? state.action.id : '';
|
||
elements.select.replaceChildren();
|
||
var placeholder = document.createElement('option');
|
||
placeholder.value = '';
|
||
placeholder.textContent = t('chooseAction');
|
||
elements.select.append(placeholder);
|
||
actionList().forEach(function (action) {
|
||
var option = document.createElement('option');
|
||
option.value = action.id;
|
||
option.textContent = actionLabel(action);
|
||
elements.select.append(option);
|
||
});
|
||
elements.select.value = selectedActionId;
|
||
elements.select.hidden = actionList().length === 0;
|
||
elements.form.replaceChildren();
|
||
elements.status.textContent = '';
|
||
elements.submit.disabled = state.busy;
|
||
var action = state.action;
|
||
if (!action) {
|
||
elements.selection.textContent = state.selected ? text(state.selected.title || state.selected.name || state.selected.id || '') : '';
|
||
elements.submit.hidden = true;
|
||
elements.cancel.hidden = true;
|
||
if (actionList().length && !state.selected && actionList().every(hasTargets)) {
|
||
elements.form.append(make('div', t('selectRecord'), 'shd-widget-actions-hint'));
|
||
}
|
||
return;
|
||
}
|
||
elements.selection.textContent = state.selected ? text(state.selected.title || state.selected.name || state.selected.id || '') : '';
|
||
elements.submit.hidden = false;
|
||
elements.cancel.hidden = false;
|
||
if (hasTargets(action) && !state.selected) elements.form.append(make('div', t('selectRecord'), 'shd-widget-actions-hint'));
|
||
(action.fields || []).forEach(function (fieldConfig) {
|
||
if (!fieldConfig || !fieldConfig.name || fieldConfig.generated || fieldConfig.type === 'hidden') return;
|
||
var wrapper = make('label', undefined, 'shd-widget-actions-field');
|
||
wrapper.append(make('span', label(fieldConfig.label)));
|
||
wrapper.append(createInput(fieldConfig));
|
||
elements.form.append(wrapper);
|
||
});
|
||
if (action.confirm) {
|
||
var confirmLabel = make('label', undefined, 'shd-widget-actions-confirm');
|
||
var confirm = document.createElement('input');
|
||
confirm.type = 'checkbox';
|
||
confirm.name = '__confirm';
|
||
confirm.required = true;
|
||
confirmLabel.append(confirm, make('span', t('confirm')));
|
||
elements.form.append(confirmLabel);
|
||
}
|
||
var notice = make('div', action.destructive ? t('destructive') : t('warning'), action.destructive ? 'shd-widget-actions-warning shd-widget-actions-danger' : 'shd-widget-actions-warning');
|
||
elements.form.append(notice);
|
||
}
|
||
|
||
function coerce(value, type) {
|
||
if (type === 'number') {
|
||
var number = Number(value);
|
||
return Number.isFinite(number) ? number : value;
|
||
}
|
||
if (type === 'checkbox') return Boolean(value);
|
||
if (type === 'json') {
|
||
try { return JSON.parse(value); } catch (error) { throw new Error(t('invalidJson')); }
|
||
}
|
||
return value;
|
||
}
|
||
|
||
function targetType(name) {
|
||
return /^(task|document|contract|booking|item|monitor|proposal|approval|page|notification|topic|membership|organization|space|status|project)_?id$/.test(name) || name === 'issue_number' || name === 'number' ? 'number' : 'text';
|
||
}
|
||
|
||
function targetList(action) {
|
||
if (Array.isArray(action.targets)) return action.targets;
|
||
return action.target ? [action.target] : [];
|
||
}
|
||
|
||
function collectArguments(action) {
|
||
var args = Object.assign({}, action.fixed || {});
|
||
(action.context || []).forEach(function (name) {
|
||
var value = firstValue([name]);
|
||
if (value !== undefined && value !== null && value !== '') args[name] = value;
|
||
});
|
||
targetList(action).forEach(function (targetConfig) {
|
||
var value = firstValue(targetConfig.source || targetConfig.name);
|
||
if (value === undefined || value === null || value === '') throw new Error(t('selectRecord'));
|
||
args[targetConfig.name] = coerce(value, targetConfig.type || targetType(targetConfig.name));
|
||
});
|
||
(action.fields || []).forEach(function (fieldConfig) {
|
||
if (!fieldConfig || !fieldConfig.name || fieldConfig.generated || fieldConfig.type === 'hidden') return;
|
||
var input = Array.prototype.find.call(elements.form.querySelectorAll('[name]'), function (node) {
|
||
return node.getAttribute('name') === fieldConfig.name;
|
||
});
|
||
if (!input) return;
|
||
var raw = fieldConfig.type === 'checkbox' ? input.checked : input.value.trim();
|
||
if (raw === '' && !input.checked && !fieldConfig.required) return;
|
||
if (raw === '' && fieldConfig.required) throw new Error(t('required'));
|
||
args[fieldConfig.name] = coerce(raw, fieldConfig.type || 'text');
|
||
});
|
||
if (action.idempotencyKey) args.idempotency_key = 'shd-widget-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
|
||
if (action.confirmArg) args.confirm = true;
|
||
return args;
|
||
}
|
||
|
||
function setStatus(message, error) {
|
||
elements.status.textContent = message || '';
|
||
elements.status.className = 'shd-widget-actions-status' + (error ? ' shd-widget-actions-status-error' : '');
|
||
}
|
||
|
||
function submitAction() {
|
||
if (state.busy || !state.action) return;
|
||
var action = state.action;
|
||
var args;
|
||
try { args = collectArguments(action); } catch (error) { setStatus(error.message || t('failure'), true); return; }
|
||
state.busy = true;
|
||
elements.submit.disabled = true;
|
||
elements.cancel.disabled = true;
|
||
setStatus(t('loading'), false);
|
||
request('tools/call', { name: action.tool, arguments: args }).then(function (result) {
|
||
unwrap(result);
|
||
setStatus(t('success'), false);
|
||
var refresh = document.getElementById('refresh');
|
||
if (refresh && typeof refresh.click === 'function') window.setTimeout(function () { refresh.click(); }, 250);
|
||
}).catch(function (error) {
|
||
setStatus(error && error.message ? error.message : t('failure'), true);
|
||
}).finally(function () {
|
||
state.busy = false;
|
||
elements.submit.disabled = false;
|
||
elements.cancel.disabled = false;
|
||
});
|
||
}
|
||
|
||
function recordFromNode(node) {
|
||
var content = String(node.textContent || '').toLowerCase();
|
||
var exact = state.records.find(function (item) {
|
||
var id = text(item.id || item.code || item.key || item.number).toLowerCase();
|
||
return id && content.indexOf(id) !== -1;
|
||
});
|
||
if (exact) return exact;
|
||
return state.records.find(function (item) {
|
||
var title = text(item.title || item.name || item.display_name || item.number).toLowerCase();
|
||
return title && content.indexOf(title) !== -1;
|
||
}) || null;
|
||
}
|
||
|
||
function update(value) {
|
||
var payload = normalize(value) || {};
|
||
var widget = widgetFrom(payload);
|
||
if (Object.keys(widget).length) state.widget = widget;
|
||
var records = recordsFrom(payload);
|
||
if (records.length || Array.isArray(payload.data) || Array.isArray(payload.items) || Array.isArray(payload.projects)) state.records = records;
|
||
ensurePanel();
|
||
var actions = actionList();
|
||
elements.panel.hidden = actions.length === 0;
|
||
if (actions.length && !state.action) renderForm();
|
||
else if (actions.length) renderForm();
|
||
}
|
||
|
||
document.addEventListener('click', function (event) {
|
||
var node = event.target && event.target.closest ? event.target.closest('.record,.project-row,.visual-card,.progress-card,.preview-card,.calendar-card,.timeline-card,.kanban-card') : null;
|
||
if (!node || (elements.panel && elements.panel.contains(node))) return;
|
||
var item = recordFromNode(node);
|
||
if (!item) return;
|
||
state.selected = item;
|
||
ensurePanel();
|
||
renderForm();
|
||
}, true);
|
||
|
||
window.addEventListener('message', function (event) {
|
||
if (event.source !== window.parent) return;
|
||
var message = event.data;
|
||
if (!message || message.jsonrpc !== '2.0') return;
|
||
if (message.id !== undefined && pending.has(message.id)) {
|
||
var requestState = pending.get(message.id);
|
||
pending.delete(message.id);
|
||
window.clearTimeout(requestState.timeout);
|
||
if (message.error) requestState.reject(new Error(message.error.message || t('failure')));
|
||
else requestState.resolve(message.result);
|
||
return;
|
||
}
|
||
if (message.method === 'ui/notifications/tool-input' || message.method === 'ui/notifications/tool-result') update(message.params || {});
|
||
}, { passive: true });
|
||
|
||
ensurePanel();
|
||
var initial = typeof window !== 'undefined' && window.openai ? (window.openai.toolOutput || window.openai.toolInput) : null;
|
||
if (initial) update(initial);
|
||
}());
|
||
</script>
|
||
</body>
|
||
</html>
|