@ykurilov/agent-chat-react 0.63.2 → 0.64.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.
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  MAX_LIVE_DEMO_BYTES,
8
8
  STEP_RESET_PART_TYPE,
9
9
  activeChatTitle,
10
+ customErrorText,
10
11
  englishLabels,
11
12
  englishWorkLabels,
12
13
  englishWorkViewLabels,
@@ -26,7 +27,7 @@ import {
26
27
  voiceAnswerIds,
27
28
  voiceErrorText,
28
29
  withoutResetSteps
29
- } from "./chunk-Y5HL6THS.js";
30
+ } from "./chunk-OAQ7TGUB.js";
30
31
 
31
32
  // src/chat-list/chat-workspace.tsx
32
33
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -479,6 +480,7 @@ export {
479
480
  MemoryRequestError,
480
481
  STEP_RESET_PART_TYPE,
481
482
  createMemoryApi,
483
+ customErrorText,
482
484
  englishLabels,
483
485
  englishWorkLabels,
484
486
  englishWorkViewLabels,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/chat-list/chat-workspace.tsx","../src/memory/agent-memory.tsx","../src/memory/memory-tree.ts","../src/memory/memory-types.ts","../src/memory/use-memory-notes.ts","../src/memory/memory-client.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { AgentChat } from \"../agent-chat\";\nimport type { AgentChatProps } from \"../agent-chat\";\nimport type { ChatAccess } from \"../chat-access\";\nimport { useChatLabels } from \"../labels\";\nimport type { ChatSkin } from \"../skin-contract\";\nimport type { ChatListSkin } from \"./chat-list-skin\";\nimport { useChatList } from \"./use-chat-list\";\nimport { activeChatTitle, useChatListView } from \"./use-chat-list-view\";\nimport { useWorkspaceListSync } from \"./use-workspace-list-sync\";\n\nexport interface ChatWorkspaceProps extends Omit<AgentChatProps, \"chatId\" | \"subject\" | \"skin\" | \"onBusyChange\" | \"onChatUnavailable\" | \"initialDraft\"> {\n /** Скин чата плюс скин списка (например, `tandemSkin`). */\n skin: ChatSkin & ChatListSkin;\n /** Какой чат открыть первым, например из адреса страницы. Главнее запомненного; чата нет — «Чат не найден». */\n initialChatId?: string;\n /** Открытый чат сменился (выбор, создание, удаление): например, чтобы поменять адрес страницы. */\n onChatChange?: (chatId: string | null) => void;\n}\n\n/**\n * Чат со списком чатов рядом: выбрать, создать, переименовать, удалить. Всё видимое рисует скин. У открытого чата\n * запрещено право `chatList` — списка нет, только чат: он не показывается и не обновляется, пока права открытого чата неизвестны\n * (до первой истории) и если запрещён.\n */\nexport function ChatWorkspace({ skin, initialChatId, onChatChange, onChatAccessChange, ...chatProps }: ChatWorkspaceProps) {\n const labels = useChatLabels();\n const { api } = chatProps;\n const list = useChatList({ api, ...(initialChatId !== undefined && { initialChatId }), ...(chatProps.onTurnRefused && { onCreateRefused: chatProps.onTurnRefused }) });\n const [listOpen, setListOpen] = useState(false);\n const [activeBusy, setActiveBusy] = useState(false);\n const { activeChatId } = list;\n /**\n * Список показан только когда известно, что он разрешён: права приходят с первой историей открытого чата, и список,\n * который тут же спрятали бы (`chatList: false`), не должен мигать. Выбор другого чата список не прячет: права нового\n * чата приходят позже, а список в это время остаётся на месте. Не открылся чат или сам список — список виден: из него\n * выбирают другой чат.\n */\n const [listAllowed, setListAllowed] = useState(false);\n const reportAccess = useRef(onChatAccessChange);\n reportAccess.current = onChatAccessChange;\n const onActiveAccessChange = useCallback((access: ChatAccess) => {\n setListAllowed(access.capabilities.chatList);\n reportAccess.current?.(access);\n }, []);\n const onActiveChatUnavailable = useCallback(() => setListAllowed(true), []);\n const listShown = listAllowed || (activeChatId === null && list.status !== \"loading\");\n\n const reportChange = useRef(onChatChange);\n reportChange.current = onChatChange;\n useEffect(() => {\n reportChange.current?.(activeChatId);\n }, [activeChatId]);\n\n // Список скрыт — его не запрашивают: ни при смене занятости, ни ради названия чата.\n useWorkspaceListSync(list, activeBusy, listShown);\n const closeList = useCallback(() => setListOpen(false), []);\n const listView = useChatListView(list, closeList);\n\n return (\n <skin.ChatWorkspace\n activeTitle={activeChatTitle(list, labels)}\n listOpen={listOpen && listShown}\n onListOpenChange={setListOpen}\n {...(listShown && { list: <skin.ChatList {...listView} /> })}\n >\n {activeChatId !== null && (\n <AgentChat key={activeChatId} {...chatProps} chatId={activeChatId} skin={skin} onBusyChange={setActiveBusy} onChatAccessChange={onActiveAccessChange} onChatUnavailable={onActiveChatUnavailable} />\n )}\n </skin.ChatWorkspace>\n );\n}\n","import { useCallback, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useChatLabels } from \"../labels\";\nimport type { ChatLabels } from \"../labels\";\nimport type { MemoryConfirmTarget, MemoryNoteEditorErrors, MemorySkin } from \"./memory-skin\";\nimport { buildMemoryTree } from \"./memory-tree\";\nimport type { MemoryTreeGroup } from \"./memory-tree\";\nimport { memoryNoteLimits } from \"./memory-types\";\nimport type { MemoryActionResult, MemoryApi, MemoryFailure, MemoryNote, MemoryNoteInput } from \"./memory-types\";\nimport { useMemoryNotes } from \"./use-memory-notes\";\n\nexport interface AgentMemoryProps {\n /** Обмен с сервером: `createMemoryApi({ api: \"/api/chat\" })`. Создайте один раз — при смене объекта список грузится заново. */\n api: MemoryApi;\n /** Скин экрана памяти (договор 11), например `tandemMemorySkin`. */\n skin: MemorySkin;\n}\n\ninterface Draft {\n /** `null` — новая запись. */\n id: string | null;\n path: string;\n content: string;\n}\n\ntype Confirm = { kind: \"note\"; id: string; content: string } | { kind: \"all\" };\n\nconst squeeze = (text: string) => text.replace(/\\s+/g, \" \").trim();\n\n/** Форма черновика → ввод для сервера либо ошибки полей. Пределы те же, что у пакета памяти. */\nfunction readDraft(draft: Draft, labels: ChatLabels): { input: MemoryNoteInput } | { errors: MemoryNoteEditorErrors } {\n const errors: MemoryNoteEditorErrors = {};\n const categories = draft.path.split(\"/\").map(squeeze).filter((part) => part !== \"\");\n if (categories.length === 0) errors.path = labels.memoryFieldErrors.pathRequired;\n else if (categories.length > memoryNoteLimits.maxCategories) errors.path = labels.memoryFieldErrors.pathTooMany;\n else if (categories.some((part) => part.length > memoryNoteLimits.maxCategoryChars)) errors.path = labels.memoryFieldErrors.pathTooLong;\n const content = squeeze(draft.content);\n if (content === \"\") errors.content = labels.memoryFieldErrors.contentRequired;\n else if (content.length > memoryNoteLimits.maxContentChars) errors.content = labels.memoryFieldErrors.contentTooLong;\n return errors.path || errors.content ? { errors } : { input: { categories, content } };\n}\n\n/**\n * Экран памяти агента: записи по разделам, добавление, правка, удаление, «Стереть всю память» и блок «Объединено» с кнопкой «Вернуть как было».\n * Всё видимое рисует скин; здесь — состояние, черновики и проверка ввода.\n */\nexport function AgentMemory({ api, skin }: AgentMemoryProps) {\n const labels = useChatLabels();\n const memory = useMemoryNotes(api);\n const { notes, edit: editNote, add: addNote, remove: removeNote, clear: clearNotes, revertMerge } = memory;\n const [draft, setDraft] = useState<Draft | null>(null);\n const [fieldErrors, setFieldErrors] = useState<MemoryNoteEditorErrors>({});\n const [confirm, setConfirm] = useState<Confirm | null>(null);\n const [confirmError, setConfirmError] = useState<string | undefined>();\n const [notice, setNotice] = useState<string | undefined>();\n const [pending, setPending] = useState(false);\n\n // Правка записи, которой уже нет в списке (её удалили в другом месте), закрывается сама.\n const editorOpen = draft !== null && (draft.id === null || notes.some((item) => item.id === draft.id));\n const failureText = (failure: MemoryFailure) => labels.memoryErrors[failure];\n\n const run = useCallback(async (action: () => Promise<MemoryActionResult>): Promise<MemoryActionResult> => {\n setPending(true);\n try {\n return await action();\n } finally {\n setPending(false);\n }\n }, []);\n\n const openAdd = () => {\n setNotice(undefined);\n setFieldErrors({});\n setDraft({ id: null, path: \"\", content: \"\" });\n };\n const openEdit = (note: MemoryNote) => {\n setNotice(undefined);\n setFieldErrors({});\n setDraft({ id: note.id, path: note.categories.join(\" / \"), content: note.content });\n };\n const closeEditor = () => {\n setDraft(null);\n setFieldErrors({});\n };\n const changeDraft = (change: Partial<Pick<Draft, \"path\" | \"content\">>) => {\n setDraft((current) => current && { ...current, ...change });\n setFieldErrors({});\n };\n\n const save = async () => {\n if (!draft) return;\n const read = readDraft(draft, labels);\n if (\"errors\" in read) {\n setFieldErrors(read.errors);\n return;\n }\n setFieldErrors({});\n const result = await run(() => (draft.id === null ? addNote(read.input) : editNote(draft.id, read.input)));\n if (result.ok) {\n setDraft(null);\n } else if (result.failure === \"not_found\" && draft.id !== null) {\n // Записи нет: форму закрываем, причину говорим на экране.\n setDraft(null);\n setNotice(failureText(\"not_found\"));\n } else {\n setFieldErrors({ form: failureText(result.failure) });\n }\n };\n\n const revert = async (runId: string) => {\n setNotice(undefined);\n const result = await run(() => revertMerge(runId));\n if (result.ok) return;\n const { failure } = result;\n setNotice(failure === \"limit_reached\" || failure === \"duplicate\" ? labels.memoryRevertErrors[failure] : failureText(failure));\n };\n\n const openConfirm = (next: Confirm) => {\n setNotice(undefined);\n setConfirmError(undefined);\n setConfirm(next);\n };\n const closeConfirm = () => {\n setConfirm(null);\n setConfirmError(undefined);\n };\n const confirmAction = async () => {\n if (!confirm) return;\n const result = await run(() => (confirm.kind === \"note\" ? removeNote(confirm.id) : clearNotes()));\n if (result.ok) {\n closeConfirm();\n } else if (result.failure === \"stale_version\" || result.failure === \"not_found\") {\n // Запись уже другая или её нет: список обновлён, человек решает заново.\n closeConfirm();\n setNotice(failureText(result.failure));\n } else {\n setConfirmError(failureText(result.failure));\n }\n };\n\n const confirmTarget: MemoryConfirmTarget | null =\n confirm === null ? null : confirm.kind === \"note\" ? { kind: \"note\", content: confirm.content } : { kind: \"all\", total: notes.length };\n\n const canChange = !pending;\n const renderEditor = (mode: \"create\" | \"edit\"): ReactNode =>\n draft && (\n <skin.MemoryNoteEditor\n mode={mode}\n path={draft.path}\n content={draft.content}\n onPathChange={(path) => changeDraft({ path })}\n onContentChange={(content) => changeDraft({ content })}\n onSave={() => void save()}\n onCancel={closeEditor}\n canSave={draft.path.trim() !== \"\" && draft.content.trim() !== \"\"}\n pending={pending}\n errors={fieldErrors}\n />\n );\n\n const renderNote = (note: MemoryNote): ReactNode =>\n editorOpen && draft?.id === note.id ? (\n <div key={note.id}>{renderEditor(\"edit\")}</div>\n ) : (\n <skin.MemoryNoteRow\n key={note.id}\n content={note.content}\n {...(canChange && { onEdit: () => openEdit(note), onDelete: () => openConfirm({ kind: \"note\", id: note.id, content: note.content }) })}\n />\n );\n\n const renderGroup = (group: MemoryTreeGroup, depth: number): ReactNode => (\n <skin.MemoryGroup key={group.key} name={group.name} depth={depth} count={group.count}>\n {group.notes.map(renderNote)}\n {group.groups.map((inner) => renderGroup(inner, depth + 1))}\n </skin.MemoryGroup>\n );\n\n const renderMerges = (): ReactNode =>\n memory.merges.map((merge) => (\n <skin.MemoryMerge\n key={merge.runId}\n finishedAt={merge.finishedAt}\n groups={merge.groups.map((item) => ({ before: item.originals.map((original) => original.content), after: item.result.content }))}\n revertible={merge.revertible}\n {...(merge.revertible && canChange && { onRevert: () => void revert(merge.runId) })}\n />\n ));\n\n const usable = memory.state === \"ready\" || memory.state === \"empty\";\n return (\n <skin.MemoryScreen\n status={memory.state}\n {...(memory.counts && { counts: memory.counts })}\n {...(usable && !editorOpen && { onAdd: openAdd })}\n {...(memory.state === \"ready\" && canChange && { onClearAll: () => openConfirm({ kind: \"all\" }) })}\n {...((memory.state === \"error\") && { onRetry: memory.reload })}\n {...(notice && { error: notice })}\n {...(editorOpen && draft?.id === null && { newNoteEditor: renderEditor(\"create\") })}\n {...(memory.state === \"ready\" && memory.merges.length > 0 && { merges: renderMerges() })}\n {...(confirmTarget && {\n confirm: (\n <skin.MemoryConfirm\n target={confirmTarget}\n pending={pending}\n {...(confirmError && { error: confirmError })}\n onConfirm={() => void confirmAction()}\n onCancel={closeConfirm}\n />\n ),\n })}\n >\n {memory.state === \"ready\" && buildMemoryTree(notes).map((group) => renderGroup(group, 0))}\n </skin.MemoryScreen>\n );\n}\n","import type { MemoryNote } from \"./memory-types\";\n\n/** Раздел дерева: записи, лежащие прямо в нём, и вложенные разделы. */\nexport interface MemoryTreeGroup {\n name: string;\n /** Путь от верхнего раздела, имена в нижнем регистре: годится в ключ. */\n key: string;\n notes: MemoryNote[];\n groups: MemoryTreeGroup[];\n /** Записей в разделе вместе с вложенными. */\n count: number;\n}\n\n/**\n * Раскладывает записи по разделам: запись с разделами `[\"О себе\", \"Работа\"]` лежит в «Работа» внутри «О себе».\n * Разделы, различающиеся только регистром, сливаются (имя берётся у первой записи); разделы идут по алфавиту,\n * записи — в порядке списка.\n */\nexport function buildMemoryTree(notes: readonly MemoryNote[]): MemoryTreeGroup[] {\n const roots: MemoryTreeGroup[] = [];\n for (const note of notes) {\n let level = roots;\n let key = \"\";\n note.categories.forEach((name, index) => {\n key = `${key}/${name.trim().toLowerCase()}`;\n let group = level.find((candidate) => candidate.key === key);\n if (!group) {\n group = { name: name.trim(), key, notes: [], groups: [], count: 0 };\n level.push(group);\n }\n group.count += 1;\n if (index === note.categories.length - 1) group.notes.push(note);\n level = group.groups;\n });\n }\n return sortGroups(roots);\n}\n\nfunction sortGroups(groups: MemoryTreeGroup[]): MemoryTreeGroup[] {\n for (const group of groups) sortGroups(group.groups);\n return groups.sort((a, b) => a.name.localeCompare(b.name, \"ru\"));\n}\n","/** Запись памяти, как её отдаёт сервер (`GET {api}/memory`). Владельца в ответах нет. */\nexport interface MemoryNote {\n id: string;\n /** Раздел записи от общего к частному: от 1 до 3 уровней. */\n categories: string[];\n content: string;\n /** Замок: правка и удаление называют версию, которую человек видел. */\n version: number;\n createdAt: string;\n updatedAt: string;\n}\n\n/** То, что человек вводит: разделы и текст. */\nexport interface MemoryNoteInput {\n categories: string[];\n content: string;\n}\n\nexport interface MemoryCounts {\n /** Записей всего. */\n total: number;\n /** Предел записей. */\n limit: number;\n /** Сколько записей видит агент в каждом запросе (остальные он находит поиском). */\n shown: number;\n}\n\n/** Одна склейка повторов: `originals` («было») заменены записью `result` («стало»). */\nexport interface MemoryMergeGroup {\n result: MemoryNote;\n originals: MemoryNote[];\n}\n\n/** Слияние одного запуска оптимизации (`GET {api}/memory/merges`), новейшие первыми. */\nexport interface MemoryMerge {\n runId: string;\n /** Время в формате ISO 8601. */\n finishedAt: string;\n /** Можно ли «Вернуть как было»: результаты склеек с тех пор не менялись. */\n revertible: boolean;\n groups: MemoryMergeGroup[];\n}\n\n/**\n * Обмен с сервером памяти. Каждый метод при отказе бросает `MemoryRequestError`\n * (обрыв связи — обычная ошибка `fetch`). `createMemoryApi` строит его по адресам `{api}/memory`.\n */\nexport interface MemoryApi {\n list(): Promise<{ notes: MemoryNote[]; counts: MemoryCounts }>;\n create(input: MemoryNoteInput): Promise<MemoryNote>;\n update(id: string, version: number, input: MemoryNoteInput): Promise<MemoryNote>;\n remove(id: string, version: number): Promise<void>;\n /** Стирает всю память человека; возвращает, сколько записей удалено. */\n clear(): Promise<number>;\n /** Склейки повторов, которые можно показать и вернуть. */\n listMerges(): Promise<MemoryMerge[]>;\n /** Возвращает слияние как было. Отказ 409: `conflict` — результат с тех пор менялся; `limit_reached` — памяти не хватит места; `duplicate` — запись, что вернулась бы, уже добавлена заново. */\n revertMerge(runId: string): Promise<void>;\n}\n\n/** Что пошло не так с действием: коды сервера (`conflict` — слияние вернуть нельзя) плюс `unavailable` (памяти нет) и `other` (сбой, обрыв связи). */\nexport type MemoryFailure = \"not_found\" | \"stale_version\" | \"limit_reached\" | \"duplicate\" | \"invalid\" | \"conflict\" | \"unavailable\" | \"other\";\n\n/** Итог действия. Отказ — не исключение: скину нужна надпись, а не стек. */\nexport type MemoryActionResult = { ok: true } | { ok: false; failure: MemoryFailure };\n\n/**\n * Состояние экрана: `loading` — грузится; `ready` — записи есть; `empty` — записей нет; `unavailable` — сервер ответил\n * 404 (память в приложении не включена); `error` — список не загрузился.\n */\nexport type MemoryStatus = \"loading\" | \"ready\" | \"empty\" | \"unavailable\" | \"error\";\n\n/** Форма записи (как у пакета памяти): до 3 разделов по 60 знаков, текст до 500. Экран проверяет её до отправки. */\nexport const memoryNoteLimits = { maxCategories: 3, maxCategoryChars: 60, maxContentChars: 500 } as const;\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { MemoryRequestError } from \"./memory-client\";\nimport type { MemoryActionResult, MemoryApi, MemoryCounts, MemoryFailure, MemoryMerge, MemoryNote, MemoryNoteInput, MemoryStatus } from \"./memory-types\";\n\nexport interface MemoryNotesState {\n state: MemoryStatus;\n /** Записи в порядке сервера: новейшие первыми. */\n notes: MemoryNote[];\n /** `null`, пока список не загружен. */\n counts: MemoryCounts | null;\n /** Склейки повторов, новейшие первыми; пусто, если их нет или сервер их не отдаёт. */\n merges: MemoryMerge[];\n /** Как закончилось последнее действие, если отказом; следующее действие сбрасывает. */\n error: MemoryFailure | null;\n add(input: MemoryNoteInput): Promise<MemoryActionResult>;\n /** Уходит версия записи из списка. Конфликт версий: список перезагружен, вызывающий держит свои значения и может повторить. */\n edit(id: string, input: MemoryNoteInput): Promise<MemoryActionResult>;\n remove(id: string): Promise<MemoryActionResult>;\n clear(): Promise<MemoryActionResult>;\n /** «Вернуть как было»: оригиналы возвращаются вместо результата, список записей и склеек загружается заново. */\n revertMerge(runId: string): Promise<MemoryActionResult>;\n /** Загружает список заново: после отказа — с экраном загрузки, иначе тихо. */\n reload(): void;\n}\n\nconst knownKinds = new Set<string>([\"not_found\", \"stale_version\", \"limit_reached\", \"duplicate\", \"invalid\", \"conflict\"]);\n\n/** Отказ действия по ответу сервера. 404 без `not_found` в теле — адреса памяти нет совсем. */\nfunction failureOf(error: unknown): MemoryFailure {\n if (!(error instanceof MemoryRequestError)) return \"other\";\n if (error.kind !== undefined && knownKinds.has(error.kind)) return error.kind as MemoryFailure;\n return error.status === 404 ? \"unavailable\" : \"other\";\n}\n\n/** Состояние экрана памяти: загрузка, добавление, правка, удаление, стирание. Ничего не рисует. */\nexport function useMemoryNotes(api: MemoryApi): MemoryNotesState {\n const [status, setStatus] = useState<MemoryStatus>(\"loading\");\n const [notes, setNotes] = useState<MemoryNote[]>([]);\n const [counts, setCounts] = useState<MemoryCounts | null>(null);\n const [merges, setMerges] = useState<MemoryMerge[]>([]);\n const [error, setError] = useState<MemoryFailure | null>(null);\n\n const apiRef = useRef(api);\n apiRef.current = api;\n const notesRef = useRef(notes);\n notesRef.current = notes;\n const statusRef = useRef(status);\n statusRef.current = status;\n const countsRef = useRef(counts);\n countsRef.current = counts;\n /** Номер последнего запроса списка: ответ более раннего не затирает свежий. */\n const listRequest = useRef(0);\n const alive = useRef(true);\n useEffect(() => {\n alive.current = true;\n return () => {\n alive.current = false;\n };\n }, []);\n\n const show = useCallback((next: MemoryNote[], nextCounts: MemoryCounts | null) => {\n notesRef.current = next;\n setNotes(next);\n setCounts(nextCounts);\n setStatus(next.length === 0 ? \"empty\" : \"ready\");\n }, []);\n\n /** `quiet`: список уже на экране, экран загрузки не нужен, отказ не ломает то, что показано. */\n const load = useCallback(\n async (quiet: boolean) => {\n const request = ++listRequest.current;\n if (!quiet) setStatus(\"loading\");\n try {\n // Склейки — дополнение к списку: их сбой (или сервер без склеек) записи не ломает, прежние склейки остаются.\n const [loaded, loadedMerges] = await Promise.all([apiRef.current.list(), apiRef.current.listMerges().catch(() => null)]);\n if (!alive.current || request !== listRequest.current) return;\n show(loaded.notes, loaded.counts);\n if (loadedMerges) setMerges(loadedMerges);\n } catch (failure) {\n if (!alive.current || request !== listRequest.current || quiet) return;\n setStatus(failureOf(failure) === \"unavailable\" ? \"unavailable\" : \"error\");\n }\n },\n [show],\n );\n\n useEffect(() => {\n void load(false);\n }, [api, load]);\n\n const reload = useCallback(() => {\n void load(statusRef.current === \"ready\" || statusRef.current === \"empty\");\n }, [load]);\n\n /** Действие с отказом: его причина остаётся в `error`; при конфликте список заново. */\n const refuse = useCallback(\n async (failure: MemoryFailure): Promise<MemoryActionResult> => {\n if (failure === \"stale_version\" || failure === \"not_found\" || failure === \"conflict\") await load(true);\n if (alive.current) setError(failure);\n return { ok: false, failure };\n },\n [load],\n );\n\n const succeed = useCallback(\n (next: MemoryNote[]): MemoryActionResult => {\n show(next, countsRef.current === null ? null : { ...countsRef.current, total: next.length });\n // Счётчик «показано агенту» считает сервер: тихо спрашиваем его после изменения.\n void load(true);\n return { ok: true };\n },\n [load, show],\n );\n\n const add = useCallback(\n async (input: MemoryNoteInput): Promise<MemoryActionResult> => {\n setError(null);\n try {\n const created = await apiRef.current.create(input);\n return alive.current ? succeed([created, ...notesRef.current]) : { ok: true };\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n },\n [refuse, succeed],\n );\n\n const edit = useCallback(\n async (id: string, input: MemoryNoteInput): Promise<MemoryActionResult> => {\n setError(null);\n const current = notesRef.current.find((item) => item.id === id);\n if (!current) return refuse(\"not_found\");\n try {\n const saved = await apiRef.current.update(id, current.version, input);\n return alive.current ? succeed([saved, ...notesRef.current.filter((item) => item.id !== id)]) : { ok: true };\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n },\n [refuse, succeed],\n );\n\n const remove = useCallback(\n async (id: string): Promise<MemoryActionResult> => {\n setError(null);\n const current = notesRef.current.find((item) => item.id === id);\n if (!current) return refuse(\"not_found\");\n try {\n await apiRef.current.remove(id, current.version);\n } catch (failure) {\n const kind = failureOf(failure);\n // Записи уже нет — человек этого и хотел.\n if (kind !== \"not_found\") return refuse(kind);\n }\n return alive.current ? succeed(notesRef.current.filter((item) => item.id !== id)) : { ok: true };\n },\n [refuse, succeed],\n );\n\n const clear = useCallback(async (): Promise<MemoryActionResult> => {\n setError(null);\n try {\n await apiRef.current.clear();\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n return alive.current ? succeed([]) : { ok: true };\n }, [refuse, succeed]);\n\n const revertMerge = useCallback(\n async (runId: string): Promise<MemoryActionResult> => {\n setError(null);\n try {\n await apiRef.current.revertMerge(runId);\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n if (!alive.current) return { ok: true };\n await load(true);\n return { ok: true };\n },\n [load, refuse],\n );\n\n return { state: status, notes, counts, merges, error, add, edit, remove, clear, revertMerge, reload };\n}\n","import type { MemoryApi, MemoryCounts, MemoryMerge, MemoryNote, MemoryNoteInput } from \"./memory-types\";\n\ntype Fetch = typeof globalThis.fetch;\n\n/** Сервер ответил отказом; `status` — код ответа, `kind` — `error.kind` из тела, если оно было (404 без тела — памяти нет). */\nexport class MemoryRequestError extends Error {\n constructor(\n readonly status: number,\n readonly kind: string | undefined,\n action: string,\n ) {\n super(`Memory request failed (${action}): HTTP ${status}${kind ? ` ${kind}` : \"\"}`);\n this.name = \"MemoryRequestError\";\n }\n}\n\nconst jsonBody = (body: unknown): RequestInit => ({\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n});\n\nasync function readKind(response: Response): Promise<string | undefined> {\n try {\n const body = (await response.json()) as { error?: { kind?: unknown } };\n return typeof body.error?.kind === \"string\" ? body.error.kind : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Клиент адресов памяти сервера: `GET/POST {api}/memory`, `PATCH/DELETE {api}/memory/{id}`, `DELETE {api}/memory`,\n * склейки повторов — `GET {api}/memory/merges` и `POST {api}/memory/merges/{runId}/revert`.\n * `api` — тот же адрес обработчика чата, например `/api/chat`. Вызывайте один раз (например, в `useMemo`):\n * экран загружает список заново при смене объекта.\n */\nexport function createMemoryApi({ api, fetch: customFetch }: { api: string; fetch?: Fetch }): MemoryApi {\n const base = `${api.replace(/\\/+$/, \"\")}/memory`;\n const noteUrl = (id: string) => `${base}/${encodeURIComponent(id)}`;\n\n async function request(action: string, url: string, init?: RequestInit): Promise<Response> {\n const response = await (customFetch ?? globalThis.fetch)(url, init);\n if (!response.ok) throw new MemoryRequestError(response.status, await readKind(response), action);\n return response;\n }\n\n return {\n async list() {\n const response = await request(\"list\", base);\n return (await response.json()) as { notes: MemoryNote[]; counts: MemoryCounts };\n },\n async create(input: MemoryNoteInput) {\n const response = await request(\"create\", base, { method: \"POST\", ...jsonBody(input) });\n return ((await response.json()) as { note: MemoryNote }).note;\n },\n async update(id, version, input) {\n const response = await request(\"update\", noteUrl(id), { method: \"PATCH\", ...jsonBody({ ...input, version }) });\n return ((await response.json()) as { note: MemoryNote }).note;\n },\n async remove(id, version) {\n await request(\"remove\", `${noteUrl(id)}?version=${version}`, { method: \"DELETE\" });\n },\n async clear() {\n const response = await request(\"clear\", base, { method: \"DELETE\" });\n return ((await response.json()) as { deleted: number }).deleted;\n },\n async listMerges() {\n const response = await request(\"listMerges\", `${base}/merges`);\n return ((await response.json()) as { merges?: MemoryMerge[] }).merges ?? [];\n },\n async revertMerge(runId) {\n await request(\"revertMerge\", `${base}/merges/${encodeURIComponent(runId)}/revert`, { method: \"POST\" });\n },\n };\n}\n"],"mappings":"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","names":["useCallback","useState","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","jsx","useState","useCallback"]}
