@ykurilov/agent-chat-react 0.0.0-stage → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +1670 -0
  2. package/LICENSE +21 -0
  3. package/README.md +128 -2
  4. package/dist/chunk-JZQ7ZOJI.js +6959 -0
  5. package/dist/chunk-JZQ7ZOJI.js.map +1 -0
  6. package/dist/index.js +501 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/panel.js +399 -0
  9. package/dist/panel.js.map +1 -0
  10. package/dist/types/agent-chat-extensions.d.ts +76 -0
  11. package/dist/types/agent-chat.d.ts +103 -0
  12. package/dist/types/agent-panel/agent-panel-hooks.d.ts +14 -0
  13. package/dist/types/agent-panel/agent-panel-provider.d.ts +24 -0
  14. package/dist/types/agent-panel/agent-panel-skin.d.ts +41 -0
  15. package/dist/types/agent-panel/agent-panel.d.ts +13 -0
  16. package/dist/types/agent-panel/index.d.ts +8 -0
  17. package/dist/types/agent-panel/panel-context.d.ts +46 -0
  18. package/dist/types/agent-panel/panel-state.d.ts +12 -0
  19. package/dist/types/answer-editing/answer-editing.d.ts +25 -0
  20. package/dist/types/app-bridge/app-bridge.d.ts +50 -0
  21. package/dist/types/app-bridge/browser-calls.d.ts +24 -0
  22. package/dist/types/app-bridge/create-app-bridge.d.ts +35 -0
  23. package/dist/types/app-bridge/remembered-ids.d.ts +6 -0
  24. package/dist/types/app-bridge/tab-id.d.ts +10 -0
  25. package/dist/types/app-events.d.ts +39 -0
  26. package/dist/types/app-refusal.d.ts +21 -0
  27. package/dist/types/attachments-contract.d.ts +82 -0
  28. package/dist/types/background-tasks/use-background-tasks.d.ts +53 -0
  29. package/dist/types/chat-access.d.ts +33 -0
  30. package/dist/types/chat-list/active-chat-memory.d.ts +5 -0
  31. package/dist/types/chat-list/chat-list-client.d.ts +35 -0
  32. package/dist/types/chat-list/chat-list-skin.d.ts +63 -0
  33. package/dist/types/chat-list/chat-workspace.d.ts +17 -0
  34. package/dist/types/chat-list/use-chat-list-view.d.ts +12 -0
  35. package/dist/types/chat-list/use-chat-list.d.ts +47 -0
  36. package/dist/types/chat-list/use-workspace-list-sync.d.ts +7 -0
  37. package/dist/types/chat-state/chat-state.d.ts +27 -0
  38. package/dist/types/chat-types.d.ts +85 -0
  39. package/dist/types/chat-watch/chat-watch.d.ts +57 -0
  40. package/dist/types/close-stopped-tool-calls.d.ts +8 -0
  41. package/dist/types/composer-actions.d.ts +29 -0
  42. package/dist/types/diagrams/diagram-renders.d.ts +16 -0
  43. package/dist/types/diagrams/mermaid-blocks.d.ts +18 -0
  44. package/dist/types/diagrams/unique-svg-ids.d.ts +6 -0
  45. package/dist/types/diagrams/use-diagram-blocks.d.ts +25 -0
  46. package/dist/types/diagrams/use-diagram-colors.d.ts +8 -0
  47. package/dist/types/dictation-field.d.ts +19 -0
  48. package/dist/types/expanded-parts.d.ts +11 -0
  49. package/dist/types/feed-announcement.d.ts +7 -0
  50. package/dist/types/helpers/helper-call.d.ts +57 -0
  51. package/dist/types/index.d.ts +53 -0
  52. package/dist/types/labels.d.ts +468 -0
  53. package/dist/types/live-demos/answer-fences.d.ts +25 -0
  54. package/dist/types/live-demos/live-demo-document.d.ts +42 -0
  55. package/dist/types/live-demos/live-demo-frame.d.ts +14 -0
  56. package/dist/types/live-demos/use-live-demo-blocks.d.ts +18 -0
  57. package/dist/types/liveness/connection-errors.d.ts +15 -0
  58. package/dist/types/liveness/reconnect-attempts.d.ts +14 -0
  59. package/dist/types/liveness/request-deadline.d.ts +16 -0
  60. package/dist/types/liveness/silence-watchdog.d.ts +21 -0
  61. package/dist/types/media-viewer/answer-file-images.d.ts +18 -0
  62. package/dist/types/media-viewer/collect-viewer-items.d.ts +16 -0
  63. package/dist/types/media-viewer/generated-images.d.ts +18 -0
  64. package/dist/types/media-viewer/media-viewer-types.d.ts +17 -0
  65. package/dist/types/media-viewer/open-attachments.d.ts +9 -0
  66. package/dist/types/media-viewer/use-media-viewer.d.ts +23 -0
  67. package/dist/types/memory/agent-memory.d.ts +13 -0
  68. package/dist/types/memory/memory-client.d.ts +19 -0
  69. package/dist/types/memory/memory-skin.d.ts +103 -0
  70. package/dist/types/memory/memory-tree.d.ts +17 -0
  71. package/dist/types/memory/memory-types.d.ts +77 -0
  72. package/dist/types/memory/use-memory-notes.d.ts +23 -0
  73. package/dist/types/message-actions.d.ts +45 -0
  74. package/dist/types/message-feed.d.ts +27 -0
  75. package/dist/types/notices/agent-notices.d.ts +40 -0
  76. package/dist/types/pause.d.ts +2 -0
  77. package/dist/types/server-refusal.d.ts +16 -0
  78. package/dist/types/skin-contract.d.ts +673 -0
  79. package/dist/types/spending-status.d.ts +76 -0
  80. package/dist/types/steering/corrections.d.ts +19 -0
  81. package/dist/types/step-reset.d.ts +15 -0
  82. package/dist/types/subject-chat/subject-chat-loading.d.ts +8 -0
  83. package/dist/types/subject-chat/use-subject-chat-id.d.ts +21 -0
  84. package/dist/types/thinking-status/thinking-status.d.ts +33 -0
  85. package/dist/types/turn-finished.d.ts +14 -0
  86. package/dist/types/use-agent-chat.d.ts +228 -0
  87. package/dist/types/use-model-choice.d.ts +31 -0
  88. package/dist/types/use-voice-dialog.d.ts +18 -0
  89. package/dist/types/use-voice-dictation.d.ts +17 -0
  90. package/dist/types/voice-dialog.d.ts +103 -0
  91. package/dist/types/voice-input.d.ts +47 -0
  92. package/dist/types/voice-tags.d.ts +18 -0
  93. package/dist/types/voice-turn.d.ts +11 -0
  94. package/dist/types/widgets/custom-widget-guard.d.ts +21 -0
  95. package/dist/types/widgets/form-check.d.ts +29 -0
  96. package/dist/types/widgets/suggested-replies.d.ts +6 -0
  97. package/dist/types/widgets/use-widget-answer.d.ts +54 -0
  98. package/dist/types/widgets/widget-answer-keys.d.ts +14 -0
  99. package/dist/types/widgets/widget-messages.d.ts +14 -0
  100. package/dist/types/widgets/widget-part.d.ts +17 -0
  101. package/dist/types/widgets/widget-view.d.ts +50 -0
  102. package/dist/types/without-markdown.d.ts +1 -0
  103. package/dist/types/work-view/step-phrases.d.ts +37 -0
  104. package/dist/types/work-view/work-layout.d.ts +57 -0
  105. package/dist/types/work-view/work-steps-part.d.ts +15 -0
  106. package/package.json +53 -3
