pith-ui 0.0.0-stage → 0.1.1

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 (134) hide show
  1. package/AI.md +75 -0
  2. package/API.md +3688 -0
  3. package/LICENSE +661 -0
  4. package/README.md +80 -2
  5. package/dist/composer.d.ts +38 -0
  6. package/dist/composer.d.ts.map +1 -0
  7. package/dist/composer.js +123 -0
  8. package/dist/composer.js.map +1 -0
  9. package/dist/content/conversation-reader.d.ts +19 -0
  10. package/dist/content/conversation-reader.d.ts.map +1 -0
  11. package/dist/content/conversation-reader.js +280 -0
  12. package/dist/content/conversation-reader.js.map +1 -0
  13. package/dist/content/conversation-view.d.ts +65 -0
  14. package/dist/content/conversation-view.d.ts.map +1 -0
  15. package/dist/content/conversation-view.js +140 -0
  16. package/dist/content/conversation-view.js.map +1 -0
  17. package/dist/content/image-viewer.d.ts +25 -0
  18. package/dist/content/image-viewer.d.ts.map +1 -0
  19. package/dist/content/image-viewer.js +114 -0
  20. package/dist/content/image-viewer.js.map +1 -0
  21. package/dist/content/markdown.d.ts +12 -0
  22. package/dist/content/markdown.d.ts.map +1 -0
  23. package/dist/content/markdown.js +68 -0
  24. package/dist/content/markdown.js.map +1 -0
  25. package/dist/content/paged-preview.d.ts +15 -0
  26. package/dist/content/paged-preview.d.ts.map +1 -0
  27. package/dist/content/paged-preview.js +108 -0
  28. package/dist/content/paged-preview.js.map +1 -0
  29. package/dist/content/views.d.ts +9 -0
  30. package/dist/content/views.d.ts.map +1 -0
  31. package/dist/content/views.js +53 -0
  32. package/dist/content/views.js.map +1 -0
  33. package/dist/content/welcome.d.ts +18 -0
  34. package/dist/content/welcome.d.ts.map +1 -0
  35. package/dist/content/welcome.js +54 -0
  36. package/dist/content/welcome.js.map +1 -0
  37. package/dist/core.d.ts +36 -0
  38. package/dist/core.d.ts.map +1 -0
  39. package/dist/core.js +44 -0
  40. package/dist/core.js.map +1 -0
  41. package/dist/dom.d.ts +14 -0
  42. package/dist/dom.d.ts.map +1 -0
  43. package/dist/dom.js +69 -0
  44. package/dist/dom.js.map +1 -0
  45. package/dist/files/panels.d.ts +100 -0
  46. package/dist/files/panels.d.ts.map +1 -0
  47. package/dist/files/panels.js +286 -0
  48. package/dist/files/panels.js.map +1 -0
  49. package/dist/files/systems.d.ts +91 -0
  50. package/dist/files/systems.d.ts.map +1 -0
  51. package/dist/files/systems.js +273 -0
  52. package/dist/files/systems.js.map +1 -0
  53. package/dist/forms/actions.d.ts +27 -0
  54. package/dist/forms/actions.d.ts.map +1 -0
  55. package/dist/forms/actions.js +39 -0
  56. package/dist/forms/actions.js.map +1 -0
  57. package/dist/forms/custom-connection.d.ts +62 -0
  58. package/dist/forms/custom-connection.d.ts.map +1 -0
  59. package/dist/forms/custom-connection.js +72 -0
  60. package/dist/forms/custom-connection.js.map +1 -0
  61. package/dist/forms/dialog-frame.d.ts +41 -0
  62. package/dist/forms/dialog-frame.d.ts.map +1 -0
  63. package/dist/forms/dialog-frame.js +95 -0
  64. package/dist/forms/dialog-frame.js.map +1 -0
  65. package/dist/forms/history.d.ts +16 -0
  66. package/dist/forms/history.d.ts.map +1 -0
  67. package/dist/forms/history.js +21 -0
  68. package/dist/forms/history.js.map +1 -0
  69. package/dist/forms/identity.d.ts +16 -0
  70. package/dist/forms/identity.d.ts.map +1 -0
  71. package/dist/forms/identity.js +53 -0
  72. package/dist/forms/identity.js.map +1 -0
  73. package/dist/forms/model-editor.d.ts +40 -0
  74. package/dist/forms/model-editor.d.ts.map +1 -0
  75. package/dist/forms/model-editor.js +89 -0
  76. package/dist/forms/model-editor.js.map +1 -0
  77. package/dist/forms/panels.d.ts +146 -0
  78. package/dist/forms/panels.d.ts.map +1 -0
  79. package/dist/forms/panels.js +339 -0
  80. package/dist/forms/panels.js.map +1 -0
  81. package/dist/forms/provider-login.d.ts +22 -0
  82. package/dist/forms/provider-login.d.ts.map +1 -0
  83. package/dist/forms/provider-login.js +116 -0
  84. package/dist/forms/provider-login.js.map +1 -0
  85. package/dist/forms/resource-editor.d.ts +27 -0
  86. package/dist/forms/resource-editor.d.ts.map +1 -0
  87. package/dist/forms/resource-editor.js +34 -0
  88. package/dist/forms/resource-editor.js.map +1 -0
  89. package/dist/foundations.d.ts +95 -0
  90. package/dist/foundations.d.ts.map +1 -0
  91. package/dist/foundations.js +315 -0
  92. package/dist/foundations.js.map +1 -0
  93. package/dist/index.d.ts +28 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +28 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/navigation/context-menu.d.ts +26 -0
  98. package/dist/navigation/context-menu.d.ts.map +1 -0
  99. package/dist/navigation/context-menu.js +81 -0
  100. package/dist/navigation/context-menu.js.map +1 -0
  101. package/dist/navigation/navigation.d.ts +92 -0
  102. package/dist/navigation/navigation.d.ts.map +1 -0
  103. package/dist/navigation/navigation.js +368 -0
  104. package/dist/navigation/navigation.js.map +1 -0
  105. package/dist/primitives.d.ts +310 -0
  106. package/dist/primitives.d.ts.map +1 -0
  107. package/dist/primitives.js +633 -0
  108. package/dist/primitives.js.map +1 -0
  109. package/dist/styles.css +853 -0
  110. package/dist/surface.d.ts +29 -0
  111. package/dist/surface.d.ts.map +1 -0
  112. package/dist/surface.js +154 -0
  113. package/dist/surface.js.map +1 -0
  114. package/dist/tasks/approval.d.ts +25 -0
  115. package/dist/tasks/approval.d.ts.map +1 -0
  116. package/dist/tasks/approval.js +64 -0
  117. package/dist/tasks/approval.js.map +1 -0
  118. package/dist/tasks/permission.d.ts +37 -0
  119. package/dist/tasks/permission.d.ts.map +1 -0
  120. package/dist/tasks/permission.js +73 -0
  121. package/dist/tasks/permission.js.map +1 -0
  122. package/dist/tasks/runtime.d.ts +31 -0
  123. package/dist/tasks/runtime.d.ts.map +1 -0
  124. package/dist/tasks/runtime.js +34 -0
  125. package/dist/tasks/runtime.js.map +1 -0
  126. package/dist/theme.d.ts +4 -0
  127. package/dist/theme.d.ts.map +1 -0
  128. package/dist/theme.js +6 -0
  129. package/dist/theme.js.map +1 -0
  130. package/dist/workspace.d.ts +92 -0
  131. package/dist/workspace.d.ts.map +1 -0
  132. package/dist/workspace.js +288 -0
  133. package/dist/workspace.js.map +1 -0
  134. package/package.json +27 -4