1
+ {"version":3,"sources":["../src/chat-list/chat-workspace.tsx","../src/memory/agent-memory.tsx","../src/memory/memory-tree.ts","../src/memory/memory-types.ts","../src/memory/use-memory-notes.ts","../src/memory/memory-client.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { AgentChat } from \"../agent-chat\";\nimport type { AgentChatProps } from \"../agent-chat\";\nimport type { ChatAccess } from \"../chat-access\";\nimport { useChatLabels } from \"../labels\";\nimport type { ChatSkin } from \"../skin-contract\";\nimport type { ChatListSkin } from \"./chat-list-skin\";\nimport { useChatList } from \"./use-chat-list\";\nimport { activeChatTitle, useChatListView } from \"./use-chat-list-view\";\nimport { useWorkspaceListSync } from \"./use-workspace-list-sync\";\n\nexport interface ChatWorkspaceProps extends Omit<AgentChatProps, \"chatId\" | \"subject\" | \"skin\" | \"onBusyChange\" | \"onChatUnavailable\" | \"initialDraft\"> {\n /** Скин чата плюс скин списка (например, `tandemSkin`). */\n skin: ChatSkin & ChatListSkin;\n /** Какой чат открыть первым, например из адреса страницы. Главнее запомненного; чата нет — «Чат не найден». */\n initialChatId?: string;\n /** Открытый чат сменился (выбор, создание, удаление): например, чтобы поменять адрес страницы. */\n onChatChange?: (chatId: string | null) => void;\n}\n\n/**\n * Чат со списком чатов рядом: выбрать, создать, переименовать, удалить. Всё видимое рисует скин. У открытого чата\n * запрещено право `chatList` — списка нет, только чат: он не показывается и не обновляется, пока права открытого чата неизвестны\n * (до первой истории) и если запрещён.\n */\nexport function ChatWorkspace({ skin, initialChatId, onChatChange, onChatAccessChange, ...chatProps }: ChatWorkspaceProps) {\n const labels = useChatLabels();\n const { api } = chatProps;\n const list = useChatList({ api, ...(initialChatId !== undefined && { initialChatId }), ...(chatProps.onTurnRefused && { onCreateRefused: chatProps.onTurnRefused }) });\n const [listOpen, setListOpen] = useState(false);\n const [activeBusy, setActiveBusy] = useState(false);\n const { activeChatId } = list;\n /**\n * Список показан только когда известно, что он разрешён: права приходят с первой историей открытого чата, и список,\n * который тут же спрятали бы (`chatList: false`), не должен мигать. Выбор другого чата список не прячет: права нового\n * чата приходят позже, а список в это время остаётся на месте. Не открылся чат или сам список — список виден: из него\n * выбирают другой чат.\n */\n const [listAllowed, setListAllowed] = useState(false);\n const reportAccess = useRef(onChatAccessChange);\n reportAccess.current = onChatAccessChange;\n const onActiveAccessChange = useCallback((access: ChatAccess) => {\n setListAllowed(access.capabilities.chatList);\n reportAccess.current?.(access);\n }, []);\n const onActiveChatUnavailable = useCallback(() => setListAllowed(true), []);\n const listShown = listAllowed || (activeChatId === null && list.status !== \"loading\");\n\n const reportChange = useRef(onChatChange);\n reportChange.current = onChatChange;\n useEffect(() => {\n reportChange.current?.(activeChatId);\n }, [activeChatId]);\n\n // Список скрыт — его не запрашивают: ни при смене занятости, ни ради названия чата.\n useWorkspaceListSync(list, activeBusy, listShown);\n const closeList = useCallback(() => setListOpen(false), []);\n const listView = useChatListView(list, closeList);\n\n return (\n <skin.ChatWorkspace\n activeTitle={activeChatTitle(list, labels)}\n listOpen={listOpen && listShown}\n onListOpenChange={setListOpen}\n {...(listShown && { list: <skin.ChatList {...listView} /> })}\n >\n {activeChatId !== null && (\n <AgentChat key={activeChatId} {...chatProps} chatId={activeChatId} skin={skin} onBusyChange={setActiveBusy} onChatAccessChange={onActiveAccessChange} onChatUnavailable={onActiveChatUnavailable} />\n )}\n </skin.ChatWorkspace>\n );\n}\n","import { useCallback, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useChatLabels } from \"../labels\";\nimport type { ChatLabels } from \"../labels\";\nimport type { MemoryConfirmTarget, MemoryNoteEditorErrors, MemorySkin } from \"./memory-skin\";\nimport { buildMemoryTree } from \"./memory-tree\";\nimport type { MemoryTreeGroup } from \"./memory-tree\";\nimport { memoryNoteLimits } from \"./memory-types\";\nimport type { MemoryActionResult, MemoryApi, MemoryFailure, MemoryNote, MemoryNoteInput } from \"./memory-types\";\nimport { useMemoryNotes } from \"./use-memory-notes\";\n\nexport interface AgentMemoryProps {\n /** Обмен с сервером: `createMemoryApi({ api: \"/api/chat\" })`. Создайте один раз — при смене объекта список грузится заново. */\n api: MemoryApi;\n /** Скин экрана памяти (договор 11), например `tandemMemorySkin`. */\n skin: MemorySkin;\n}\n\ninterface Draft {\n /** `null` — новая запись. */\n id: string | null;\n path: string;\n content: string;\n}\n\ntype Confirm = { kind: \"note\"; id: string; content: string } | { kind: \"all\" };\n\nconst squeeze = (text: string) => text.replace(/\\s+/g, \" \").trim();\n\n/** Форма черновика → ввод для сервера либо ошибки полей. Пределы те же, что у пакета памяти. */\nfunction readDraft(draft: Draft, labels: ChatLabels): { input: MemoryNoteInput } | { errors: MemoryNoteEditorErrors } {\n const errors: MemoryNoteEditorErrors = {};\n const categories = draft.path.split(\"/\").map(squeeze).filter((part) => part !== \"\");\n if (categories.length === 0) errors.path = labels.memoryFieldErrors.pathRequired;\n else if (categories.length > memoryNoteLimits.maxCategories) errors.path = labels.memoryFieldErrors.pathTooMany;\n else if (categories.some((part) => part.length > memoryNoteLimits.maxCategoryChars)) errors.path = labels.memoryFieldErrors.pathTooLong;\n const content = squeeze(draft.content);\n if (content === \"\") errors.content = labels.memoryFieldErrors.contentRequired;\n else if (content.length > memoryNoteLimits.maxContentChars) errors.content = labels.memoryFieldErrors.contentTooLong;\n return errors.path || errors.content ? { errors } : { input: { categories, content } };\n}\n\n/**\n * Экран памяти агента: записи по разделам, добавление, правка, удаление, «Стереть всю память» и блок «Объединено» с кнопкой «Вернуть как было».\n * Всё видимое рисует скин; здесь — состояние, черновики и проверка ввода.\n */\nexport function AgentMemory({ api, skin }: AgentMemoryProps) {\n const labels = useChatLabels();\n const memory = useMemoryNotes(api);\n const { notes, edit: editNote, add: addNote, remove: removeNote, clear: clearNotes, revertMerge } = memory;\n const [draft, setDraft] = useState<Draft | null>(null);\n const [fieldErrors, setFieldErrors] = useState<MemoryNoteEditorErrors>({});\n const [confirm, setConfirm] = useState<Confirm | null>(null);\n const [confirmError, setConfirmError] = useState<string | undefined>();\n const [notice, setNotice] = useState<string | undefined>();\n const [pending, setPending] = useState(false);\n\n // Правка записи, которой уже нет в списке (её удалили в другом месте), закрывается сама.\n const editorOpen = draft !== null && (draft.id === null || notes.some((item) => item.id === draft.id));\n const failureText = (failure: MemoryFailure) => labels.memoryErrors[failure];\n\n const run = useCallback(async (action: () => Promise<MemoryActionResult>): Promise<MemoryActionResult> => {\n setPending(true);\n try {\n return await action();\n } finally {\n setPending(false);\n }\n }, []);\n\n const openAdd = () => {\n setNotice(undefined);\n setFieldErrors({});\n setDraft({ id: null, path: \"\", content: \"\" });\n };\n const openEdit = (note: MemoryNote) => {\n setNotice(undefined);\n setFieldErrors({});\n setDraft({ id: note.id, path: note.categories.join(\" / \"), content: note.content });\n };\n const closeEditor = () => {\n setDraft(null);\n setFieldErrors({});\n };\n const changeDraft = (change: Partial<Pick<Draft, \"path\" | \"content\">>) => {\n setDraft((current) => current && { ...current, ...change });\n setFieldErrors({});\n };\n\n const save = async () => {\n if (!draft) return;\n const read = readDraft(draft, labels);\n if (\"errors\" in read) {\n setFieldErrors(read.errors);\n return;\n }\n setFieldErrors({});\n const result = await run(() => (draft.id === null ? addNote(read.input) : editNote(draft.id, read.input)));\n if (result.ok) {\n setDraft(null);\n } else if (result.failure === \"not_found\" && draft.id !== null) {\n // Записи нет: форму закрываем, причину говорим на экране.\n setDraft(null);\n setNotice(failureText(\"not_found\"));\n } else {\n setFieldErrors({ form: failureText(result.failure) });\n }\n };\n\n const revert = async (runId: string) => {\n setNotice(undefined);\n const result = await run(() => revertMerge(runId));\n if (result.ok) return;\n const { failure } = result;\n setNotice(failure === \"limit_reached\" || failure === \"duplicate\" ? labels.memoryRevertErrors[failure] : failureText(failure));\n };\n\n const openConfirm = (next: Confirm) => {\n setNotice(undefined);\n setConfirmError(undefined);\n setConfirm(next);\n };\n const closeConfirm = () => {\n setConfirm(null);\n setConfirmError(undefined);\n };\n const confirmAction = async () => {\n if (!confirm) return;\n const result = await run(() => (confirm.kind === \"note\" ? removeNote(confirm.id) : clearNotes()));\n if (result.ok) {\n closeConfirm();\n } else if (result.failure === \"stale_version\" || result.failure === \"not_found\") {\n // Запись уже другая или её нет: список обновлён, человек решает заново.\n closeConfirm();\n setNotice(failureText(result.failure));\n } else {\n setConfirmError(failureText(result.failure));\n }\n };\n\n const confirmTarget: MemoryConfirmTarget | null =\n confirm === null ? null : confirm.kind === \"note\" ? { kind: \"note\", content: confirm.content } : { kind: \"all\", total: notes.length };\n\n const canChange = !pending;\n const renderEditor = (mode: \"create\" | \"edit\"): ReactNode =>\n draft && (\n <skin.MemoryNoteEditor\n mode={mode}\n path={draft.path}\n content={draft.content}\n onPathChange={(path) => changeDraft({ path })}\n onContentChange={(content) => changeDraft({ content })}\n onSave={() => void save()}\n onCancel={closeEditor}\n canSave={draft.path.trim() !== \"\" && draft.content.trim() !== \"\"}\n pending={pending}\n errors={fieldErrors}\n />\n );\n\n const renderNote = (note: MemoryNote): ReactNode =>\n editorOpen && draft?.id === note.id ? (\n <div key={note.id}>{renderEditor(\"edit\")}</div>\n ) : (\n <skin.MemoryNoteRow\n key={note.id}\n content={note.content}\n {...(canChange && { onEdit: () => openEdit(note), onDelete: () => openConfirm({ kind: \"note\", id: note.id, content: note.content }) })}\n />\n );\n\n const renderGroup = (group: MemoryTreeGroup, depth: number): ReactNode => (\n <skin.MemoryGroup key={group.key} name={group.name} depth={depth} count={group.count}>\n {group.notes.map(renderNote)}\n {group.groups.map((inner) => renderGroup(inner, depth + 1))}\n </skin.MemoryGroup>\n );\n\n const renderMerges = (): ReactNode =>\n memory.merges.map((merge) => (\n <skin.MemoryMerge\n key={merge.runId}\n finishedAt={merge.finishedAt}\n groups={merge.groups.map((item) => ({ before: item.originals.map((original) => original.content), after: item.result.content }))}\n revertible={merge.revertible}\n {...(merge.revertible && canChange && { onRevert: () => void revert(merge.runId) })}\n />\n ));\n\n const usable = memory.state === \"ready\" || memory.state === \"empty\";\n return (\n <skin.MemoryScreen\n status={memory.state}\n {...(memory.counts && { counts: memory.counts })}\n {...(usable && !editorOpen && { onAdd: openAdd })}\n {...(memory.state === \"ready\" && canChange && { onClearAll: () => openConfirm({ kind: \"all\" }) })}\n {...((memory.state === \"error\") && { onRetry: memory.reload })}\n {...(notice && { error: notice })}\n {...(editorOpen && draft?.id === null && { newNoteEditor: renderEditor(\"create\") })}\n {...(memory.state === \"ready\" && memory.merges.length > 0 && { merges: renderMerges() })}\n {...(confirmTarget && {\n confirm: (\n <skin.MemoryConfirm\n target={confirmTarget}\n pending={pending}\n {...(confirmError && { error: confirmError })}\n onConfirm={() => void confirmAction()}\n onCancel={closeConfirm}\n />\n ),\n })}\n >\n {memory.state === \"ready\" && buildMemoryTree(notes).map((group) => renderGroup(group, 0))}\n </skin.MemoryScreen>\n );\n}\n","import type { MemoryNote } from \"./memory-types\";\n\n/** Раздел дерева: записи, лежащие прямо в нём, и вложенные разделы. */\nexport interface MemoryTreeGroup {\n name: string;\n /** Путь от верхнего раздела, имена в нижнем регистре: годится в ключ. */\n key: string;\n notes: MemoryNote[];\n groups: MemoryTreeGroup[];\n /** Записей в разделе вместе с вложенными. */\n count: number;\n}\n\n/**\n * Раскладывает записи по разделам: запись с разделами `[\"О себе\", \"Работа\"]` лежит в «Работа» внутри «О себе».\n * Разделы, различающиеся только регистром, сливаются (имя берётся у первой записи); разделы идут по алфавиту,\n * записи — в порядке списка.\n */\nexport function buildMemoryTree(notes: readonly MemoryNote[]): MemoryTreeGroup[] {\n const roots: MemoryTreeGroup[] = [];\n for (const note of notes) {\n let level = roots;\n let key = \"\";\n note.categories.forEach((name, index) => {\n key = `${key}/${name.trim().toLowerCase()}`;\n let group = level.find((candidate) => candidate.key === key);\n if (!group) {\n group = { name: name.trim(), key, notes: [], groups: [], count: 0 };\n level.push(group);\n }\n group.count += 1;\n if (index === note.categories.length - 1) group.notes.push(note);\n level = group.groups;\n });\n }\n return sortGroups(roots);\n}\n\nfunction sortGroups(groups: MemoryTreeGroup[]): MemoryTreeGroup[] {\n for (const group of groups) sortGroups(group.groups);\n return groups.sort((a, b) => a.name.localeCompare(b.name, \"ru\"));\n}\n","/** Запись памяти, как её отдаёт сервер (`GET {api}/memory`). Владельца в ответах нет. */\nexport interface MemoryNote {\n id: string;\n /** Раздел записи от общего к частному: от 1 до 3 уровней. */\n categories: string[];\n content: string;\n /** Замок: правка и удаление называют версию, которую человек видел. */\n version: number;\n createdAt: string;\n updatedAt: string;\n}\n\n/** То, что человек вводит: разделы и текст. */\nexport interface MemoryNoteInput {\n categories: string[];\n content: string;\n}\n\nexport interface MemoryCounts {\n /** Записей всего. */\n total: number;\n /** Предел записей. */\n limit: number;\n /** Сколько записей видит агент в каждом запросе (остальные он находит поиском). */\n shown: number;\n}\n\n/** Одна склейка повторов: `originals` («было») заменены записью `result` («стало»). */\nexport interface MemoryMergeGroup {\n result: MemoryNote;\n originals: MemoryNote[];\n}\n\n/** Слияние одного запуска оптимизации (`GET {api}/memory/merges`), новейшие первыми. */\nexport interface MemoryMerge {\n runId: string;\n /** Время в формате ISO 8601. */\n finishedAt: string;\n /** Можно ли «Вернуть как было»: результаты склеек с тех пор не менялись. */\n revertible: boolean;\n groups: MemoryMergeGroup[];\n}\n\n/**\n * Обмен с сервером памяти. Каждый метод при отказе бросает `MemoryRequestError`\n * (обрыв связи — обычная ошибка `fetch`). `createMemoryApi` строит его по адресам `{api}/memory`.\n */\nexport interface MemoryApi {\n list(): Promise<{ notes: MemoryNote[]; counts: MemoryCounts }>;\n create(input: MemoryNoteInput): Promise<MemoryNote>;\n update(id: string, version: number, input: MemoryNoteInput): Promise<MemoryNote>;\n remove(id: string, version: number): Promise<void>;\n /** Стирает всю память человека; возвращает, сколько записей удалено. */\n clear(): Promise<number>;\n /** Склейки повторов, которые можно показать и вернуть. */\n listMerges(): Promise<MemoryMerge[]>;\n /** Возвращает слияние как было. Отказ 409: `conflict` — результат с тех пор менялся; `limit_reached` — памяти не хватит места; `duplicate` — запись, что вернулась бы, уже добавлена заново. */\n revertMerge(runId: string): Promise<void>;\n}\n\n/** Что пошло не так с действием: коды сервера (`conflict` — слияние вернуть нельзя) плюс `unavailable` (памяти нет) и `other` (сбой, обрыв связи). */\nexport type MemoryFailure = \"not_found\" | \"stale_version\" | \"limit_reached\" | \"duplicate\" | \"invalid\" | \"conflict\" | \"unavailable\" | \"other\";\n\n/** Итог действия. Отказ — не исключение: скину нужна надпись, а не стек. */\nexport type MemoryActionResult = { ok: true } | { ok: false; failure: MemoryFailure };\n\n/**\n * Состояние экрана: `loading` — грузится; `ready` — записи есть; `empty` — записей нет; `unavailable` — сервер ответил\n * 404 (память в приложении не включена); `error` — список не загрузился.\n */\nexport type MemoryStatus = \"loading\" | \"ready\" | \"empty\" | \"unavailable\" | \"error\";\n\n/** Форма записи (как у пакета памяти): до 3 разделов по 60 знаков, текст до 500. Экран проверяет её до отправки. */\nexport const memoryNoteLimits = { maxCategories: 3, maxCategoryChars: 60, maxContentChars: 500 } as const;\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { MemoryRequestError } from \"./memory-client\";\nimport type { MemoryActionResult, MemoryApi, MemoryCounts, MemoryFailure, MemoryMerge, MemoryNote, MemoryNoteInput, MemoryStatus } from \"./memory-types\";\n\nexport interface MemoryNotesState {\n state: MemoryStatus;\n /** Записи в порядке сервера: новейшие первыми. */\n notes: MemoryNote[];\n /** `null`, пока список не загружен. */\n counts: MemoryCounts | null;\n /** Склейки повторов, новейшие первыми; пусто, если их нет или сервер их не отдаёт. */\n merges: MemoryMerge[];\n /** Как закончилось последнее действие, если отказом; следующее действие сбрасывает. */\n error: MemoryFailure | null;\n add(input: MemoryNoteInput): Promise<MemoryActionResult>;\n /** Уходит версия записи из списка. Конфликт версий: список перезагружен, вызывающий держит свои значения и может повторить. */\n edit(id: string, input: MemoryNoteInput): Promise<MemoryActionResult>;\n remove(id: string): Promise<MemoryActionResult>;\n clear(): Promise<MemoryActionResult>;\n /** «Вернуть как было»: оригиналы возвращаются вместо результата, список записей и склеек загружается заново. */\n revertMerge(runId: string): Promise<MemoryActionResult>;\n /** Загружает список заново: после отказа — с экраном загрузки, иначе тихо. */\n reload(): void;\n}\n\nconst knownKinds = new Set<string>([\"not_found\", \"stale_version\", \"limit_reached\", \"duplicate\", \"invalid\", \"conflict\"]);\n\n/** Отказ действия по ответу сервера. 404 без `not_found` в теле — адреса памяти нет совсем. */\nfunction failureOf(error: unknown): MemoryFailure {\n if (!(error instanceof MemoryRequestError)) return \"other\";\n if (error.kind !== undefined && knownKinds.has(error.kind)) return error.kind as MemoryFailure;\n return error.status === 404 ? \"unavailable\" : \"other\";\n}\n\n/** Состояние экрана памяти: загрузка, добавление, правка, удаление, стирание. Ничего не рисует. */\nexport function useMemoryNotes(api: MemoryApi): MemoryNotesState {\n const [status, setStatus] = useState<MemoryStatus>(\"loading\");\n const [notes, setNotes] = useState<MemoryNote[]>([]);\n const [counts, setCounts] = useState<MemoryCounts | null>(null);\n const [merges, setMerges] = useState<MemoryMerge[]>([]);\n const [error, setError] = useState<MemoryFailure | null>(null);\n\n const apiRef = useRef(api);\n apiRef.current = api;\n const notesRef = useRef(notes);\n notesRef.current = notes;\n const statusRef = useRef(status);\n statusRef.current = status;\n const countsRef = useRef(counts);\n countsRef.current = counts;\n /** Номер последнего запроса списка: ответ более раннего не затирает свежий. */\n const listRequest = useRef(0);\n const alive = useRef(true);\n useEffect(() => {\n alive.current = true;\n return () => {\n alive.current = false;\n };\n }, []);\n\n const show = useCallback((next: MemoryNote[], nextCounts: MemoryCounts | null) => {\n notesRef.current = next;\n setNotes(next);\n setCounts(nextCounts);\n setStatus(next.length === 0 ? \"empty\" : \"ready\");\n }, []);\n\n /** `quiet`: список уже на экране, экран загрузки не нужен, отказ не ломает то, что показано. */\n const load = useCallback(\n async (quiet: boolean) => {\n const request = ++listRequest.current;\n if (!quiet) setStatus(\"loading\");\n try {\n // Склейки — дополнение к списку: их сбой (или сервер без склеек) записи не ломает, прежние склейки остаются.\n const [loaded, loadedMerges] = await Promise.all([apiRef.current.list(), apiRef.current.listMerges().catch(() => null)]);\n if (!alive.current || request !== listRequest.current) return;\n show(loaded.notes, loaded.counts);\n if (loadedMerges) setMerges(loadedMerges);\n } catch (failure) {\n if (!alive.current || request !== listRequest.current || quiet) return;\n setStatus(failureOf(failure) === \"unavailable\" ? \"unavailable\" : \"error\");\n }\n },\n [show],\n );\n\n useEffect(() => {\n void load(false);\n }, [api, load]);\n\n const reload = useCallback(() => {\n void load(statusRef.current === \"ready\" || statusRef.current === \"empty\");\n }, [load]);\n\n /** Действие с отказом: его причина остаётся в `error`; при конфликте список заново. */\n const refuse = useCallback(\n async (failure: MemoryFailure): Promise<MemoryActionResult> => {\n if (failure === \"stale_version\" || failure === \"not_found\" || failure === \"conflict\") await load(true);\n if (alive.current) setError(failure);\n return { ok: false, failure };\n },\n [load],\n );\n\n const succeed = useCallback(\n (next: MemoryNote[]): MemoryActionResult => {\n show(next, countsRef.current === null ? null : { ...countsRef.current, total: next.length });\n // Счётчик «показано агенту» считает сервер: тихо спрашиваем его после изменения.\n void load(true);\n return { ok: true };\n },\n [load, show],\n );\n\n const add = useCallback(\n async (input: MemoryNoteInput): Promise<MemoryActionResult> => {\n setError(null);\n try {\n const created = await apiRef.current.create(input);\n return alive.current ? succeed([created, ...notesRef.current]) : { ok: true };\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n },\n [refuse, succeed],\n );\n\n const edit = useCallback(\n async (id: string, input: MemoryNoteInput): Promise<MemoryActionResult> => {\n setError(null);\n const current = notesRef.current.find((item) => item.id === id);\n if (!current) return refuse(\"not_found\");\n try {\n const saved = await apiRef.current.update(id, current.version, input);\n return alive.current ? succeed([saved, ...notesRef.current.filter((item) => item.id !== id)]) : { ok: true };\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n },\n [refuse, succeed],\n );\n\n const remove = useCallback(\n async (id: string): Promise<MemoryActionResult> => {\n setError(null);\n const current = notesRef.current.find((item) => item.id === id);\n if (!current) return refuse(\"not_found\");\n try {\n await apiRef.current.remove(id, current.version);\n } catch (failure) {\n const kind = failureOf(failure);\n // Записи уже нет — человек этого и хотел.\n if (kind !== \"not_found\") return refuse(kind);\n }\n return alive.current ? succeed(notesRef.current.filter((item) => item.id !== id)) : { ok: true };\n },\n [refuse, succeed],\n );\n\n const clear = useCallback(async (): Promise<MemoryActionResult> => {\n setError(null);\n try {\n await apiRef.current.clear();\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n return alive.current ? succeed([]) : { ok: true };\n }, [refuse, succeed]);\n\n const revertMerge = useCallback(\n async (runId: string): Promise<MemoryActionResult> => {\n setError(null);\n try {\n await apiRef.current.revertMerge(runId);\n } catch (failure) {\n return refuse(failureOf(failure));\n }\n if (!alive.current) return { ok: true };\n await load(true);\n return { ok: true };\n },\n [load, refuse],\n );\n\n return { state: status, notes, counts, merges, error, add, edit, remove, clear, revertMerge, reload };\n}\n","import type { MemoryApi, MemoryCounts, MemoryMerge, MemoryNote, MemoryNoteInput } from \"./memory-types\";\n\ntype Fetch = typeof globalThis.fetch;\n\n/** Сервер ответил отказом; `status` — код ответа, `kind` — `error.kind` из тела, если оно было (404 без тела — памяти нет). */\nexport class MemoryRequestError extends Error {\n constructor(\n readonly status: number,\n readonly kind: string | undefined,\n action: string,\n ) {\n super(`Memory request failed (${action}): HTTP ${status}${kind ? ` ${kind}` : \"\"}`);\n this.name = \"MemoryRequestError\";\n }\n}\n\nconst jsonBody = (body: unknown): RequestInit => ({\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n});\n\nasync function readKind(response: Response): Promise<string | undefined> {\n try {\n const body = (await response.json()) as { error?: { kind?: unknown } };\n return typeof body.error?.kind === \"string\" ? body.error.kind : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Клиент адресов памяти сервера: `GET/POST {api}/memory`, `PATCH/DELETE {api}/memory/{id}`, `DELETE {api}/memory`,\n * склейки повторов — `GET {api}/memory/merges` и `POST {api}/memory/merges/{runId}/revert`.\n * `api` — тот же адрес обработчика чата, например `/api/chat`. Вызывайте один раз (например, в `useMemo`):\n * экран загружает список заново при смене объекта.\n */\nexport function createMemoryApi({ api, fetch: customFetch }: { api: string; fetch?: Fetch }): MemoryApi {\n const base = `${api.replace(/\\/+$/, \"\")}/memory`;\n const noteUrl = (id: string) => `${base}/${encodeURIComponent(id)}`;\n\n async function request(action: string, url: string, init?: RequestInit): Promise<Response> {\n const response = await (customFetch ?? globalThis.fetch)(url, init);\n if (!response.ok) throw new MemoryRequestError(response.status, await readKind(response), action);\n return response;\n }\n\n return {\n async list() {\n const response = await request(\"list\", base);\n return (await response.json()) as { notes: MemoryNote[]; counts: MemoryCounts };\n },\n async create(input: MemoryNoteInput) {\n const response = await request(\"create\", base, { method: \"POST\", ...jsonBody(input) });\n return ((await response.json()) as { note: MemoryNote }).note;\n },\n async update(id, version, input) {\n const response = await request(\"update\", noteUrl(id), { method: \"PATCH\", ...jsonBody({ ...input, version }) });\n return ((await response.json()) as { note: MemoryNote }).note;\n },\n async remove(id, version) {\n await request(\"remove\", `${noteUrl(id)}?version=${version}`, { method: \"DELETE\" });\n },\n async clear() {\n const response = await request(\"clear\", base, { method: \"DELETE\" });\n return ((await response.json()) as { deleted: number }).deleted;\n },\n async listMerges() {\n const response = await request(\"listMerges\", `${base}/merges`);\n return ((await response.json()) as { merges?: MemoryMerge[] }).merges ?? [];\n },\n async revertMerge(runId) {\n await request(\"revertMerge\", `${base}/merges/${encodeURIComponent(runId)}/revert`, { method: \"POST\" });\n },\n };\n}\n"],"mappings":"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","names":["useCallback","useState","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","jsx","useState","useCallback"]}
package/dist/panel.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  useChatList,
7
7
  useChatListView,