@@ -0,0 +1,673 @@
1
+ import type { ComponentType, ReactElement, ReactNode, RefObject } from "react";
2
+ import type { ComposerAttachments, MessageAttachmentsView } from "./attachments-contract.js";
3
+ import type { AnswerStatus, ApprovalDecision, ErrorNoticeKind } from "./chat-types.js";
4
+ import type { MediaViewerItem } from "./media-viewer/media-viewer-types.js";
5
+ import type { VoiceDialogView } from "./voice-dialog.js";
6
+ /** Состояние вызова инструмента в ленте (FR-010). */
7
+ export type ToolCallState = "running" | "done" | "error";
8
+ /**
9
+ * Подтверждение вызова инструмента в строке вызова (договор скина, версия 9).
10
+ * `requested` — агент ждёт решения: показать что он хочет сделать и три кнопки; `pending` — ответ уходит на сервер
11
+ * (кнопки заняты), `failed` — не вышло, можно нажать ещё раз; `failureText` — почему, готовой надписью (нет — общая
12
+ * `approvalFailed`). `canAlways` — кнопку «Всегда в этом чате» показывать только при `true`: для вызова, чьё решение
13
+ * зависит от входных данных, сервер её не предлагает. Остальное — итог решения, пометка без кнопок.
14
+ */
15
+ export type ToolApprovalView = {
16
+ state: "requested";
17
+ onDecide: (decision: ApprovalDecision) => void;
18
+ pending: boolean;
19
+ failed: boolean;
20
+ failureText?: string;
21
+ canAlways: boolean;
22
+ } | {
23
+ state: "allowed" | "always" | "denied";
24
+ };
25
+ /** Состояние помощника в строке вызова (версия 18). `background` — помощник ушёл в фон, итог придёт служебным сообщением. */
26
+ export type HelperCallStatus = "running" | "awaiting_approval" | "done" | "failed" | "background";
27
+ /** Шаг помощника: вызов инструмента или кусок текста (версия 18). Результат вызова уже обрезан сервером. */
28
+ export type HelperStepView = {
29
+ kind: "tool";
30
+ id: string;
31
+ toolName: string;
32
+ title?: string;
33
+ state: ToolCallState;
34
+ input?: unknown;
35
+ output?: unknown;
36
+ errorText?: string;
37
+ } | {
38
+ kind: "text";
39
+ id: string;
40
+ text: string;
41
+ };
42
+ /**
43
+ * Строка вызова помощника (версия 18): человек видит всё, что делал помощник, модель основного агента получает только
44
+ * итог. Подтверждение помощника — тот же `ToolApprovalView`, что у обычного вызова.
45
+ */
46
+ export interface HelperCallView {
47
+ /** Имя помощника, `helper_<имя>` без приставки; нет названия — показывать его. */
48
+ name: string;
49
+ title?: string;
50
+ /** Задание, которое получил помощник. */
51
+ task: string;
52
+ steps: HelperStepView[];
53
+ /** Итог помощника; пока он работает — то, что уже написано. */
54
+ text: string;
55
+ status: HelperCallStatus;
56
+ approval?: ToolApprovalView;
57
+ /** Что помощник хочет сделать, пока ждёт решения: его инструмент, `title` инструмента и входные данные. */
58
+ approvalAction?: {
59
+ toolName: string;
60
+ title?: string;
61
+ input?: unknown;
62
+ };
63
+ errorText?: string;
64
+ open: boolean;
65
+ onOpenChange: (open: boolean) => void;
66
+ }
67
+ /** Состояние фоновой задачи в карточке (версия 18). */
68
+ export type TaskStatusView = "queued" | "running" | "awaiting_approval" | "done" | "failed" | "canceled" | "interrupted" | "timed_out";
69
+ /** Карточка фоновой задачи (версия 18). `onCancel` есть, пока задачу можно отменить. */
70
+ export interface TaskCardView {
71
+ id: string;
72
+ title: string;
73
+ status: TaskStatusView;
74
+ /** Сколько идёт (или шла) задача, миллисекунды; готовой надписью её делает скин через `labels.tasks.elapsed`. */
75
+ elapsedMs: number;
76
+ onCancel?: () => void;
77
+ /** Отмена ушла на сервер, ответа ещё нет: кнопку сделать занятой. */
78
+ canceling?: boolean;
79
+ }
80
+ /** Что случилось, о чём пишет служебное сообщение (версия 18). */
81
+ export type AgentNoticeViewKind = "task_done" | "task_failed" | "task_canceled" | "task_interrupted" | "task_timed_out" | "task_awaiting_approval" | "conversation_started";
82
+ /**
83
+ * Служебное сообщение в ленте (версия 18): рисуется плашкой, а не пузырём человека. Текст `result` и `errorText` —
84
+ * только текстом, без разметки. У `task_awaiting_approval` есть `approval` — кнопки решения.
85
+ */
86
+ export interface AgentNoticeViewProps {
87
+ kind: AgentNoticeViewKind;
88
+ title: string;
89
+ result?: string;
90
+ errorText?: string;
91
+ approval?: ToolApprovalView;
92
+ }
93
+ /** Мессенджер, из которого создан чат (версия 18: пометка в списке чатов, `ChatListItem.origin`). */
94
+ export type ChatOriginView = "telegram" | "vk" | "max";
95
+ /** Выбор модели и уровня рассуждений в поле ввода (договор скина, версия 3). */
96
+ export interface ModelChoiceView {
97
+ providers: Array<{
98
+ id: string;
99
+ name: string;
100
+ models: Array<{
101
+ id: string;
102
+ name: string;
103
+ acceptsImages?: boolean;
104
+ }>;
105
+ }>;
106
+ /** Текущий выбор: провайдер и модель. */
107
+ current: {
108
+ providerId: string;
109
+ modelId: string;
110
+ };
111
+ onChange: (next: {
112
+ providerId: string;
113
+ modelId: string;
114
+ }) => void;
115
+ /** Уровни выбранной модели по порядку профиля; пустой массив — выбор уровня не показывать. */
116
+ reasoningLevels: Array<{
117
+ id: string;
118
+ name: string;
119
+ }>;
120
+ reasoning: string;
121
+ onReasoningChange: (levelId: string) => void;
122
+ /** Что пошло не так, готовой надписью: список не загрузился (тогда `providers` пуст) или выбор не сохранился (откачен). */
123
+ error?: string;
124
+ }
125
+ /** Диктовка в поле ввода (договор скина, версия 5). */
126
+ export interface VoiceInputView {
127
+ state: "idle" | "connecting" | "listening" | "stopping";
128
+ /** Нажатие: начать диктовку или закончить. Во время подключения — отменить. */
129
+ onToggle: () => void;
130
+ /** Текст надиктован, и сервер причёсывает его моделью: показать тихий признак (необязательное поле, скины без него работают). */
131
+ polishing?: boolean;
132
+ /** Что пошло не так, готовой надписью. */
133
+ error?: string;
134
+ }
135
+ /** Поле формы `ask_form` для скина; `error` — готовая надпись об ошибке поля (проверка до отправки, слой поведения). */
136
+ export interface FormFieldView {
137
+ name: string;
138
+ label: string;
139
+ kind: "text" | "number" | "date" | "select";
140
+ required: boolean;
141
+ /** Пределы: число у `number`, дата `YYYY-MM-DD` у `date`. */
142
+ min?: number | string;
143
+ max?: number | string;
144
+ /** Варианты `select`. */
145
+ options?: string[];
146
+ /** Многострочное текстовое поле. */
147
+ multiline?: boolean;
148
+ error?: string;
149
+ }
150
+ /**
151
+ * Вид базового или своего виджета для скина (версия 13). Проверку формы и выбора (min/max, обязательные поля, пределы
152
+ * чисел и дат) делает слой поведения: `onSubmit` неверных значений не отправляет, а скин получает ошибку поля в
153
+ * `FormFieldView.error`.
154
+ */
155
+ export type WidgetView = {
156
+ toolCallId: string;
157
+ /** Можно нажимать: последний ответ ветки ждёт. */
158
+ active: boolean;
159
+ /** Ответ отправляется: кнопки неактивны. */
160
+ pending: boolean;
161
+ failure?: string;
162
+ /** Показать «Ответ дан сообщением» (`labels.widgets.answeredByMessage`). */
163
+ answeredByMessage: boolean;
164
+ } & ({
165
+ kind: "choice";
166
+ question: string;
167
+ options: string[];
168
+ chosen?: string;
169
+ onChoose?: (option: string) => void;
170
+ } | {
171
+ kind: "multi_choice";
172
+ question: string;
173
+ options: string[];
174
+ min: number;
175
+ max: number;
176
+ chosen?: string[];
177
+ onSubmit?: (options: string[]) => void;
178
+ } | {
179
+ kind: "rating";
180
+ question: string;
181
+ scale: "stars" | "range";
182
+ from: number;
183
+ to: number;
184
+ fromLabel?: string;
185
+ toLabel?: string;
186
+ value?: number;
187
+ onRate?: (value: number) => void;
188
+ } | {
189
+ kind: "form";
190
+ title: string;
191
+ fields: FormFieldView[];
192
+ values?: Record<string, string | number>;
193
+ onSubmit?: (values: Record<string, string | number>) => void;
194
+ }
195
+ /** `content` — компонент приложения из `widgets[name]`; нет — запасной вид: вопрос и `labels.widgets.fallback`. */
196
+ | {
197
+ kind: "custom";
198
+ name: string;
199
+ question?: string;
200
+ content?: ReactNode;
201
+ });
202
+ /** Свойства компонента своего виджета (`widgets={{ имя: Компонент }}` у страницы). */
203
+ export interface CustomWidgetProps<Input = unknown, Answer = unknown> {
204
+ input: Input;
205
+ /**
206
+ * Чат, где задан вопрос, и id вызова инструмента: по ним компонент сам загрузит свои данные (тот же `toolCallId`
207
+ * видит `checkAnswer` на сервере). `AgentChat` передаёт оба всегда; в типе они необязательные, чтобы свои тесты
208
+ * компонента без них не сломались.
209
+ */
210
+ chatId?: string;
211
+ toolCallId?: string;
212
+ /** `closed` — «Стоп», сбой или другая ветка. */
213
+ state: "pending" | "answered" | "answered_by_message" | "closed";
214
+ answer?: Answer;
215
+ /** Есть только у активного виджета. */
216
+ onAnswer?: (answer: Answer) => void;
217
+ /** Ответ отправляется. */
218
+ pending: boolean;
219
+ /** Текст ошибки на языке собеседника. */
220
+ failure?: string;
221
+ }
222
+ /** Цвета схемы: их отдаёт скин (`diagramColors`), по ним рисовальщик красит схему. Совпадает с `DiagramColors` пакета схем. */
223
+ export interface DiagramColors {
224
+ background: string;
225
+ text: string;
226
+ line: string;
227
+ node: string;
228
+ accent: string;
229
+ dark: boolean;
230
+ }
231
+ /** Схема из блока ```mermaid ответа (версия 13). */
232
+ export type DiagramView =
233
+ /** Блок печатается или рисуется: заглушка `labels.diagram.loading`. */
234
+ {
235
+ state: "streaming";
236
+ }
237
+ /** `svg` — чистая разметка, скин кладёт её как есть; диктору — «схема» и `source`. */
238
+ | {
239
+ state: "ready";
240
+ svg: string;
241
+ source: string;
242
+ }
243
+ /** Не нарисовалась: показать исходник блоком кода. */
244
+ | {
245
+ state: "failed";
246
+ source: string;
247
+ };
248
+ /**
249
+ * Цвета живой демонстрации (версия 22): их отдаёт скин (`demoColors`), внутрь рамки они идут CSS-переменными `--bg`,
250
+ * `--surface`, `--text`, `--muted`, `--border`, `--accent`, `--on-accent`.
251
+ */
252
+ export interface LiveDemoColors {
253
+ background: string;
254
+ surface: string;
255
+ text: string;
256
+ muted: string;
257
+ border: string;
258
+ accent: string;
259
+ /** Текст на заливке `accent`. */
260
+ onAccent: string;
261
+ dark: boolean;
262
+ }
263
+ /** Живая демонстрация из блока ```html-demo ответа (версия 22, specs/043-live-demos). */
264
+ export type LiveDemoView =
265
+ /** Блок дописывается: плашка `labels.demo.loading`. */
266
+ {
267
+ state: "streaming";
268
+ }
269
+ /**
270
+ * `frame` — готовая изолированная рамка слоя поведения: скин кладёт её как есть, во всю ширину ответа, и не меняет
271
+ * её атрибуты. Высоту рамка держит сама.
272
+ */
273
+ | {
274
+ state: "ready";
275
+ source: string;
276
+ frame: ReactElement;
277
+ }
278
+ /** Не запускается: показать исходник блоком кода и пометку `labels.demo[reason]`. */
279
+ | {
280
+ state: "code";
281
+ source: string;
282
+ reason: "tooLarge" | "unfinished" | "leftPage" | "externalContent";
283
+ };
284
+ /** Картинки агента из вызова `generate_images` (версия 13). */
285
+ export interface GeneratedImagesView {
286
+ /** Заказано картинок. */
287
+ ordered: number;
288
+ /** Пропорция, например `"16:9"`: заглушки и готовые картинки её держат, чтобы лента не прыгала. */
289
+ aspectRatio: string;
290
+ items: Array<{
291
+ state: "loading";
292
+ key: string;
293
+ } | {
294
+ state: "ready";
295
+ key: string;
296
+ src: string;
297
+ alt: string;
298
+ width?: number;
299
+ height?: number;
300
+ onOpen: (trigger: HTMLElement) => void;
301
+ } | {
302
+ state: "failed";
303
+ key: string;
304
+ reason: string;
305
+ }>;
306
+ /** Человек нажал «Стоп»: показать `labels.images.stopped`. */
307
+ stopped: boolean;
308
+ }
309
+ /**
310
+ * Видео агента из вызова `show_video` (версия 14). Плеер грузить только по нажатию на карточку: до него к площадке
311
+ * не уходит ни одного запроса. Играет или нет — держит сам скин.
312
+ */
313
+ export interface AgentVideoView {
314
+ /** id вызова `show_video`: годится в `key` и в id элементов. */
315
+ id: string;
316
+ title: string;
317
+ provider: "youtube" | "kinescope";
318
+ /** Адрес встроенного плеера с автозапуском и стартом; собран из площадки и кода, а не взят из базы как есть. */
319
+ embedUrl: string;
320
+ /** Страница видео на площадке с тем же стартом. */
321
+ pageUrl: string;
322
+ /** Обложка из каталога приложения. Нет — своя заглушка, не картинка с площадки. */
323
+ poster?: string;
324
+ /** «18:40». */
325
+ duration?: string;
326
+ /** С какой минуты начнётся, «3:20»: показать `labels.videos.from(start)`. */
327
+ start?: string;
328
+ }
329
+ /** Окно просмотра картинок и схем (версия 13). Фокус после закрытия возвращает слой поведения. */
330
+ export interface MediaViewerProps {
331
+ items: MediaViewerItem[];
332
+ index: number;
333
+ onIndexChange: (index: number) => void;
334
+ onClose: () => void;
335
+ }
336
+ /**
337
+ * Своя кнопка приложения под ответом агента (версия 17): подпись и иконку задало приложение, иконка — готовый элемент.
338
+ * `busy` — обработчик ещё идёт: кнопку показать неактивной, `onRun` не звать.
339
+ */
340
+ export interface MessageActionView {
341
+ id: string;
342
+ label: string;
343
+ icon?: ReactNode;
344
+ busy: boolean;
345
+ onRun: () => void;
346
+ }
347
+ /**
348
+ * Работа агента в простом виде (версия 23, `workView: "simple"` у `AgentChat`). `live` — ход идёт: одна строка с текущим
349
+ * шагом, уже с многоточием («Рисую календарь…»); фраза меняется часто — диктору её не читать на каждой смене.
350
+ * `done` — шаги свёрнуты: строка `labels.workView.stepsDone(count)` (`count` — число вызовов вместе с повторами),
351
+ * раскрытие — список фраз (одинаковые подряд уже схлопнуты в «… ×N»; `failed` — шаг не получился или не закончен,
352
+ * отметить тихо). Фразы — только текст, без разметки, не длиннее 120 знаков, но слово может быть длинным — переносить.
353
+ */
354
+ export type WorkStepsView = {
355
+ state: "live";
356
+ phrase: string;
357
+ } | {
358
+ state: "done";
359
+ steps: Array<{
360
+ phrase: string;
361
+ failed: boolean;
362
+ }>;
363
+ count: number;
364
+ open: boolean;
365
+ onOpenChange: (open: boolean) => void;
366
+ };
367
+ /** Реплика живого сотрудника человеку (версия 25, `ChatService.appendStaffMessage`): сказано от имени компании, не агентом. */
368
+ export interface StaffMessageView {
369
+ /** Что сотрудник написал человеку: обычный текст, без разметки. */
370
+ text: string;
371
+ /** Имя сотрудника («Ольга»); нет — подписать просто «сотрудник». */
372
+ staffName?: string;
373
+ }
374
+ /**
375
+ * Договор скина, версия 25 (в версии 25 добавилось только необязательное `StaffMessage` — реплика живого сотрудника,
376
+ * docs/skin-v25.md; скин версии 24 работает без правок: реплика показывается ответом агента с подписью
377
+ * `labels.notices.staffSaid`).
378
+ * Версия 24 (в версии 24 добавилось только необязательное `AgentMessage.undelivered` — отметка
379
+ * «не доставлено», specs/061-flows-for-apps/plan.md; скин версии 23 работает без правок: отметки нет).
380
+ * Версия 23 (в версии 23 добавилось только необязательное `WorkSteps` — простой вид работы агента,
381
+ * docs/work-view.md; скин версии 22 работает без правок: в простом виде шаги рисуются строкой `ToolCallRow` с фразой
382
+ * в `title`, без входа, а в результате — список понятных фраз текстом).
383
+ * Версия 22 (в версии 22 добавились только необязательные `AgentText.renderDemo` и `demoColors` — живые
384
+ * демонстрации, specs/043-live-demos/spec.md; скин версии 21 работает без правок: блок ```html-demo показывается кодом).
385
+ * Версия 21 (в версии 21 добавились только необязательные `Composer.canSteer` и `UserMessage.pending` —
386
+ * поправка во время ответа, specs/034-steering/spec.md; скин версии 20 работает без правок: во время ответа поле не
387
+ * отправляет, поправок нет).
388
+ * Версия 20 (в версии 20 добавилось только необязательное `ChatClosedNotice` — надпись закрытого чата,
389
+ * а у `ChatListSkin.ChatWorkspace` свойство `list` стало необязательным, specs/030-closed-chat-rights/plan.md; скин
390
+ * версии 19 работает без правок: закрытие показывает `ErrorNotice` с текстом).
391
+ * Версия 19 (в версии 19 добавилось только необязательное `Composer.actions` — кнопки приложения у поля
392
+ * ввода, specs/029-in-product-agent/plan.md; скин версии 18 работает без правок: этих кнопок нет).
393
+ * Версия 18 (в версии 18 добавились только необязательные `HelperCallRow`, `TaskCard` и `AgentNoticeView`
394
+ * и тип `ChatOriginView` для пометки мессенджера в списке чатов, specs/027-helpers-background-messengers/plan.md;
395
+ * скин версии 17 работает без правок: вызов помощника — обычной строкой инструмента, задачи не показываются, служебное
396
+ * сообщение — короткой строкой).
397
+ * Версия 17 (в версии 17 добавилось только необязательное `AgentMessage.actions` — свои кнопки приложения
398
+ * под ответом; скин версии 16 работает без правок: этих кнопок нет).
399
+ * Версия 16 (в версии 16 добавилось только необязательное `ErrorNotice.text` — текст отказа сервера;
400
+ * скин версии 15 работает без правок: вместо текста сервера общая надпись).
401
+ * Версия 15 (в версии 15 добавились только необязательные `AgentMessage.onEdit`, `AgentMessage.edited` и
402
+ * `ToolCallRow.outputEdited` для правки ответа в песочнице, specs/026-agent-sandbox/plan.md; скин версии 14 работает без
403
+ * правок: кнопки правки и отметок «изменено» нет).
404
+ * Версия 14 (в версии 14 добавилась только необязательная `AgentVideo`, specs/025-agent-videos/plan.md;
405
+ * скин версии 13 работает без правок: видео показывается ссылкой в тексте ответа).
406
+ * Версия 13 (в версии 13 добавились только необязательные `Widget`, `SuggestedReplies`, `GeneratedImages`,
407
+ * `MediaViewer`, `diagramColors`, `AgentText.renderDiagram`/`onOpenDiagram` и `MessageAttachmentItem.onOpen`,
408
+ * specs/024-agent-widgets/contracts/react-and-skin.md; скин версии 10 работает без правок: виджеты показываются запасным
409
+ * видом в строке инструмента, схемы — кодом, картинки — строкой инструмента, окно просмотра не открывается).
410
+ * Версия 10 (добавились только необязательные `Composer.dialog`, `AgentMessage.voiceInterrupted` и `AgentMessage.cutByLength`,
411
+ * specs/016-voice-dialog/contracts/skin.md; версия 7 — отдельный `ChatListSkin` для `ChatWorkspace`, см. chat-list/chat-list-skin.ts; в версии 5 добавились только необязательные `Composer.voice`, `Composer.attachments`
412
+ * и `UserMessage.attachments`, в версии 6 — `BranchSwitcher`, `MessageEditor` и свойства веток у сообщений, в версии 8 —
413
+ * только необязательные `SpendingNotice` и `Composer.sendingBlocked`, в версии 9 — только необязательные
414
+ * `ToolCallRow.title` и `ToolCallRow.approval`): слой поведения
415
+ * собирает ленту только из этих
416
+ * компонентов и ничего своего не рисует. Надписи компоненты берут из `useChatLabels()`.
417
+ */
418
+ export interface ChatSkin {
419
+ Layout: ComponentType<{
420
+ feed: ReactNode;
421
+ composer: ReactNode;
422
+ notice?: ReactNode;
423
+ /** Повесить на элемент, который прокручивается; `feed` положить внутрь него. */
424
+ scrollRef: RefObject<HTMLDivElement | null>;
425
+ /** Текст для диктора: положить в визуально скрытый элемент с `aria-live="polite"`. */
426
+ announcement: string;
427
+ }>;
428
+ UserMessage: ComponentType<{
429
+ text: string;
430
+ /** Файлы сообщения (миниатюры картинок и плашки); нет — у сообщения только текст. */
431
+ attachments?: MessageAttachmentsView;
432
+ /** Переключатель веток (`BranchSwitcher`), если у сообщения есть соседи: положить рядом с сообщением. */
433
+ branchSwitcher?: ReactNode;
434
+ /** «Изменить»: нет — правка сейчас недоступна (идёт ответ, нет связи). */
435
+ onEdit?: () => void;
436
+ /**
437
+ * Поправка, которую агент ещё не взял (версия 21): показать пометку `labels.steeringPending` у сообщения. Агент взял
438
+ * поправку — сообщение придёт без `pending`.
439
+ */
440
+ pending?: boolean;
441
+ }>;
442
+ /**
443
+ * Переключатель веток «‹ 2/3 ›»: `position` считается с 1, `count` — сколько всего вариантов.
444
+ * Обработчика нет на краю (нет предыдущего или следующего) и пока идёт ответ: кнопку показать неактивной.
445
+ */
446
+ BranchSwitcher: ComponentType<{
447
+ position: number;
448
+ count: number;
449
+ onPrevious?: () => void;
450
+ onNext?: () => void;
451
+ }>;
452
+ /**
453
+ * Правка отправленного сообщения. Слой поведения показывает её вместо `UserMessage` и сам хранит черновик:
454
+ * скин рисует поле с `value`, кнопки «Отправить» (`onSave`) и «Отмена» (`onCancel`); `canSave` — `false` для пустого текста.
455
+ * Файлы сообщения при правке остаются как были: `attachments` — их показать над полем, без кнопок «убрать».
456
+ */
457
+ MessageEditor: ComponentType<{
458
+ value: string;
459
+ onValueChange: (value: string) => void;
460
+ onSave: () => void;
461
+ onCancel: () => void;
462
+ canSave: boolean;
463
+ /** Почему правку не удалось отправить (текст уже на языке собеседника): показать под полем как `role="alert"`; текст в поле остаётся. */
464
+ error?: string;
465
+ attachments?: MessageAttachmentsView;
466
+ }>;
467
+ AgentMessage: ComponentType<{
468
+ status: AnswerStatus;
469
+ /** Части ответа в порядке прихода. */
470
+ children: ReactNode;
471
+ /** Скопировать ответ целиком. */
472
+ onCopy: () => void;
473
+ /** Модель не написала текста. */
474
+ empty: boolean;
475
+ /** «Повторить» у остановленного, прерванного или пустого ответа; только у последнего ответа. */
476
+ onRetry?: () => void;
477
+ /** Блок ошибки ответа (`ErrorNotice`), рисуется под написанной частью. */
478
+ notice?: ReactNode;
479
+ /** Переключатель веток (`BranchSwitcher`), если у ответа есть соседи (переделки). */
480
+ branchSwitcher?: ReactNode;
481
+ /** «Переделать ответ» с чистого листа; только у последнего ответа: законченного или оборванного на вызовах инструментов (рядом с «Повторить»). */
482
+ onRegenerate?: () => void;
483
+ /** Озвучку ответа оборвало перебивание (версия 10): показать отметку «Вы перебили: озвучено до «…»» из `labels.voiceDialog.interruptedMark`. */
484
+ voiceInterrupted?: {
485
+ spokenTail: string;
486
+ };
487
+ /** Ответ оборван пределом длины ответа модели (версия 10, необязательное): показать отметку `labels.answerCutByLength`. */
488
+ cutByLength?: boolean;
489
+ /** «Изменить ответ» (версия 15, необязательное): есть только в песочнице и только у законченного ответа; подпись `labels.editAnswer`. */
490
+ onEdit?: () => void;
491
+ /** Ответ исправил человек (версия 15, необязательное): показать отметку `labels.answerEdited`. */
492
+ edited?: boolean;
493
+ /**
494
+ * Ответ не дошёл до человека (версия 24, необязательное): показать серую отметку `labels.answerUndelivered`. Модель
495
+ * этот ответ больше не видит.
496
+ */
497
+ undelivered?: boolean;
498
+ /**
499
+ * Свои кнопки приложения (версия 17, необязательное): нарисовать в ряду действий под ответом, после встроенных, в том
500
+ * же виде. Пока ответ пишется, их нет.
501
+ */
502
+ actions?: MessageActionView[];
503
+ }>;
504
+ AgentText: ComponentType<{
505
+ markdown: string;
506
+ streaming: boolean;
507
+ /** Версия 13: есть, если подключён `diagrams`. Скин зовёт для каждого блока ```mermaid; нет — блок как обычный код. */
508
+ renderDiagram?: (source: string, blockIndex: number) => DiagramView;
509
+ /** Версия 13: открыть готовую схему в окне просмотра. */
510
+ onOpenDiagram?: (blockIndex: number, trigger: HTMLElement) => void;
511
+ /**
512
+ * Версия 22: есть, если подключены `liveDemos`. Скин зовёт для каждого блока ```html-demo (номер — порядок среди таких
513
+ * блоков, `splitAnswerFences`); нет — блок как обычный код.
514
+ */
515
+ renderDemo?: (source: string, blockIndex: number) => LiveDemoView;
516
+ }>;
517
+ Reasoning: ComponentType<{
518
+ text: string;
519
+ streaming: boolean;
520
+ /** `null`, пока рассуждение идёт. */
521
+ durationMs: number | null;
522
+ open: boolean;
523
+ onOpenChange: (open: boolean) => void;
524
+ }>;
525
+ ToolCallRow: ComponentType<{
526
+ toolName: string;
527
+ /** Название действия по-человечески, если приложение его задало (`title` инструмента); нет — показывать `toolName`. */
528
+ title?: string;
529
+ /** Есть, если вызов требует подтверждения (версия 9). Нет — обычная строка вызова. */
530
+ approval?: ToolApprovalView;
531
+ state: ToolCallState;
532
+ /** Вход вызова; пока модель его пишет — частичный. */
533
+ input?: unknown;
534
+ output?: unknown;
535
+ errorText?: string;
536
+ /** Результат вызова исправил человек (версия 15, необязательное): показать отметку `labels.toolOutputEdited`. */
537
+ outputEdited?: boolean;
538
+ open: boolean;
539
+ onOpenChange: (open: boolean) => void;
540
+ }>;
541
+ /**
542
+ * Понятная ошибка вместо сырого текста провайдера; кнопка «повторить» — если передан `onRetry`. Кроме ошибок ответа
543
+ * приходят `signed_out` (вход истёк), `too_many_turns` (идут ответы в других чатах) и `forbidden_origin` (сервер не признал страницу своей); текст — `labels.errors[kind]`.
544
+ * `text` (версия 16, необязательное) — причина отказа словами сервера (4xx с текстом, до 300 знаков, `kind` — `other`):
545
+ * показать вместо `labels.errors[kind]` только как текст, без разметки. Скин версии 15 покажет общую надпись.
546
+ */
547
+ ErrorNotice: ComponentType<{
548
+ kind: ErrorNoticeKind;
549
+ text?: string;
550
+ onRetry?: () => void;
551
+ }>;
552
+ /**
553
+ * Состояние связи с сервером. `reconnecting` — тихая строка «Переподключаемся…», без кнопок;
554
+ * `offline` — «Нет связи с сервером» с «Повторить» (`onRetry` только у него).
555
+ */
556
+ ConnectionNotice: ComponentType<{
557
+ state: "reconnecting" | "offline";
558
+ onRetry?: () => void;
559
+ }>;
560
+ /**
561
+ * Разделитель на границе сжатой истории: сразу после последнего сообщения, которое заменила сводка.
562
+ * Свёрнутый — строка «История сжата» (`labels.historyCompacted`); раскрытый — ещё и `summary` в Markdown.
563
+ * `summary` пуст — сводка скрыта правами зрителя (`modelDetails: false`): раскрывать нечего, только строка.
564
+ */
565
+ CompactionDivider: ComponentType<{
566
+ summary: string;
567
+ open: boolean;
568
+ onOpenChange: (open: boolean) => void;
569
+ }>;
570
+ /**
571
+ * Лимит расходов (версия 8): `warning` — расход подошёл к лимиту, чат работает; `exhausted` — лимит исчерпан, поле
572
+ * ввода неактивно; `none` — причин нет, `text` пуст. `text` — готовая надпись из словаря. Рисуется рядом с полем ввода
573
+ * (в слоте `Layout.notice`) и вызывается всегда, когда включены лимиты: область для диктора (`role="status"`) должна
574
+ * быть в странице ещё до того, как в неё придёт текст, а при `none` — ничего не показывать и места не занимать.
575
+ * `id` повесить на этот элемент: на него ссылается `Composer.sendingBlockedReasonId`. Элемент фокусируемый программно
576
+ * (`tabIndex={-1}`): туда уходит фокус, когда поле ввода стало неактивным.
577
+ * Нет — надписи про лимит не будет, только неактивное поле; скину с лимитами компонент нужен.
578
+ */
579
+ SpendingNotice?: ComponentType<{
580
+ id: string;
581
+ state: "none" | "warning" | "exhausted";
582
+ text: string;
583
+ }>;
584
+ /**
585
+ * Чат закрыт (версия 20): `message` — слова приложения (до 300 знаков, только как текст), `code` — для оформления.
586
+ * Рисуется рядом с полем ввода (в слоте `Layout.notice`), пока чат закрыт; поле при этом неактивно. `id` повесить на
587
+ * элемент: на него ссылается `Composer.sendingBlockedReasonId`. Нет — слой поведения покажет `ErrorNotice` с текстом.
588
+ */
589
+ ChatClosedNotice?: ComponentType<{
590
+ id: string;
591
+ code: string;
592
+ message: string;
593
+ }>;
594
+ /** Вместо всего чата, если он чужой или его нет: без ленты, поля ввода и «повторить». */
595
+ ChatNotFound: ComponentType;
596
+ Composer: ComponentType<{
597
+ value: string;
598
+ onValueChange: (value: string) => void;
599
+ onSubmit: () => void;
600
+ /** `false`: пусто, одни пробелы или идёт ответ. */
601
+ canSubmit: boolean;
602
+ /** Идёт ответ. */
603
+ busy: boolean;
604
+ /**
605
+ * Поправка во время ответа (версия 21). Есть — идёт ответ, а поле принимает поправки: рядом со «Стоп» рисовать
606
+ * «Отправить», `onSubmit` отправит поправку; `true` — введённое можно отправить сейчас (как `canSubmit` вне ответа).
607
+ * Нет — во время ответа поле не отправляет (`canSubmit` — `false`).
608
+ */
609
+ canSteer?: boolean;
610
+ onStop: () => void;
611
+ /** Нет — рисовать поле без выбора. `providers` пуст — список грузится или не загрузился: меню не рисовать. */
612
+ modelChoice?: ModelChoiceView;
613
+ /**
614
+ * Вложения (версия 5). Есть — рисовать скрепку, превью с удалением, принимать перетаскивание и вставку файлов,
615
+ * показывать `notice`; отправка доступна и при пустом тексте (`canSubmit` уже учтено). Нет — вложений в приложении нет,
616
+ * ничего про них не рисовать.
617
+ */
618
+ attachments?: ComposerAttachments;
619
+ /** Нет — кнопку микрофона не рисовать. */
620
+ voice?: VoiceInputView;
621
+ /**
622
+ * Отправка остановлена (версия 8): поле сделать неактивным. Причину называет `SpendingNotice` (лимит исчерпан) или
623
+ * `ChatClosedNotice` (чат закрыт, версия 20).
624
+ */
625
+ sendingBlocked?: boolean;
626
+ /** `id` надписи, объясняющей остановку: повесить на поле как `aria-describedby`. Приходит вместе с `sendingBlocked`. */
627
+ sendingBlockedReasonId?: string;
628
+ /**
629
+ * Голосовой режим (версия 10). Есть — рисовать кнопку режима рядом с кнопкой диктовки и, пока `state !== "off"`,
630
+ * панель режима: состояние словами, меню голосов, выход, надпись ошибки; поле ввода скрыть или сделать неактивным
631
+ * (на телефоне без экранной клавиатуры). Нет — режима в приложении нет, ничего про него не рисовать.
632
+ */
633
+ dialog?: VoiceDialogView;
634
+ /**
635
+ * Кнопки приложения у поля ввода (версия 19): готовый узел, положить рядом с кнопками поля; в голосовом режиме
636
+ * оставить видимым. Нет — кнопок нет.
637
+ */
638
+ actions?: ReactNode;
639
+ }>;
640
+ /** Версия 13. Ждущий или отвеченный виджет в ленте. Нет — запасной вид в `ToolCallRow` с пометкой «ответьте сообщением». */
641
+ Widget?: ComponentType<WidgetView>;
642
+ /** Версия 13. Кнопки-подсказки под последним законченным ответом. Нет — подсказки не показываются. */
643
+ SuggestedReplies?: ComponentType<{
644
+ replies: string[];
645
+ onPick: (text: string) => void;
646
+ disabled: boolean;
647
+ }>;
648
+ /** Версия 13. Картинки агента. Нет — вызов `generate_images` остаётся строкой инструмента. */
649
+ GeneratedImages?: ComponentType<GeneratedImagesView>;
650
+ /** Версия 14. Видео агента: карточка, по нажатию — плеер. Нет — ссылка «▶ Название · с 3:20» через `AgentText`. */
651
+ AgentVideo?: ComponentType<AgentVideoView>;
652
+ /** Версия 17. Вызов помощника (`helper_<имя>`): задание, шаги, итог, подтверждение. Нет — обычная строка инструмента. */
653
+ HelperCallRow?: ComponentType<HelperCallView>;
654
+ /** Версия 17. Карточка фоновой задачи с «Отменить». Нет — задачи в ленте не показываются. */
655
+ TaskCard?: ComponentType<TaskCardView>;
656
+ /** Версия 17. Служебное сообщение: результат задачи, подтверждение, «агент написал первым». Нет — обычное сообщение. */
657
+ AgentNoticeView?: ComponentType<AgentNoticeViewProps>;
658
+ /** Версия 25. Реплика живого сотрудника. Нет — ответом агента с подписью «Сотрудник Ольга» (`labels.notices.staffSaid`). */
659
+ StaffMessage?: ComponentType<StaffMessageView>;
660
+ /** Версия 23. Простой вид работы агента: живая строка и свёрнутые шаги. Нет — то же строкой `ToolCallRow`. */
661
+ WorkSteps?: ComponentType<WorkStepsView>;
662
+ /** Версия 13. Окно просмотра картинок и схем. Нет — картинки и схемы не открываются. */
663
+ MediaViewer?: ComponentType<MediaViewerProps>;
664
+ /**
665
+ * Версия 21. `true` — скин рисует поправки на лету: `Composer.canSteer` («Отправить» рядом со «Стоп») и
666
+ * `UserMessage.pending` (пометка). Нет — во время ответа поле не отправляет, как до версии 21.
667
+ */
668
+ steering?: boolean;
669
+ /** Версия 13. Цвета схемы для светлой (`false`) и тёмной (`true`) темы. Нет — рисовальщик берёт свои. */
670
+ diagramColors?: (dark: boolean) => DiagramColors;
671
+ /** Версия 22. Цвета живых демонстраций для светлой (`false`) и тёмной (`true`) темы. Нет — свои спокойные серые. */
672
+ demoColors?: (dark: boolean) => LiveDemoColors;
673
+ }