@@ -0,0 +1,65 @@
1
+ import { type UIBlock } from '../core.js';
2
+ import { type MarkdownOptions } from './markdown.js';
3
+ /** UI data only. Hosts decide copy/edit/branch eligibility and resolve images. */
4
+ export interface ConversationMessage {
5
+ id: string;
6
+ role: string;
7
+ text: string;
8
+ timestamp?: number;
9
+ toolName?: string;
10
+ toolCallId?: string;
11
+ status?: string;
12
+ images?: Array<{
13
+ index: number;
14
+ mimeType?: string;
15
+ }>;
16
+ command?: {
17
+ text: string;
18
+ cwd: string;
19
+ exitCode?: number;
20
+ };
21
+ actions?: {
22
+ copy?: boolean | string;
23
+ edit?: boolean;
24
+ branch?: {
25
+ key: string;
26
+ label?: string;
27
+ };
28
+ };
29
+ }
30
+ export interface ConversationViewState {
31
+ id: string;
32
+ messages: readonly ConversationMessage[];
33
+ working: boolean;
34
+ folds?: ReadonlyMap<string, boolean>;
35
+ }
36
+ export interface ConversationAction {
37
+ action: 'copy' | 'edit' | 'branch';
38
+ messageId: string;
39
+ branchKey?: string;
40
+ }
41
+ export interface ConversationActionAvailability {
42
+ copy?: boolean;
43
+ edit?: boolean;
44
+ branch?: boolean;
45
+ }
46
+ export type ConversationImageState = {
47
+ status: 'ready';
48
+ src: string;
49
+ } | {
50
+ status: 'unavailable';
51
+ };
52
+ export interface ConversationViewBlock extends UIBlock<ConversationViewState> {
53
+ /** Disabled state, separate from whether an action exists for a message. */
54
+ updateActionAvailability(disabled: Readonly<ConversationActionAvailability>): void;
55
+ images(): readonly HTMLImageElement[];
56
+ setImageState(image: HTMLImageElement, state: ConversationImageState): void;
57
+ }
58
+ export interface ConversationViewOptions {
59
+ container?: HTMLElement;
60
+ markdown?: (conversationId: string) => MarkdownOptions;
61
+ onAction?: (action: ConversationAction, button: HTMLButtonElement) => void;
62
+ onCopyCode?: (text: string, button: HTMLButtonElement) => void;
63
+ }
64
+ export declare function createConversationView(options?: ConversationViewOptions): ConversationViewBlock;
65
+ //# sourceMappingURL=conversation-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-view.d.ts","sourceRoot":"","sources":["../../src/content/conversation-view.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrE,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACjG;AACD,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AACD,MAAM,WAAW,kBAAkB;IAAG,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;AACjH,MAAM,WAAW,8BAA8B;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAC;AAClG,MAAM,MAAM,sBAAsB,GAAG;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,GAAG;IAAC,MAAM,EAAE,aAAa,CAAA;CAAC,CAAC;AAC9F,MAAM,WAAW,qBAAsB,SAAQ,OAAO,CAAC,qBAAqB,CAAC;IAC3E,4EAA4E;IAC5E,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,8BAA8B,CAAC,GAAG,IAAI,CAAC;IACnF,MAAM,IAAI,SAAS,gBAAgB,EAAE,CAAC;IACtC,aAAa,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAC7E;AACD,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,eAAe,CAAC;IACvD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC3E,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAChE;AAGD,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,qBAAqB,CAuEnG"}
@@ -0,0 +1,140 @@
1
+ import { escape, icon } from '../dom.js';
2
+ import { controlDisabled } from '../core.js';
3
+ import { adoptProductParts } from '../foundations.js';
4
+ import { renderMarkdown } from './markdown.js';
5
+ import { reconcileViews, clearViews } from './views.js';
6
+ export function createConversationView(options = {}) {
7
+ const container = options.container || document.createElement('div');
8
+ container.classList.add('pui-conversation-messages');
9
+ const parts = adoptProductParts(container);
10
+ let actionButtons = [];
11
+ let disabledActions = {};
12
+ const syncAvailability = () => { for (const button of actionButtons)
13
+ parts.action(button).update({ disabled: controlDisabled(button, !!disabledActions[button.dataset.messageAction]) }); };
14
+ let destroyed = false;
15
+ const click = (event) => {
16
+ const target = event.target.closest('[data-message-action], [data-copy-code]');
17
+ if (!target || !container.contains(target) || target.disabled)
18
+ return;
19
+ if (target.hasAttribute('data-copy-code')) {
20
+ const text = target.closest('.pui-code-block')?.querySelector('pre > code')?.textContent || '';
21
+ if (options.onCopyCode) {
22
+ event.stopPropagation();
23
+ options.onCopyCode(text, target);
24
+ }
25
+ return;
26
+ }
27
+ if (options.onAction) {
28
+ event.stopPropagation();
29
+ options.onAction({ action: target.dataset.messageAction, messageId: target.dataset.messageActionId, branchKey: target.dataset.branchKey }, target);
30
+ }
31
+ };
32
+ container.addEventListener('click', click);
33
+ let activeId = '';
34
+ function update(id, messages, working, folds) {
35
+ if (destroyed)
36
+ return;
37
+ if (id !== activeId) {
38
+ clearViews(container);
39
+ activeId = id;
40
+ }
41
+ const views = [];
42
+ const messageView = (message, key = message.id, foldKey = '') => ({
43
+ key, signature: JSON.stringify(message), html: () => renderMessage(id, message, folds.get(foldKey), foldKey, (text, conversationId) => renderMarkdown(text, options.markdown?.(conversationId))),
44
+ });
45
+ let groupIndex = 0;
46
+ for (let index = 0; index < messages.length;) {
47
+ if (messages[index].role !== 'tool') {
48
+ views.push(messageView(messages[index++]));
49
+ continue;
50
+ }
51
+ const start = index;
52
+ while (index < messages.length && messages[index].role === 'tool')
53
+ index++;
54
+ const tools = messages.slice(start, index), key = `group-${groupIndex++}`;
55
+ const running = tools.filter((tool) => tool.status === 'running').length;
56
+ const failed = tools.filter((tool) => tool.status === 'error').length;
57
+ const label = toolGroupLabel(tools);
58
+ views.push({ key, signature: JSON.stringify([label, tools.length, failed, running]),
59
+ html: () => `<details class="pui-tool-group" data-tool-fold="${key}" ${folds.get(key) ? 'open' : ''}><summary>${icon('terminal')}<span class="pui-tool-group-label">${label}</span><span class="pui-tool-group-count">${tools.length} ${tools.length === 1 ? 'call' : 'calls'}</span>${failed ? `<span class="pui-tool-group-failed">${failed} failed</span>` : ''}${running ? `<span class="pui-tool-group-running">${running} running</span>` : ''}${icon('down')}</summary><div class="pui-tool-group-content" data-view-children></div></details>`,
60
+ children: tools.map((tool) => { const toolKey = `${key}-tool-${tool.toolCallId || tool.id}`; return messageView(tool, toolKey, toolKey); }),
61
+ });
62
+ }
63
+ if (working)
64
+ views.push({ key: 'working', signature: 'working', html: () => '<div class="pui-agent-working" role="status"><span class="pui-working-dots" aria-hidden="true"><i></i><i></i><i></i></span><span>Working…</span></div>' });
65
+ reconcileViews(container, views);
66
+ actionButtons = [...container.querySelectorAll('[data-message-action]')];
67
+ parts.update();
68
+ syncAvailability();
69
+ }
70
+ return {
71
+ element: container,
72
+ update(state) {
73
+ const folds = state.folds || new Map(Array.from(container.querySelectorAll('[data-tool-fold]'), (element) => [element.dataset.toolFold, element.open]));
74
+ update(state.id, state.messages, state.working, folds);
75
+ },
76
+ updateActionAvailability(disabled) {
77
+ if (destroyed || (disabled.copy === disabledActions.copy && disabled.edit === disabledActions.edit && disabled.branch === disabledActions.branch))
78
+ return;
79
+ disabledActions = { ...disabled };
80
+ syncAvailability();
81
+ },
82
+ images: () => destroyed ? [] : [...container.querySelectorAll('img[data-image-key]')],
83
+ setImageState(image, state) {
84
+ if (destroyed || !container.contains(image))
85
+ return;
86
+ image.classList.toggle('pui-image-unavailable', state.status === 'unavailable');
87
+ image.alt = state.status === 'unavailable' ? 'Image unavailable' : `Image ${Number(image.dataset.imageIndex) + 1}`;
88
+ if (state.status === 'ready')
89
+ image.src = state.src;
90
+ },
91
+ destroy() {
92
+ if (destroyed)
93
+ return;
94
+ destroyed = true;
95
+ container.removeEventListener('click', click);
96
+ clearViews(container);
97
+ parts.destroy();
98
+ actionButtons = [];
99
+ },
100
+ };
101
+ }
102
+ function toolGroupLabel(tools) {
103
+ if (tools.length === 1)
104
+ return { list_files: 'Listed files', read_file: 'Read a file', run_command: 'Ran a command', write_file: 'Wrote a file', edit_file: 'Edited a file', tool_search: 'Searched tools', codemode: 'Ran code' }[tools[0].toolName || ''] || 'Used a tool';
105
+ const hasCommands = tools.some((tool) => tool.toolName === 'run_command');
106
+ const exploredFiles = tools.every((tool) => ['list_files', 'read_file', 'grep', 'find', 'ls', 'run_command'].includes(tool.toolName || ''));
107
+ if (tools.every((tool) => tool.toolName === 'run_command'))
108
+ return 'Ran commands';
109
+ if (exploredFiles)
110
+ return hasCommands ? 'Ran commands and explored files' : 'Explored files';
111
+ return 'Used tools';
112
+ }
113
+ function renderMessage(conversationId, message, expanded = false, foldKey = '', markdown = (text) => renderMarkdown(text)) {
114
+ const text = message.text ?? '';
115
+ const images = (message.images || []).map((image) => `<img class="pui-history-image" data-image-key="${escape(`${conversationId}/${message.id}/${image.index}`)}" data-message-id="${escape(message.id)}" data-image-index="${image.index}" alt="Image ${image.index + 1}" role="button" tabindex="0" title="Click to enlarge" loading="lazy" />`).join('');
116
+ const gallery = images ? `<div class="pui-message-images">${images}</div>` : '';
117
+ const copyLabel = typeof message.actions?.copy === 'string' ? message.actions.copy : 'Copy message';
118
+ const copy = message.actions?.copy ? `<button type="button" class="pui-quiet-icon pui-message-action" data-message-action="copy" data-message-action-id="${escape(message.id)}" data-copy-message="${escape(message.id)}" title="${escape(copyLabel)}" aria-label="${escape(copyLabel)}">${icon('copy')}</button>` : '';
119
+ const edit = message.actions?.edit ? `<button type="button" class="pui-quiet-icon pui-message-action" data-message-action="edit" data-message-action-id="${escape(message.id)}" data-edit-message="${escape(message.id)}" title="Edit message" aria-label="Edit message">${icon('edit')}</button>` : '';
120
+ const branchAction = message.actions?.branch;
121
+ const branch = branchAction ? `<button type="button" class="pui-quiet-icon pui-message-action" data-message-action="branch" data-message-action-id="${escape(message.id)}" data-branch-key="${escape(branchAction.key)}" data-node="${escape(branchAction.key)}" title="${escape(branchAction.label || 'Branch from this message')}" aria-label="${escape(branchAction.label || 'Branch from this message')}">${icon('branch')}</button>` : '';
122
+ const actions = copy || edit || branch ? `<div class="pui-message-actions">${copy}${edit}${branch}</div>` : '';
123
+ const date = new Date(message.timestamp || NaN);
124
+ const time = Number.isFinite(date.getTime()) ? `<time class="pui-message-time" datetime="${date.toISOString()}" title="${escape(date.toLocaleString())}">${escape(date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }))}</time>` : '';
125
+ const footer = time || actions ? `<div class="pui-message-footer">${time}${actions}</div>` : '';
126
+ if (message.role === 'tool') {
127
+ const command = message.command;
128
+ const details = command ? `<div class="pui-command-details"><span class="pui-tool-field-label">Command</span><pre class="pui-command-text">${escape(command.text)}</pre><div class="pui-command-directory"><span class="pui-tool-field-label">Directory</span><code>${escape(command.cwd)}</code></div></div>` : '';
129
+ const status = command ? { running: 'Running', done: 'Completed', error: 'Failed' }[message.status || ''] || message.status || 'Result' : message.status || 'Result';
130
+ const exit = command?.exitCode !== undefined ? ` · Exit ${command.exitCode}` : '';
131
+ const output = command ? `<div class="pui-command-output"><span class="pui-tool-field-label">Output</span><pre>${escape(text)}</pre></div>` : `<pre>${escape(text)}</pre>`;
132
+ return `<details class="pui-tool-message" data-reading-id="${escape(message.id)}" data-tool-fold="${escape(foldKey)}" ${expanded ? 'open' : ''}><summary>${icon('terminal')}<span>${command ? 'Command' : escape(message.toolName || 'Tool result')}</span><span class="pui-tool-status${message.status === 'error' ? ' pui-tool-status-error' : ''}">${escape(status + exit)}</span>${icon('down')}</summary>${details}${output}${gallery}</details>`;
133
+ }
134
+ if (message.role === 'user')
135
+ return `<article class="pui-message pui-user-message" data-reading-id="${escape(message.id)}" aria-label="Your message"><div class="pui-message-content">${gallery}${text ? `<div class="pui-user-text">${escape(text)}</div>` : ''}${footer}</div></article>`;
136
+ if (message.role === 'system')
137
+ return `<div class="pui-system-message" data-reading-id="${escape(message.id)}">${escape(text)}</div>`;
138
+ return `<article class="pui-message pui-assistant-message" data-reading-id="${escape(message.id)}" aria-label="Agent response"><div class="pui-message-content"><div class="pui-markdown">${markdown(text, conversationId)}</div>${message.status === 'error' ? '<span class="pui-message-error-label">Response interrupted</span>' : ''}${footer}</div></article>`;
139
+ }
140
+ //# sourceMappingURL=conversation-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-view.js","sourceRoot":"","sources":["../../src/content/conversation-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,eAAe,EAAgB,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAwB,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAa,MAAM,YAAY,CAAC;AAsCnE,MAAM,UAAU,sBAAsB,CAAC,UAAmC,EAAE;IAC1E,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACrE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC3C,IAAI,aAAa,GAAwB,EAAE,CAAC;IAC5C,IAAI,eAAe,GAA6C,EAAE,CAAC;IACnE,MAAM,gBAAgB,GAAG,GAAG,EAAE,GAAE,KAAK,MAAM,MAAM,IAAI,aAAa;QAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAC,QAAQ,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,aAA6C,CAAC,CAAC,EAAC,CAAC,CAAC,CAAA,CAAC,CAAC;IACxN,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,MAAM,KAAK,GAAG,CAAC,KAAY,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAI,KAAK,CAAC,MAAkB,CAAC,OAAO,CAAoB,yCAAyC,CAAC,CAAC;QAC/G,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ;YAAE,OAAO;QACtE,IAAI,MAAM,CAAC,YAAY,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,EAAE,WAAW,IAAI,EAAE,CAAC;YAC/F,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;gBAAA,KAAK,CAAC,eAAe,EAAE,CAAC;gBAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAAA,CAAC;YACpF,OAAO;QACT,CAAC;QACD,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YAAA,KAAK,CAAC,eAAe,EAAE,CAAC;YAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,aAA6C,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,eAAgB,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;QAAA,CAAC;IACvO,CAAC,CAAC;IACF,SAAS,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3C,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,SAAS,MAAM,CAAC,EAAU,EAAE,QAAwC,EAAE,OAAgB,EAAE,KAAmC;QACzH,IAAI,SAAS;YAAE,OAAO;QACtB,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;YAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAAC,QAAQ,GAAG,EAAE,CAAC;QAAC,CAAC;QAC9D,MAAM,KAAK,GAAW,EAAE,CAAC;QACzB,MAAM,WAAW,GAAG,CAAC,OAA4B,EAAE,GAAG,GAAG,OAAO,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,EAAQ,EAAE,CAAC,CAAC;YAC3F,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;SACjM,CAAC,CAAC;QACH,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC;YAC7C,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;gBAAC,SAAS;YAAC,CAAC;YAC9F,MAAM,KAAK,GAAG,KAAK,CAAC;YACpB,OAAO,KAAK,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,MAAM;gBAAE,KAAK,EAAE,CAAC;YAC3E,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,GAAG,GAAG,SAAS,UAAU,EAAE,EAAE,CAAC;YAC1E,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;YACzE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;YACtE,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;YACpC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;gBACjF,IAAI,EAAE,GAAG,EAAE,CAAC,mDAAmD,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,IAAI,CAAC,UAAU,CAAC,sCAAsC,KAAK,6CAA6C,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,UAAU,MAAM,CAAC,CAAC,CAAC,uCAAuC,MAAM,gBAAgB,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,wCAAwC,OAAO,iBAAiB,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,mFAAmF;gBACthB,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,OAAO,GAAG,GAAG,GAAG,SAAS,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;aAC5I,CAAC,CAAC;QACL,CAAC;QACD,IAAI,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,wJAAwJ,EAAE,CAAC,CAAC;QACxO,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACjC,aAAa,GAAG,CAAC,GAAG,SAAS,CAAC,gBAAgB,CAAoB,uBAAuB,CAAC,CAAC,CAAC;QAC5F,KAAK,CAAC,MAAM,EAAE,CAAC;QAAC,gBAAgB,EAAE,CAAC;IACrC,CAAC;IACD,OAAO;QACL,OAAO,EAAE,SAAS;QAClB,MAAM,CAAC,KAAK;YACV,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAqB,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAS,EAAE,OAAO,CAAC,IAAI,CAAU,CAAC,CAAC,CAAC;YACtL,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACzD,CAAC;QACD,wBAAwB,CAAC,QAAQ;YAC/B,IAAI,SAAS,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,eAAe,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,KAAK,eAAe,CAAC,IAAI,IAAI,QAAQ,CAAC,MAAM,KAAK,eAAe,CAAC,MAAM,CAAC;gBAAE,OAAO;YAC1J,eAAe,GAAG,EAAC,GAAG,QAAQ,EAAC,CAAC;YAAC,gBAAgB,EAAE,CAAC;QACtD,CAAC;QACD,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,gBAAgB,CAAmB,qBAAqB,CAAC,CAAC;QACvG,aAAa,CAAC,KAAK,EAAE,KAAK;YACxB,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO;YACpD,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,uBAAuB,EAAE,KAAK,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC;YAChF,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACnH,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO;gBAAE,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;QACtD,CAAC;QACD,OAAO;YACL,IAAI,SAAS;gBAAE,OAAO;YACtB,SAAS,GAAG,IAAI,CAAC;YACjB,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC9C,UAAU,CAAC,SAAS,CAAC,CAAC;YACtB,KAAK,CAAC,OAAO,EAAE,CAAC;YAAC,aAAa,GAAG,EAAE,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAqC;IAC3D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE,UAAU,EAA6B,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,IAAI,aAAa,CAAC;IACzS,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,aAAa,CAAC,CAAC;IAC1E,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5I,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,aAAa,CAAC;QAAE,OAAO,cAAc,CAAC;IAClF,IAAI,aAAa;QAAE,OAAO,WAAW,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAC7F,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,aAAa,CAAC,cAAsB,EAAE,OAA4B,EAAE,QAAQ,GAAG,KAAK,EAAE,OAAO,GAAG,EAAE,EAAE,WAA8D,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC;IACvM,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kDAAkD,MAAM,CAAC,GAAG,cAAc,IAAI,OAAO,CAAC,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC,sBAAsB,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,uBAAuB,KAAK,CAAC,KAAK,gBAAgB,KAAK,CAAC,KAAK,GAAG,CAAC,yEAAyE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5V,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,mCAAmC,MAAM,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,MAAM,SAAS,GAAG,OAAO,OAAO,CAAC,OAAO,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC;IACpG,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,sHAAsH,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,wBAAwB,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,YAAY,MAAM,CAAC,SAAS,CAAC,iBAAiB,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACxT,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,sHAAsH,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,wBAAwB,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,oDAAoD,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACxS,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC;IAC7C,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,wHAAwH,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,sBAAsB,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,YAAY,CAAC,KAAK,IAAI,0BAA0B,CAAC,iBAAiB,MAAM,CAAC,YAAY,CAAC,KAAK,IAAI,0BAA0B,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/a,MAAM,OAAO,GAAG,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,oCAAoC,IAAI,GAAG,IAAI,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/G,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,4CAA4C,IAAI,CAAC,WAAW,EAAE,YAAY,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACrP,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,mCAAmC,IAAI,GAAG,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAChG,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,mHAAmH,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,qGAAqG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;QACpT,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAE,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAA6B,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC;QACjM,MAAM,IAAI,GAAG,OAAO,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,wFAAwF,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC3K,OAAO,sDAAsD,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,qBAAqB,MAAM,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,IAAI,CAAC,UAAU,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,IAAI,aAAa,CAAC,sCAAsC,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,aAAa,OAAO,GAAG,MAAM,GAAG,OAAO,YAAY,CAAC;IACzb,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,kEAAkE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,gEAAgE,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,8BAA8B,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,kBAAkB,CAAC;IAC5R,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,oDAAoD,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;IACtI,OAAO,uEAAuE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,4FAA4F,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,mEAAmE,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,kBAAkB,CAAC;AACtW,CAAC","sourcesContent":["import { escape, icon } from '../dom.js';\nimport { controlDisabled, type UIBlock } from '../core.js';\nimport { adoptProductParts } from '../foundations.js';\nimport { renderMarkdown, type MarkdownOptions } from './markdown.js';\nimport { reconcileViews, clearViews, type View } from './views.js';\n\n/** UI data only. Hosts decide copy/edit/branch eligibility and resolve images. */\nexport interface ConversationMessage {\n id: string;\n role: string;\n text: string;\n timestamp?: number;\n toolName?: string;\n toolCallId?: string;\n status?: string;\n images?: Array<{ index: number; mimeType?: string }>;\n command?: { text: string; cwd: string; exitCode?: number };\n actions?: { copy?: boolean | string; edit?: boolean; branch?: { key: string; label?: string } };\n}\nexport interface ConversationViewState {\n id: string;\n messages: readonly ConversationMessage[];\n working: boolean;\n folds?: ReadonlyMap<string, boolean>;\n}\nexport interface ConversationAction { action: 'copy' | 'edit' | 'branch'; messageId: string; branchKey?: string }\nexport interface ConversationActionAvailability {copy?: boolean; edit?: boolean; branch?: boolean}\nexport type ConversationImageState = {status: 'ready'; src: string} | {status: 'unavailable'};\nexport interface ConversationViewBlock extends UIBlock<ConversationViewState> {\n /** Disabled state, separate from whether an action exists for a message. */\n updateActionAvailability(disabled: Readonly<ConversationActionAvailability>): void;\n images(): readonly HTMLImageElement[];\n setImageState(image: HTMLImageElement, state: ConversationImageState): void;\n}\nexport interface ConversationViewOptions {\n container?: HTMLElement;\n markdown?: (conversationId: string) => MarkdownOptions;\n onAction?: (action: ConversationAction, button: HTMLButtonElement) => void;\n onCopyCode?: (text: string, button: HTMLButtonElement) => void;\n}\n\n\nexport function createConversationView(options: ConversationViewOptions = {}): ConversationViewBlock {\n const container = options.container || document.createElement('div');\n container.classList.add('pui-conversation-messages');\n const parts = adoptProductParts(container);\n let actionButtons: HTMLButtonElement[] = [];\n let disabledActions: Readonly<ConversationActionAvailability> = {};\n const syncAvailability = () => {for (const button of actionButtons) parts.action(button).update({disabled: controlDisabled(button, !!disabledActions[button.dataset.messageAction as ConversationAction['action']])});};\n let destroyed = false;\n const click = (event: Event) => {\n const target = (event.target as Element).closest<HTMLButtonElement>('[data-message-action], [data-copy-code]');\n if (!target || !container.contains(target) || target.disabled) return;\n if (target.hasAttribute('data-copy-code')) {\n const text = target.closest('.pui-code-block')?.querySelector('pre > code')?.textContent || '';\n if (options.onCopyCode) {event.stopPropagation(); options.onCopyCode(text, target);}\n return;\n }\n if (options.onAction) {event.stopPropagation(); options.onAction({ action: target.dataset.messageAction as ConversationAction['action'], messageId: target.dataset.messageActionId!, branchKey: target.dataset.branchKey }, target);}\n };\n container.addEventListener('click', click);\n let activeId = '';\n function update(id: string, messages: readonly ConversationMessage[], working: boolean, folds: ReadonlyMap<string, boolean>): void {\n if (destroyed) return;\n if (id !== activeId) { clearViews(container); activeId = id; }\n const views: View[] = [];\n const messageView = (message: ConversationMessage, key = message.id, foldKey = ''): View => ({\n key, signature: JSON.stringify(message), html: () => renderMessage(id, message, folds.get(foldKey), foldKey, (text, conversationId) => renderMarkdown(text, options.markdown?.(conversationId))),\n });\n let groupIndex = 0;\n for (let index = 0; index < messages.length;) {\n if (messages[index].role !== 'tool') { views.push(messageView(messages[index++])); continue; }\n const start = index;\n while (index < messages.length && messages[index].role === 'tool') index++;\n const tools = messages.slice(start, index), key = `group-${groupIndex++}`;\n const running = tools.filter((tool) => tool.status === 'running').length;\n const failed = tools.filter((tool) => tool.status === 'error').length;\n const label = toolGroupLabel(tools);\n views.push({ key, signature: JSON.stringify([label, tools.length, failed, running]),\n html: () => `<details class=\"pui-tool-group\" data-tool-fold=\"${key}\" ${folds.get(key) ? 'open' : ''}><summary>${icon('terminal')}<span class=\"pui-tool-group-label\">${label}</span><span class=\"pui-tool-group-count\">${tools.length} ${tools.length === 1 ? 'call' : 'calls'}</span>${failed ? `<span class=\"pui-tool-group-failed\">${failed} failed</span>` : ''}${running ? `<span class=\"pui-tool-group-running\">${running} running</span>` : ''}${icon('down')}</summary><div class=\"pui-tool-group-content\" data-view-children></div></details>`,\n children: tools.map((tool) => { const toolKey = `${key}-tool-${tool.toolCallId || tool.id}`; return messageView(tool, toolKey, toolKey); }),\n });\n }\n if (working) views.push({ key: 'working', signature: 'working', html: () => '<div class=\"pui-agent-working\" role=\"status\"><span class=\"pui-working-dots\" aria-hidden=\"true\"><i></i><i></i><i></i></span><span>Working…</span></div>' });\n reconcileViews(container, views);\n actionButtons = [...container.querySelectorAll<HTMLButtonElement>('[data-message-action]')];\n parts.update(); syncAvailability();\n }\n return {\n element: container,\n update(state): void {\n const folds = state.folds || new Map(Array.from(container.querySelectorAll<HTMLDetailsElement>('[data-tool-fold]'), (element) => [element.dataset.toolFold!, element.open] as const));\n update(state.id, state.messages, state.working, folds);\n },\n updateActionAvailability(disabled): void {\n if (destroyed || (disabled.copy === disabledActions.copy && disabled.edit === disabledActions.edit && disabled.branch === disabledActions.branch)) return;\n disabledActions = {...disabled}; syncAvailability();\n },\n images: () => destroyed ? [] : [...container.querySelectorAll<HTMLImageElement>('img[data-image-key]')],\n setImageState(image, state): void {\n if (destroyed || !container.contains(image)) return;\n image.classList.toggle('pui-image-unavailable', state.status === 'unavailable');\n image.alt = state.status === 'unavailable' ? 'Image unavailable' : `Image ${Number(image.dataset.imageIndex) + 1}`;\n if (state.status === 'ready') image.src = state.src;\n },\n destroy(): void {\n if (destroyed) return;\n destroyed = true;\n container.removeEventListener('click', click);\n clearViews(container);\n parts.destroy(); actionButtons = [];\n },\n };\n}\n\nfunction toolGroupLabel(tools: readonly ConversationMessage[]): string {\n if (tools.length === 1) return ({ list_files: 'Listed files', read_file: 'Read a file', run_command: 'Ran a command', write_file: 'Wrote a file', edit_file: 'Edited a file', tool_search: 'Searched tools', codemode: 'Ran code' } as Record<string, string>)[tools[0].toolName || ''] || 'Used a tool';\n const hasCommands = tools.some((tool) => tool.toolName === 'run_command');\n const exploredFiles = tools.every((tool) => ['list_files', 'read_file', 'grep', 'find', 'ls', 'run_command'].includes(tool.toolName || ''));\n if (tools.every((tool) => tool.toolName === 'run_command')) return 'Ran commands';\n if (exploredFiles) return hasCommands ? 'Ran commands and explored files' : 'Explored files';\n return 'Used tools';\n}\n\nfunction renderMessage(conversationId: string, message: ConversationMessage, expanded = false, foldKey = '', markdown: (value: string, conversationId: string) => string = (text) => renderMarkdown(text)): string {\n const text = message.text ?? '';\n const images = (message.images || []).map((image) => `<img class=\"pui-history-image\" data-image-key=\"${escape(`${conversationId}/${message.id}/${image.index}`)}\" data-message-id=\"${escape(message.id)}\" data-image-index=\"${image.index}\" alt=\"Image ${image.index + 1}\" role=\"button\" tabindex=\"0\" title=\"Click to enlarge\" loading=\"lazy\" />`).join('');\n const gallery = images ? `<div class=\"pui-message-images\">${images}</div>` : '';\n const copyLabel = typeof message.actions?.copy === 'string' ? message.actions.copy : 'Copy message';\n const copy = message.actions?.copy ? `<button type=\"button\" class=\"pui-quiet-icon pui-message-action\" data-message-action=\"copy\" data-message-action-id=\"${escape(message.id)}\" data-copy-message=\"${escape(message.id)}\" title=\"${escape(copyLabel)}\" aria-label=\"${escape(copyLabel)}\">${icon('copy')}</button>` : '';\n const edit = message.actions?.edit ? `<button type=\"button\" class=\"pui-quiet-icon pui-message-action\" data-message-action=\"edit\" data-message-action-id=\"${escape(message.id)}\" data-edit-message=\"${escape(message.id)}\" title=\"Edit message\" aria-label=\"Edit message\">${icon('edit')}</button>` : '';\n const branchAction = message.actions?.branch;\n const branch = branchAction ? `<button type=\"button\" class=\"pui-quiet-icon pui-message-action\" data-message-action=\"branch\" data-message-action-id=\"${escape(message.id)}\" data-branch-key=\"${escape(branchAction.key)}\" data-node=\"${escape(branchAction.key)}\" title=\"${escape(branchAction.label || 'Branch from this message')}\" aria-label=\"${escape(branchAction.label || 'Branch from this message')}\">${icon('branch')}</button>` : '';\n const actions = copy || edit || branch ? `<div class=\"pui-message-actions\">${copy}${edit}${branch}</div>` : '';\n const date = new Date(message.timestamp || NaN);\n const time = Number.isFinite(date.getTime()) ? `<time class=\"pui-message-time\" datetime=\"${date.toISOString()}\" title=\"${escape(date.toLocaleString())}\">${escape(date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }))}</time>` : '';\n const footer = time || actions ? `<div class=\"pui-message-footer\">${time}${actions}</div>` : '';\n if (message.role === 'tool') {\n const command = message.command;\n const details = command ? `<div class=\"pui-command-details\"><span class=\"pui-tool-field-label\">Command</span><pre class=\"pui-command-text\">${escape(command.text)}</pre><div class=\"pui-command-directory\"><span class=\"pui-tool-field-label\">Directory</span><code>${escape(command.cwd)}</code></div></div>` : '';\n const status = command ? ({ running: 'Running', done: 'Completed', error: 'Failed' } as Record<string, string>)[message.status || ''] || message.status || 'Result' : message.status || 'Result';\n const exit = command?.exitCode !== undefined ? ` · Exit ${command.exitCode}` : '';\n const output = command ? `<div class=\"pui-command-output\"><span class=\"pui-tool-field-label\">Output</span><pre>${escape(text)}</pre></div>` : `<pre>${escape(text)}</pre>`;\n return `<details class=\"pui-tool-message\" data-reading-id=\"${escape(message.id)}\" data-tool-fold=\"${escape(foldKey)}\" ${expanded ? 'open' : ''}><summary>${icon('terminal')}<span>${command ? 'Command' : escape(message.toolName || 'Tool result')}</span><span class=\"pui-tool-status${message.status === 'error' ? ' pui-tool-status-error' : ''}\">${escape(status + exit)}</span>${icon('down')}</summary>${details}${output}${gallery}</details>`;\n }\n if (message.role === 'user') return `<article class=\"pui-message pui-user-message\" data-reading-id=\"${escape(message.id)}\" aria-label=\"Your message\"><div class=\"pui-message-content\">${gallery}${text ? `<div class=\"pui-user-text\">${escape(text)}</div>` : ''}${footer}</div></article>`;\n if (message.role === 'system') return `<div class=\"pui-system-message\" data-reading-id=\"${escape(message.id)}\">${escape(text)}</div>`;\n return `<article class=\"pui-message pui-assistant-message\" data-reading-id=\"${escape(message.id)}\" aria-label=\"Agent response\"><div class=\"pui-message-content\"><div class=\"pui-markdown\">${markdown(text, conversationId)}</div>${message.status === 'error' ? '<span class=\"pui-message-error-label\">Response interrupted</span>' : ''}${footer}</div></article>`;\n}\n"]}
@@ -0,0 +1,25 @@
1
+ export interface ImageViewerOptions {
2
+ dialog?: HTMLDialogElement;
3
+ headingId?: string;
4
+ }
5
+ export interface ImageViewerInput {
6
+ src: string;
7
+ alt?: string;
8
+ returnFocus?: HTMLElement;
9
+ }
10
+ /** A modal above either a generated-file preview or a conversation attachment. */
11
+ export declare function createImageViewer(options?: ImageViewerOptions): {
12
+ element: HTMLDialogElement;
13
+ open(target: HTMLImageElement | ImageViewerInput): void;
14
+ close: () => void;
15
+ destroy(): void;
16
+ };
17
+ export declare function installImageViewer(dialog: HTMLDialogElement, options?: {
18
+ triggerRoot?: HTMLElement;
19
+ }): {
20
+ destroy(): void;
21
+ element: HTMLDialogElement;
22
+ open(target: HTMLImageElement | ImageViewerInput): void;
23
+ close: () => void;
24
+ };
25
+ //# sourceMappingURL=image-viewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-viewer.d.ts","sourceRoot":"","sources":["../../src/content/image-viewer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IAAG,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;AACtF,MAAM,WAAW,gBAAgB;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAE;AAG1F,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB;;iBA6CjD,gBAAgB,GAAG,gBAAgB,GAAG,IAAI;iBAHvC,IAAI;eAqBT,IAAI;EAclB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,WAAW,CAAA;CAAO;;;iBAlCxF,gBAAgB,GAAG,gBAAgB,GAAG,IAAI;iBAHvC,IAAI;EAkDvB"}
@@ -0,0 +1,114 @@
1
+ import { escape, icon } from '../dom.js';
2
+ import { installDialogLifecycle } from '../surface.js';
3
+ let sequence = 0;
4
+ /** A modal above either a generated-file preview or a conversation attachment. */
5
+ export function createImageViewer(options = {}) {
6
+ const dialog = options.dialog || document.createElement('dialog');
7
+ const headingId = options.headingId || `pui-image-title-${++sequence}`;
8
+ dialog.classList.add('pui-modal', 'pui-image-viewer');
9
+ dialog.setAttribute('aria-labelledby', headingId);
10
+ dialog.innerHTML = `<div class="pui-modal-heading"><h2 id="${escape(headingId)}">Image</h2><button type="button" class="pui-quiet-icon" data-image-close aria-label="Close image">${icon('close')}</button></div>
11
+ <div class="pui-image-viewer-viewport"><img alt="" /></div>
12
+ <div class="pui-image-viewer-toolbar"><span data-image-scale role="status"></span><div><button type="button" class="pui-secondary-button" data-image-out aria-label="Zoom out">−</button><button type="button" class="pui-secondary-button" data-image-in aria-label="Zoom in">+</button><button type="button" class="pui-secondary-button" data-image-fit>Fit</button><button type="button" class="pui-secondary-button" data-image-original>Original size</button></div></div>`;
13
+ const image = dialog.querySelector('img');
14
+ const viewport = dialog.querySelector('.pui-image-viewer-viewport');
15
+ const title = dialog.querySelector('h2');
16
+ const status = dialog.querySelector('[data-image-scale]');
17
+ const listeners = new AbortController();
18
+ const signal = listeners.signal;
19
+ let destroyed = false;
20
+ let scale = 1, fitted = true, source = null;
21
+ function update() {
22
+ if (destroyed || !image.naturalWidth)
23
+ return;
24
+ if (fitted)
25
+ scale = Math.min(1, viewport.clientWidth / image.naturalWidth, viewport.clientHeight / image.naturalHeight);
26
+ image.style.width = `${image.naturalWidth * scale}px`;
27
+ image.style.height = `${image.naturalHeight * scale}px`;
28
+ status.textContent = `${Math.round(scale * 100)}% · ${image.naturalWidth} × ${image.naturalHeight}`;
29
+ }
30
+ function resize(factor) { fitted = false; const next = scale * factor; if (Number.isFinite(next) && next > 0)
31
+ scale = next; update(); }
32
+ dialog.querySelector('[data-image-fit]').addEventListener('click', () => { fitted = true; update(); }, { signal });
33
+ dialog.querySelector('[data-image-original]').addEventListener('click', () => { fitted = false; scale = 1; update(); }, { signal });
34
+ dialog.querySelector('[data-image-in]').addEventListener('click', () => resize(1.25), { signal });
35
+ dialog.querySelector('[data-image-out]').addEventListener('click', () => resize(1 / 1.25), { signal });
36
+ dialog.querySelector('[data-image-close]').addEventListener('click', () => dialog.close(), { signal });
37
+ image.addEventListener('load', update, { signal });
38
+ const observer = new ResizeObserver(() => { if (dialog.open && fitted)
39
+ update(); });
40
+ observer.observe(viewport);
41
+ const lifecycle = installDialogLifecycle(dialog, { returnFocus: () => source, onClose: () => {
42
+ image.removeAttribute('src');
43
+ image.removeAttribute('style');
44
+ source = null;
45
+ } });
46
+ document.addEventListener('keydown', (event) => {
47
+ if (!dialog.open || event.isComposing || event.metaKey || event.ctrlKey || event.altKey)
48
+ return;
49
+ if (event.key === '+' || event.key === '=') {
50
+ event.preventDefault();
51
+ resize(1.25);
52
+ }
53
+ if (event.key === '-') {
54
+ event.preventDefault();
55
+ resize(1 / 1.25);
56
+ }
57
+ }, { signal });
58
+ function close() { lifecycle.close(); }
59
+ return {
60
+ element: dialog,
61
+ open(target) {
62
+ if (destroyed)
63
+ return;
64
+ const input = target instanceof HTMLImageElement
65
+ ? target.getAttribute('src') && !target.classList.contains('pui-image-unavailable') ? { src: target.src, alt: target.alt, returnFocus: target } : null
66
+ : target;
67
+ if (!input?.src)
68
+ return;
69
+ source = input.returnFocus || null;
70
+ title.textContent = input.alt || 'Image';
71
+ image.alt = input.alt || '';
72
+ scale = 1;
73
+ fitted = true;
74
+ status.textContent = 'Loading…';
75
+ image.src = input.src;
76
+ lifecycle.open();
77
+ viewport.scrollTop = viewport.scrollLeft = 0;
78
+ update();
79
+ },
80
+ close,
81
+ destroy() {
82
+ if (destroyed)
83
+ return;
84
+ close();
85
+ image.removeAttribute('src');
86
+ image.removeAttribute('style');
87
+ source?.focus({ preventScroll: true });
88
+ source = null;
89
+ destroyed = true;
90
+ observer.disconnect();
91
+ lifecycle.destroy();
92
+ listeners.abort();
93
+ dialog.replaceChildren();
94
+ },
95
+ };
96
+ }
97
+ export function installImageViewer(dialog, options = {}) {
98
+ const viewer = createImageViewer({ dialog, headingId: dialog.getAttribute('aria-labelledby') || undefined });
99
+ const abort = new AbortController();
100
+ const trigger = (event) => {
101
+ const image = event.target;
102
+ if (!(image instanceof HTMLImageElement) || !image.matches('.pui-history-image, .pui-draft-image img, .pui-file-preview-image'))
103
+ return;
104
+ if (event instanceof KeyboardEvent && (event.isComposing || !['Enter', ' '].includes(event.key)))
105
+ return;
106
+ if (event instanceof KeyboardEvent)
107
+ event.preventDefault();
108
+ viewer.open(image);
109
+ };
110
+ options.triggerRoot?.addEventListener('click', trigger, { signal: abort.signal });
111
+ options.triggerRoot?.addEventListener('keydown', trigger, { signal: abort.signal });
112
+ return { ...viewer, destroy() { abort.abort(); viewer.destroy(); } };
113
+ }
114
+ //# sourceMappingURL=image-viewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-viewer.js","sourceRoot":"","sources":["../../src/content/image-viewer.ts"],"names":[],"mappings":"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","sourcesContent":["import { escape, icon } from '../dom.js';\nimport { installDialogLifecycle } from '../surface.js';\n\nexport interface ImageViewerOptions { dialog?: HTMLDialogElement; headingId?: string }\nexport interface ImageViewerInput { src: string; alt?: string; returnFocus?: HTMLElement }\nlet sequence = 0;\n\n/** A modal above either a generated-file preview or a conversation attachment. */\nexport function createImageViewer(options: ImageViewerOptions = {}) {\n const dialog = options.dialog || document.createElement('dialog');\n const headingId = options.headingId || `pui-image-title-${++sequence}`;\n dialog.classList.add('pui-modal', 'pui-image-viewer');\n dialog.setAttribute('aria-labelledby', headingId);\n dialog.innerHTML = `<div class=\"pui-modal-heading\"><h2 id=\"${escape(headingId)}\">Image</h2><button type=\"button\" class=\"pui-quiet-icon\" data-image-close aria-label=\"Close image\">${icon('close')}</button></div>\n <div class=\"pui-image-viewer-viewport\"><img alt=\"\" /></div>\n <div class=\"pui-image-viewer-toolbar\"><span data-image-scale role=\"status\"></span><div><button type=\"button\" class=\"pui-secondary-button\" data-image-out aria-label=\"Zoom out\">−</button><button type=\"button\" class=\"pui-secondary-button\" data-image-in aria-label=\"Zoom in\">+</button><button type=\"button\" class=\"pui-secondary-button\" data-image-fit>Fit</button><button type=\"button\" class=\"pui-secondary-button\" data-image-original>Original size</button></div></div>`;\n const image = dialog.querySelector<HTMLImageElement>('img')!;\n const viewport = dialog.querySelector<HTMLElement>('.pui-image-viewer-viewport')!;\n const title = dialog.querySelector<HTMLElement>('h2')!;\n const status = dialog.querySelector<HTMLElement>('[data-image-scale]')!;\n const listeners = new AbortController();\n const signal = listeners.signal;\n let destroyed = false;\n let scale = 1, fitted = true, source: HTMLElement | null = null;\n function update(): void {\n if (destroyed || !image.naturalWidth) return;\n if (fitted) scale = Math.min(1, viewport.clientWidth / image.naturalWidth, viewport.clientHeight / image.naturalHeight);\n image.style.width = `${image.naturalWidth * scale}px`;\n image.style.height = `${image.naturalHeight * scale}px`;\n status.textContent = `${Math.round(scale * 100)}% · ${image.naturalWidth} × ${image.naturalHeight}`;\n }\n function resize(factor: number): void { fitted = false; const next = scale * factor; if (Number.isFinite(next) && next > 0) scale = next; update(); }\n dialog.querySelector('[data-image-fit]')!.addEventListener('click', () => { fitted = true; update(); }, { signal });\n dialog.querySelector('[data-image-original]')!.addEventListener('click', () => { fitted = false; scale = 1; update(); }, { signal });\n dialog.querySelector('[data-image-in]')!.addEventListener('click', () => resize(1.25), { signal });\n dialog.querySelector('[data-image-out]')!.addEventListener('click', () => resize(1 / 1.25), { signal });\n dialog.querySelector('[data-image-close]')!.addEventListener('click', () => dialog.close(), { signal });\n image.addEventListener('load', update, { signal });\n const observer = new ResizeObserver(() => { if (dialog.open && fitted) update(); });\n observer.observe(viewport);\n const lifecycle = installDialogLifecycle(dialog, { returnFocus: () => source, onClose: () => {\n image.removeAttribute('src');\n image.removeAttribute('style');\n source = null;\n } });\n document.addEventListener('keydown', (event) => {\n if (!dialog.open || event.isComposing || event.metaKey || event.ctrlKey || event.altKey) return;\n if (event.key === '+' || event.key === '=') { event.preventDefault(); resize(1.25); }\n if (event.key === '-') { event.preventDefault(); resize(1 / 1.25); }\n }, { signal });\n function close(): void { lifecycle.close(); }\n return {\n element: dialog,\n open(target: HTMLImageElement | ImageViewerInput): void {\n if (destroyed) return;\n const input = target instanceof HTMLImageElement\n ? target.getAttribute('src') && !target.classList.contains('pui-image-unavailable') ? { src: target.src, alt: target.alt, returnFocus: target } : null\n : target;\n if (!input?.src) return;\n source = input.returnFocus || null;\n title.textContent = input.alt || 'Image';\n image.alt = input.alt || '';\n scale = 1;\n fitted = true;\n status.textContent = 'Loading…';\n image.src = input.src;\n lifecycle.open();\n viewport.scrollTop = viewport.scrollLeft = 0;\n update();\n },\n close,\n destroy(): void {\n if (destroyed) return;\n close();\n image.removeAttribute('src');\n image.removeAttribute('style');\n source?.focus({ preventScroll: true });\n source = null;\n destroyed = true;\n observer.disconnect();\n lifecycle.destroy();\n listeners.abort();\n dialog.replaceChildren();\n },\n };\n}\n\nexport function installImageViewer(dialog: HTMLDialogElement, options: { triggerRoot?: HTMLElement } = {}) {\n const viewer = createImageViewer({ dialog, headingId: dialog.getAttribute('aria-labelledby') || undefined });\n const abort = new AbortController();\n const trigger = (event: Event) => {\n const image = event.target;\n if (!(image instanceof HTMLImageElement) || !image.matches('.pui-history-image, .pui-draft-image img, .pui-file-preview-image')) return;\n if (event instanceof KeyboardEvent && (event.isComposing || !['Enter', ' '].includes(event.key))) return;\n if (event instanceof KeyboardEvent) event.preventDefault();\n viewer.open(image);\n };\n options.triggerRoot?.addEventListener('click', trigger, { signal: abort.signal });\n options.triggerRoot?.addEventListener('keydown', trigger, { signal: abort.signal });\n return { ...viewer, destroy() { abort.abort(); viewer.destroy(); } };\n}\n"]}
@@ -0,0 +1,12 @@
1
+ export interface LocalContentLink {
2
+ path: string;
3
+ conversationId: string;
4
+ }
5
+ export interface MarkdownOptions {
6
+ /** The host validates its own file identity and download permissions. */
7
+ resolveLocalLink?: (href: string) => LocalContentLink | null;
8
+ }
9
+ /** Sanitized content only: embedded images, executable markup and forms are excluded. */
10
+ export declare function renderMarkdown(value: string, options?: MarkdownOptions): string;
11
+ export declare function renderDiff(diff: string): string;
12
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/content/markdown.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE;AAC1E,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,gBAAgB,GAAG,IAAI,CAAC;CAC9D;AAED,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAqDnF;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/C"}
@@ -0,0 +1,68 @@
1
+ import DOMPurify from 'dompurify';
2
+ import { marked, Renderer } from 'marked';
3
+ import { escape } from '../dom.js';
4
+ /** Sanitized content only: embedded images, executable markup and forms are excluded. */
5
+ export function renderMarkdown(value, options = {}) {
6
+ const localLinks = new Map();
7
+ const renderer = new Renderer();
8
+ renderer.link = function (token) {
9
+ const local = options.resolveLocalLink?.(token.href);
10
+ if (local) {
11
+ const marker = `#pui-file-${localLinks.size}`;
12
+ localLinks.set(marker, local);
13
+ return Renderer.prototype.link.call(this, { ...token, href: marker });
14
+ }
15
+ return Renderer.prototype.link.call(this, token);
16
+ };
17
+ const html = marked.parse(value, { async: false, breaks: true, renderer });
18
+ const safe = DOMPurify.sanitize(html, {
19
+ FORBID_TAGS: ['img', 'video', 'audio', 'iframe', 'object', 'embed', 'svg', 'math', 'style', 'form', 'input', 'button'],
20
+ FORBID_ATTR: ['style', 'src', 'srcset'],
21
+ ALLOW_DATA_ATTR: false,
22
+ });
23
+ const container = document.createElement('div');
24
+ container.innerHTML = safe;
25
+ for (const link of container.querySelectorAll('a')) {
26
+ const href = link.getAttribute('href') ?? '';
27
+ const local = localLinks.get(href) || options.resolveLocalLink?.(href);
28
+ if (local) {
29
+ link.setAttribute('href', '#');
30
+ link.dataset.downloadPath = local.path;
31
+ link.dataset.downloadConversation = local.conversationId;
32
+ link.title = `Save file: ${local.path}`;
33
+ }
34
+ else if (/^https?:\/\//i.test(href) || /^mailto:/i.test(href)) {
35
+ link.setAttribute('target', '_blank');
36
+ link.setAttribute('rel', 'noopener noreferrer');
37
+ }
38
+ else
39
+ link.replaceWith(...link.childNodes);
40
+ }
41
+ for (const code of container.querySelectorAll('pre > code')) {
42
+ const pre = code.parentElement;
43
+ const block = document.createElement('div');
44
+ block.className = 'pui-code-block';
45
+ const toolbar = document.createElement('div');
46
+ toolbar.className = 'pui-code-toolbar';
47
+ toolbar.dataset.findIgnore = '';
48
+ const language = document.createElement('span');
49
+ language.textContent = [...code.classList].find((name) => name.startsWith('language-'))?.slice(9) || 'Code';
50
+ const copy = document.createElement('button');
51
+ copy.type = 'button';
52
+ copy.className = 'pui-secondary-button pui-code-copy';
53
+ copy.dataset.copyCode = '';
54
+ copy.textContent = 'Copy';
55
+ copy.setAttribute('aria-label', 'Copy code');
56
+ toolbar.append(language, copy);
57
+ pre.replaceWith(block);
58
+ block.append(toolbar, pre);
59
+ }
60
+ return container.innerHTML;
61
+ }
62
+ export function renderDiff(diff) {
63
+ return diff.split('\n').map((line) => {
64
+ const kind = line.startsWith('+++') || line.startsWith('---') || line.startsWith('@@') ? 'meta' : line.startsWith('+') ? 'add' : line.startsWith('-') ? 'remove' : 'context';
65
+ return `<span class="pui-diff-line pui-diff-${kind}">${escape(line) || ' '}</span>`;
66
+ }).join('');
67
+ }
68
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/content/markdown.ts"],"names":[],"mappings":"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","sourcesContent":["import DOMPurify from 'dompurify';\nimport { marked, Renderer } from 'marked';\nimport { escape } from '../dom.js';\n\nexport interface LocalContentLink { path: string; conversationId: string }\nexport interface MarkdownOptions {\n /** The host validates its own file identity and download permissions. */\n resolveLocalLink?: (href: string) => LocalContentLink | null;\n}\n\n/** Sanitized content only: embedded images, executable markup and forms are excluded. */\nexport function renderMarkdown(value: string, options: MarkdownOptions = {}): string {\n const localLinks = new Map<string, LocalContentLink>();\n const renderer = new Renderer();\n renderer.link = function(token) {\n const local = options.resolveLocalLink?.(token.href);\n if (local) {\n const marker = `#pui-file-${localLinks.size}`;\n localLinks.set(marker, local);\n return Renderer.prototype.link.call(this, { ...token, href: marker });\n }\n return Renderer.prototype.link.call(this, token);\n };\n const html = marked.parse(value, { async: false, breaks: true, renderer }) as string;\n const safe = DOMPurify.sanitize(html, {\n FORBID_TAGS: ['img', 'video', 'audio', 'iframe', 'object', 'embed', 'svg', 'math', 'style', 'form', 'input', 'button'],\n FORBID_ATTR: ['style', 'src', 'srcset'],\n ALLOW_DATA_ATTR: false,\n });\n const container = document.createElement('div');\n container.innerHTML = safe;\n for (const link of container.querySelectorAll('a')) {\n const href = link.getAttribute('href') ?? '';\n const local = localLinks.get(href) || options.resolveLocalLink?.(href);\n if (local) {\n link.setAttribute('href', '#');\n link.dataset.downloadPath = local.path;\n link.dataset.downloadConversation = local.conversationId;\n link.title = `Save file: ${local.path}`;\n } else if (/^https?:\\/\\//i.test(href) || /^mailto:/i.test(href)) {\n link.setAttribute('target', '_blank');\n link.setAttribute('rel', 'noopener noreferrer');\n } else link.replaceWith(...link.childNodes);\n }\n for (const code of container.querySelectorAll('pre > code')) {\n const pre = code.parentElement!;\n const block = document.createElement('div');\n block.className = 'pui-code-block';\n const toolbar = document.createElement('div');\n toolbar.className = 'pui-code-toolbar';\n toolbar.dataset.findIgnore = '';\n const language = document.createElement('span');\n language.textContent = [...code.classList].find((name) => name.startsWith('language-'))?.slice(9) || 'Code';\n const copy = document.createElement('button');\n copy.type = 'button';\n copy.className = 'pui-secondary-button pui-code-copy';\n copy.dataset.copyCode = '';\n copy.textContent = 'Copy';\n copy.setAttribute('aria-label', 'Copy code');\n toolbar.append(language, copy);\n pre.replaceWith(block);\n block.append(toolbar, pre);\n }\n return container.innerHTML;\n}\n\nexport function renderDiff(diff: string): string {\n return diff.split('\\n').map((line) => {\n const kind = line.startsWith('+++') || line.startsWith('---') || line.startsWith('@@') ? 'meta' : line.startsWith('+') ? 'add' : line.startsWith('-') ? 'remove' : 'context';\n return `<span class=\"pui-diff-line pui-diff-${kind}\">${escape(line) || ' '}</span>`;\n }).join('');\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import type { UIBlock } from '../core.js';
2
+ export declare const previewPageBytes: number;
3
+ export declare function previewPageRanges(bytes: Uint8Array, window?: number): Array<[number, number]>;
4
+ export type PreviewKind = 'text' | 'markdown' | 'diff';
5
+ export interface PagedPreviewState {
6
+ text: string;
7
+ kind: PreviewKind;
8
+ }
9
+ export interface PagedPreviewOptions extends PagedPreviewState {
10
+ container?: HTMLElement;
11
+ onCopyCode?: (text: string) => void;
12
+ }
13
+ export declare function createPagedPreview(options: PagedPreviewOptions): UIBlock<PagedPreviewState>;
14
+ export declare function mountPagedPreview(container: HTMLElement, text: string, kind: PreviewKind): void;
15
+ //# sourceMappingURL=paged-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paged-preview.d.ts","sourceRoot":"","sources":["../../src/content/paged-preview.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C,eAAO,MAAM,gBAAgB,QAAa,CAAC;AAE3C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,SAAmB,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAgBvG;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AACvD,MAAM,WAAW,iBAAiB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE;AACtE,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAAG,SAAS,CAAC,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE;AAE/H,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0B3F;AAGD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAG/F"}