8
8
  useWorkspaceListSync
9
- } from "./chunk-Y5HL6THS.js";
9
+ } from "./chunk-OAQ7TGUB.js";
10
10
 
11
11
  // src/agent-panel/agent-panel-provider.tsx
12
12
  import { useCallback as useCallback2, useMemo, useRef, useState, useSyncExternalStore as useSyncExternalStore2 } from "react";
@@ -2,6 +2,22 @@ import type { UIMessage } from "ai";
2
2
  /** `awaiting_approval` — агент ждёт, разрешит ли человек вызов инструмента. */
3
3
  export type AnswerStatus = "streaming" | "complete" | "stopped" | "error" | "interrupted" | "awaiting_approval";
4
4
  export type ChatErrorKind = "overloaded" | "rate_limited" | "insufficient_funds" | "invalid_key" | "context_overflow" | "other";
5
+ /**
6
+ * Подробности сбоя провайдера: приходят, только если сервер открыл их этому человеку (право `errorDetails` ядра, обычно
7
+ * для админа). `reason` — одна строка до 300 знаков без секретов, `resetsAt` — когда провайдер снимет предел (ISO).
8
+ */
9
+ export interface ChatErrorDetails {
10
+ providerId: string;
11
+ modelId: string;
12
+ statusCode?: number;
13
+ reason?: string;
14
+ resetsAt?: string;
15
+ }
16
+ /** Сбой ответа: вид и, если сервер их отдал, подробности. */
17
+ export interface ChatError {
18
+ kind: ChatErrorKind;
19
+ details?: ChatErrorDetails;
20
+ }
5
21
  /**
6
22
  * Что показывает `ErrorNotice`: ошибка ответа или отказ сервера чату целиком. `signed_out` — вход истёк (401),
7
23
  * `too_many_turns` — у человека уже идут ответы в других чатах (429), ход не начат; `forbidden_origin` — сервер
@@ -52,9 +68,7 @@ export interface ChatMessageMetadata {
52
68
  /** Длительность рассуждений по порядковому номеру части рассуждения в сообщении. */
