anentrypoint-design 0.0.230 → 0.0.232
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/app-shell.css +3 -2
- package/chat.css +14 -14
- package/dist/247420.css +17 -16
- package/dist/247420.js +15 -15
- package/package.json +1 -1
- package/src/components/agent-chat.js +9 -1
- package/src/components/chat.js +33 -0
- package/src/components/overlay-primitives.js +37 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.232",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
|
@@ -261,7 +261,15 @@ export function AgentChat(props = {}) {
|
|
|
261
261
|
if (!isStreaming && msgHasBody(m)) {
|
|
262
262
|
const built = [];
|
|
263
263
|
if (onCopyMessage) built.push({ label: 'copy', icon: 'copy', title: 'copy message', onClick: () => onCopyMessage(m) });
|
|
264
|
-
|
|
264
|
+
// Mid-thread retry: EVERY settled assistant turn gets a retry action,
|
|
265
|
+
// not only the trailing one - the host truncates from that turn's
|
|
266
|
+
// position and resends (the same mechanism edit-and-resend uses for
|
|
267
|
+
// user messages), so any assistant reply the user was unhappy with can
|
|
268
|
+
// be redone without discarding turns that came after a LATER one.
|
|
269
|
+
if (isAssistant && onRetryMessage) built.push({ label: 'retry', icon: 'refresh', title: 'retry this turn', onClick: () => onRetryMessage(m) });
|
|
270
|
+
// A dangling user message (send failed / no reply arrived) can only be
|
|
271
|
+
// the LAST message when it has no assistant reply - retry here means
|
|
272
|
+
// "resend as-is", not "redo a specific turn", so stays lastIdx-gated.
|
|
265
273
|
if (!isAssistant && onRetryMessage && i === lastIdx) built.push({ label: 'retry', icon: 'refresh', title: 'retry', onClick: () => onRetryMessage(m) });
|
|
266
274
|
// With confirmEdit the host arms its own confirm affordance (onArmEdit)
|
|
267
275
|
// instead of resending immediately; the kit stays stateless either way.
|
package/src/components/chat.js
CHANGED
|
@@ -8,6 +8,12 @@ import { isDegraded as isMarkdownDegraded } from '../markdown.js';
|
|
|
8
8
|
import { register } from '../debug.js';
|
|
9
9
|
import { Icon } from './shell.js';
|
|
10
10
|
import { fmtFileSize } from './files.js';
|
|
11
|
+
import { EmojiPicker } from './overlay-primitives.js';
|
|
12
|
+
|
|
13
|
+
// Matches a trailing `:keyword` at the end of the composer draft (optionally
|
|
14
|
+
// preceded by whitespace/start-of-string) so typing `:smile` opens an inline
|
|
15
|
+
// filtered EmojiPicker without requiring the toolbar button or Ctrl+;.
|
|
16
|
+
const EMOJI_TRIGGER_RE = /(?:^|\s)(:([a-zA-Z0-9_+-]{0,24}))$/;
|
|
11
17
|
|
|
12
18
|
const h = webjsx.createElement;
|
|
13
19
|
let _stats = { messages: 0, lastKindCounts: {} };
|
|
@@ -447,6 +453,24 @@ export function ChatComposer({ value, onInput, onSend, onAttach, onEmoji, onMenu
|
|
|
447
453
|
if (!v || disabled) return;
|
|
448
454
|
if (onSend) onSend(v);
|
|
449
455
|
};
|
|
456
|
+
const triggerMatch = EMOJI_TRIGGER_RE.exec(value || '');
|
|
457
|
+
// taEl is only assigned by taRef during DOM diffing, which happens AFTER
|
|
458
|
+
// this render function returns — so on first paint of a trigger it is
|
|
459
|
+
// still null here. Fall back to the live DOM textarea from the previous
|
|
460
|
+
// paint (same composer, content patched in place) so the picker anchors
|
|
461
|
+
// near the input instead of the viewport origin.
|
|
462
|
+
const anchorEl = taEl || (typeof document !== 'undefined' ? document.querySelector('.chat-composer textarea') : null);
|
|
463
|
+
const insertEmoji = (ch) => {
|
|
464
|
+
const v = (taEl && taEl.value) || value || '';
|
|
465
|
+
const m = EMOJI_TRIGGER_RE.exec(v);
|
|
466
|
+
const next = m ? (v.slice(0, m.index) + (m[0].startsWith(':') ? '' : v[m.index]) + ch + ' ') : (v + ch);
|
|
467
|
+
if (onInput) onInput(next);
|
|
468
|
+
if (taEl) {
|
|
469
|
+
taEl.value = next;
|
|
470
|
+
taEl.focus();
|
|
471
|
+
taEl.selectionStart = taEl.selectionEnd = next.length;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
450
474
|
let autoGrowScheduled = false;
|
|
451
475
|
const autoGrow = (e) => {
|
|
452
476
|
const ta = e.target;
|
|
@@ -517,6 +541,14 @@ export function ChatComposer({ value, onInput, onSend, onAttach, onEmoji, onMenu
|
|
|
517
541
|
}, joined);
|
|
518
542
|
}
|
|
519
543
|
const hasDraft = !!(value && value.trim());
|
|
544
|
+
const triggerPicker = triggerMatch ? EmojiPicker({
|
|
545
|
+
open: true,
|
|
546
|
+
anchorX: (anchorEl && anchorEl.getBoundingClientRect) ? anchorEl.getBoundingClientRect().left : 0,
|
|
547
|
+
anchorY: (anchorEl && anchorEl.getBoundingClientRect) ? anchorEl.getBoundingClientRect().top : 0,
|
|
548
|
+
query: triggerMatch[2] || '',
|
|
549
|
+
onSelect: (ch) => insertEmoji(ch),
|
|
550
|
+
onClose: () => { if (taEl) { const v = taEl.value.replace(EMOJI_TRIGGER_RE, (full, tail) => full.slice(0, full.length - tail.length)); if (onInput) onInput(v); taEl.value = v; taEl.focus(); } },
|
|
551
|
+
}) : null;
|
|
520
552
|
return h('div', {
|
|
521
553
|
class: 'chat-composer' + (hasDraft ? ' has-draft' : '') + (disabled ? ' is-disabled' : ''),
|
|
522
554
|
// A drop on the composer must NEVER navigate the browser away from the
|
|
@@ -535,6 +567,7 @@ export function ChatComposer({ value, onInput, onSend, onAttach, onEmoji, onMenu
|
|
|
535
567
|
},
|
|
536
568
|
},
|
|
537
569
|
contextLine,
|
|
570
|
+
triggerPicker,
|
|
538
571
|
h('textarea', { ref: taRef, placeholder, rows: 1,
|
|
539
572
|
'aria-label': label || (disabled && disabledReason ? 'message input — ' + disabledReason : 'message input'),
|
|
540
573
|
disabled: !!disabled, 'aria-disabled': disabled ? 'true' : null,
|
|
@@ -375,14 +375,38 @@ export function CommandPalette({ open, items = [], onSelect, onClose } = {}) {
|
|
|
375
375
|
// per-emoji <button> labels below. This is intentional product content, not
|
|
376
376
|
// decorative chrome.
|
|
377
377
|
const EMOJI_CATEGORIES = [
|
|
378
|
-
{ id: 'smileys', label: '😀', emoji: [
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
378
|
+
{ id: 'smileys', label: '😀', emoji: [
|
|
379
|
+
['😀', 'grinning smile'], ['😁', 'grinning smile happy'], ['😂', 'joy tears laugh'], ['🤣', 'rofl laugh'],
|
|
380
|
+
['😊', 'smile blush happy'], ['😍', 'heart eyes love'], ['😘', 'kiss'], ['😎', 'cool sunglasses'],
|
|
381
|
+
['🤔', 'thinking'], ['😅', 'sweat smile'], ['😉', 'wink'], ['🙂', 'smile slight'],
|
|
382
|
+
['😇', 'angel innocent'], ['🥳', 'party'], ['😴', 'sleep'], ['🤩', 'starstruck'],
|
|
383
|
+
['😜', 'wink tongue'], ['😢', 'cry sad'], ['😭', 'sob cry'], ['😡', 'angry mad'],
|
|
384
|
+
['😱', 'scream shock'], ['🥺', 'pleading'], ['😤', 'huff'], ['😬', 'grimace'],
|
|
385
|
+
] },
|
|
386
|
+
{ id: 'gestures', label: '👍', emoji: [
|
|
387
|
+
['👍', 'thumbsup yes good'], ['👎', 'thumbsdown no bad'], ['👌', 'ok'], ['✌️', 'peace'],
|
|
388
|
+
['🤞', 'fingers crossed'], ['🙏', 'pray thanks'], ['👏', 'clap'], ['🙌', 'raised hands'],
|
|
389
|
+
['💪', 'muscle strong'], ['👀', 'eyes look'], ['🤝', 'handshake'], ['✋', 'hand stop'],
|
|
390
|
+
['🤙', 'call'], ['👋', 'wave hi bye'], ['🤟', 'love you'], ['☝️', 'point up'],
|
|
391
|
+
] },
|
|
392
|
+
{ id: 'hearts', label: '❤️', emoji: [
|
|
393
|
+
['❤️', 'heart love red'], ['🧡', 'heart orange'], ['💛', 'heart yellow'], ['💚', 'heart green'],
|
|
394
|
+
['💙', 'heart blue'], ['💜', 'heart purple'], ['🖤', 'heart black'], ['🤍', 'heart white'],
|
|
395
|
+
['💔', 'broken heart'], ['💕', 'hearts'], ['💖', 'sparkling heart'], ['💗', 'growing heart'],
|
|
396
|
+
] },
|
|
397
|
+
{ id: 'symbols', label: '✅', emoji: [
|
|
398
|
+
['🔥', 'fire lit'], ['💯', 'hundred'], ['✅', 'check yes done'], ['❌', 'cross no'],
|
|
399
|
+
['⭐', 'star'], ['🎉', 'party tada'], ['🎊', 'confetti'], ['✨', 'sparkles'],
|
|
400
|
+
['💡', 'idea lightbulb'], ['⚡', 'zap lightning'], ['💢', 'anger'], ['💀', 'skull dead'],
|
|
401
|
+
['🚀', 'rocket launch'], ['🏆', 'trophy win'],
|
|
402
|
+
] },
|
|
382
403
|
];
|
|
404
|
+
const ALL_EMOJI = EMOJI_CATEGORIES.flatMap((c) => c.emoji);
|
|
383
405
|
|
|
384
406
|
// EmojiPicker — fixed popover near (anchorX, anchorY) with category tabs + grid.
|
|
385
|
-
|
|
407
|
+
// `query`, when non-empty, filters across all categories by name/keyword
|
|
408
|
+
// substring match (case-insensitive) instead of showing the active tab.
|
|
409
|
+
export function EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose, query = '' } = {}) {
|
|
386
410
|
if (!open) return null;
|
|
387
411
|
let cat = EMOJI_CATEGORIES[0].id;
|
|
388
412
|
let rootEl = null, gridEl = null;
|
|
@@ -390,12 +414,15 @@ export function EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose
|
|
|
390
414
|
|
|
391
415
|
const renderGrid = () => {
|
|
392
416
|
if (!gridEl) return;
|
|
393
|
-
const
|
|
417
|
+
const q = (query || '').trim().toLowerCase();
|
|
418
|
+
const cells = q
|
|
419
|
+
? ALL_EMOJI.filter(([, name]) => name.toLowerCase().includes(q))
|
|
420
|
+
: (EMOJI_CATEGORIES.find(x => x.id === cat) || EMOJI_CATEGORIES[0]).emoji;
|
|
394
421
|
webjsx.applyDiff(gridEl, h('div', { class: 'ov-emoji-grid-inner' },
|
|
395
|
-
|
|
396
|
-
type: 'button', class: 'ov-emoji-cell', 'aria-label': ch,
|
|
422
|
+
cells.length ? cells.map(([ch, name]) => h('button', {
|
|
423
|
+
type: 'button', class: 'ov-emoji-cell', 'aria-label': name || ch, title: name || ch,
|
|
397
424
|
onclick: () => { if (onSelect) onSelect(ch); },
|
|
398
|
-
}, ch))));
|
|
425
|
+
}, ch)) : h('div', { class: 'ov-emoji-empty' }, 'no emoji found')));
|
|
399
426
|
};
|
|
400
427
|
|
|
401
428
|
const tabNavKey = (e) => {
|
|
@@ -420,7 +447,7 @@ export function EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose
|
|
|
420
447
|
el._ovEmojiCleanup = _anchoredOverlayLifecycle(el, { anchorX, anchorY, fallbackW: 260, fallbackH: 240, close });
|
|
421
448
|
},
|
|
422
449
|
},
|
|
423
|
-
h('div', { class: 'ov-emoji-tabs', role: 'tablist' },
|
|
450
|
+
(query || '').trim() ? null : h('div', { class: 'ov-emoji-tabs', role: 'tablist' },
|
|
424
451
|
...EMOJI_CATEGORIES.map((c) => h('button', {
|
|
425
452
|
type: 'button', class: 'ov-emoji-tab', role: 'tab',
|
|
426
453
|
'aria-selected': c.id === cat ? 'true' : 'false',
|