53
69
  reasoningDurationsMs?: Record<string, number>;
54
70
  status?: AnswerStatus;
55
- error?: {
56
- kind: ChatErrorKind;
57
- };
71
+ error?: ChatError;
58
72
  /** Есть, если озвучку ответа оборвало перебивание. */
59
73
  voiceCut?: VoiceCut;
60
74
  /** Ответ не дошёл до человека (сервер пометил «не доставлено»): модель его не видит. */
@@ -15,8 +15,8 @@ export type { MarkdownSegment } from "./diagrams/mermaid-blocks.js";
15
15
  export { LIVE_DEMO_LANGUAGE, splitAnswerFences } from "./live-demos/answer-fences.js";
16
16
  export type { AnswerFenceKinds, AnswerSegment } from "./live-demos/answer-fences.js";
17
17
  export { LIVE_DEMO_CSP, LIVE_DEMO_SANDBOX, MAX_LIVE_DEMO_BYTES } from "./live-demos/live-demo-document.js";
18
- export type { AgentChatMessage, AnswerStatus, ApprovalDecision, BranchFork, ChatErrorKind, ChatMessageMetadata, ErrorNoticeKind, ChatModelChoice, CompactionMarker, ReasoningLevelId, TokenUsage, VoiceCut, VoiceSound, } from "./chat-types.js";
19
- export { ChatLabelsProvider, englishLabels, englishWorkLabels, englishWorkViewLabels, russianLabels, useChatLabels, voiceErrorText } from "./labels.js";
18
+ export type { AgentChatMessage, AnswerStatus, ApprovalDecision, BranchFork, ChatError, ChatErrorDetails, ChatErrorKind, ChatMessageMetadata, ErrorNoticeKind, ChatModelChoice, CompactionMarker, ReasoningLevelId, TokenUsage, VoiceCut, VoiceSound, } from "./chat-types.js";
19
+ export { ChatLabelsProvider, customErrorText, englishLabels, englishWorkLabels, englishWorkViewLabels, russianLabels, useChatLabels, voiceErrorText } from "./labels.js";
20
20
  export { useThinkingStatus } from "./thinking-status/thinking-status.js";
21
21
  export type { ThinkingStatus } from "./thinking-status/thinking-status.js";
22
22
  export type { ChatLabels, DiagramLabels, HelperLabels, LiveDemoLabels, ImageLabels, MessengerStatusLabels, NoticeLabels, ResolvedChatLabels, TaskLabels, VideoLabels, ViewerLabels, VoiceDialogLabels, VoiceErrorLabels, VoiceSpeechLabels, WidgetLabels, WorkViewLabels, } from "./labels.js";
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import type { ErrorNoticeKind, ReasoningLevelId } from "./chat-types.js";
2
+ import type { ChatError, ErrorNoticeKind, ReasoningLevelId } from "./chat-types.js";
3
3
  import type { MemoryFailure } from "./memory/memory-types.js";
4
4
  import type { ChatOriginView, HelperCallStatus, TaskStatusView } from "./skin-contract.js";
5
5
  import type { SpendingInfo } from "./spending-status.js";
@@ -251,6 +251,12 @@ export interface ChatLabels {
251
251
  /** Чат чужой или его нет: сервер не различает эти случаи. */
252
252
  chatNotFound: string;
253
253
  errors: Record<ErrorNoticeKind, string>;
254
+ /**
255
+ * Подпись сбоя ответа функцией от сбоя: вид и подробности `details`, если сервер их отдал (право `errorDetails`).
256
+ * `plain` — `errors[kind]`; вернуть его — подпись как раньше. Нет функции — `errors[kind]`. Скину уходит текстом
257
+ * (`ErrorNotice` `text`), без разметки; диктору — тот же текст.
258
+ */
259
+ errorText?: (error: ChatError, plain: string) => string;
254
260
  /** Названия уровней рассуждений в меню выбора; названия моделей и провайдеров приходят с сервера. */
255
261
  reasoningLevels: Record<ReasoningLevelId, string>;
256
262
  /** Разделитель на границе сжатой истории. */
@@ -442,6 +448,11 @@ export type VoiceErrorLabels = Record<Exclude<VoiceErrorKind, "time_limit" | "li
442
448
  forbidden?: string;
443
449
  too_long?: string;
444
450
  };
451
+ /**
452
+ * Подпись сбоя ответа от `errorText` словаря; функции нет — `undefined`: скин покажет `errors[kind]` сам. Функция
453
+ * приложения упала — прежняя подпись `errors[kind]` и одна строка в консоль: ответ не падает при отрисовке.
454
+ */
455
+ export declare function customErrorText(labels: Pick<ResolvedChatLabels, "errors" | "errorText">, error: ChatError | undefined): string | undefined;
445
456
  /** Подпись ошибки голоса для человека. */
446
457
  export declare function voiceErrorText(errors: VoiceErrorLabels, kind: VoiceErrorKind, details?: VoiceErrorDetails): string;
447
458
  /** Английские надписи простого вида работы агента (версия 23); входят в `englishLabels`. */
@@ -556,7 +556,8 @@ export interface ChatSkin {
556
556
  * Понятная ошибка вместо сырого текста провайдера; кнопка «повторить» — если передан `onRetry`. Кроме ошибок ответа
557
557
  * приходят `signed_out` (вход истёк), `too_many_turns` (идут ответы в других чатах) и `forbidden_origin` (сервер не признал страницу своей); текст — `labels.errors[kind]`.
558
558
  * `text` (версия 16, необязательное) — причина отказа словами сервера (4xx с текстом, до 300 знаков, `kind` — `other`):
559
- * показать вместо `labels.errors[kind]` только как текст, без разметки. Скин версии 15 покажет общую надпись.
559
+ * показать вместо `labels.errors[kind]` только как текст, без разметки. Скин версии 15 покажет общую надпись. У сбоя
560
+ * ответа `text` — подпись приложения `labels.errorText` (если задана).
560
561
  */
561
562
  ErrorNotice: ComponentType<{
562
563
  kind: ErrorNoticeKind;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykurilov/agent-chat-react",
3
- "version": "0.63.2",
3
+ "version": "0.64.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,