@ayni.org/ayni-ui-core 0.3.6 → 0.3.7

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.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './conversationDesign.js';
4
4
  export * from './directory.js';
5
5
  export * from './flag.js';
6
6
  export * from './homeDesign.js';
7
+ export * from './mediaMessages.js';
7
8
  export * from './profileClient.js';
8
9
  export * from './proofOfTrustDesign.js';
9
10
  export * from './readState.js';
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export * from './conversationDesign.js';
4
4
  export * from './directory.js';
5
5
  export * from './flag.js';
6
6
  export * from './homeDesign.js';
7
+ export * from './mediaMessages.js';
7
8
  export * from './profileClient.js';
8
9
  export * from './proofOfTrustDesign.js';
9
10
  export * from './readState.js';
@@ -0,0 +1,28 @@
1
+ import type { ReplyQuote, TranscriptMessage } from './types.js';
2
+ type Attachment = Extract<TranscriptMessage, {
3
+ role: 'activity';
4
+ }>;
5
+ /** Whether a transcript message is an attachment of a media message. */
6
+ export declare function isAttachment(message: TranscriptMessage): message is Attachment;
7
+ /** The id the agent knows a message under: a text by its own, a media message by its entry's. */
8
+ export declare function targetIdOf(message: TranscriptMessage): string;
9
+ /** What a media message is, for the quote of a reply and for its menu. */
10
+ export type MediaKind = 'image' | 'voice' | 'video' | 'file';
11
+ export declare function mediaKindOf(message: Attachment): MediaKind;
12
+ /** The quote a reply to any message carries (PROF-RUN-009): a text by its words, a media message by what it is. */
13
+ export declare function quoteOf(message: Exclude<TranscriptMessage, {
14
+ role: 'system';
15
+ }>): ReplyQuote;
16
+ /** The English names of a media message; a UI with its own languages translates the kind. */
17
+ export declare const MEDIA_COPY: Record<MediaKind, string>;
18
+ /**
19
+ * What a quote shows: the words of the quoted message, else what it is. A file is named by its file name; `name`
20
+ * gives the word for a kind in the UI's language.
21
+ */
22
+ export declare function quoteLabel(quote: ReplyQuote, name?: (kind: MediaKind) => string): string;
23
+ /**
24
+ * The last attachment of each media message: the one that shows the message's reactions when the message carries
25
+ * several (UI-CONV-024). Keyed by the message's entry.
26
+ */
27
+ export declare function lastAttachments(messages: readonly TranscriptMessage[]): Map<string, string>;
28
+ export {};
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Reactions and replies on a media message (UI-CONV-024, APP-MSG-005, PROF-RUN-009/010): a picture, a video, a
3
+ * file or a voice note is a message like a text. The agent knows it by the id of its entry, which each of its
4
+ * attachments carries, never by the id of an attachment: a reaction or a reply names that entry, and the
5
+ * reactions of the message are read under it. A reply quotes a media message by what it is.
6
+ */
7
+ import { ACTIVITY } from '@ayni.org/ayni-agui-profile';
8
+ import { plainText, REPLY_QUOTE_LENGTH } from './conversationDesign.js';
9
+ /** Whether a transcript message is an attachment of a media message. */
10
+ export function isAttachment(message) {
11
+ return message.role === 'activity' && message.activityType === ACTIVITY.attachment;
12
+ }
13
+ /** The id the agent knows a message under: a text by its own, a media message by its entry's. */
14
+ export function targetIdOf(message) {
15
+ return isAttachment(message) ? (message.entryId ?? message.id) : message.id;
16
+ }
17
+ export function mediaKindOf(message) {
18
+ const type = message.content.type;
19
+ return type === 'image' || type === 'voice' || type === 'video' ? type : 'file';
20
+ }
21
+ /** The words a media message has of its own: a caption, a transcription; not the agent's placeholder for it. */
22
+ function ownWords(textRepr) {
23
+ const text = typeof textRepr === 'string' ? textRepr.trim() : '';
24
+ return /^\[[^\]]*\]$/.test(text) ? '' : text;
25
+ }
26
+ /** The quote a reply to any message carries (PROF-RUN-009): a text by its words, a media message by what it is. */
27
+ export function quoteOf(message) {
28
+ if (message.role !== 'activity')
29
+ return {
30
+ id: message.id,
31
+ text: plainText(message.content).slice(0, REPLY_QUOTE_LENGTH),
32
+ role: message.role === 'user' ? 'user' : 'assistant',
33
+ };
34
+ const content = message.content;
35
+ return {
36
+ id: targetIdOf(message),
37
+ text: ownWords(content.textRepr).slice(0, REPLY_QUOTE_LENGTH),
38
+ role: content.role === 'user' ? 'user' : 'assistant',
39
+ media: mediaKindOf(message),
40
+ ...(content.filename ? { filename: content.filename } : {}),
41
+ };
42
+ }
43
+ /** The English names of a media message; a UI with its own languages translates the kind. */
44
+ export const MEDIA_COPY = {
45
+ image: 'Photo',
46
+ voice: 'Voice note',
47
+ video: 'Video',
48
+ file: 'File',
49
+ };
50
+ /**
51
+ * What a quote shows: the words of the quoted message, else what it is. A file is named by its file name; `name`
52
+ * gives the word for a kind in the UI's language.
53
+ */
54
+ export function quoteLabel(quote, name = (kind) => MEDIA_COPY[kind]) {
55
+ if (quote.text)
56
+ return quote.text;
57
+ if (!quote.media)
58
+ return '';
59
+ return quote.media === 'file' && quote.filename ? quote.filename : name(quote.media);
60
+ }
61
+ /**
62
+ * The last attachment of each media message: the one that shows the message's reactions when the message carries
63
+ * several (UI-CONV-024). Keyed by the message's entry.
64
+ */
65
+ export function lastAttachments(messages) {
66
+ const last = new Map();
67
+ for (const m of messages)
68
+ if (isAttachment(m))
69
+ last.set(targetIdOf(m), m.id);
70
+ return last;
71
+ }
package/dist/reducer.js CHANGED
@@ -287,8 +287,11 @@ export function reactionsOf(page) {
287
287
  const out = {};
288
288
  for (const m of page) {
289
289
  const meta = (m.metadata ?? {});
290
+ // the reactions of a media message are those of its entry, which each of its attachments carries: they are
291
+ // kept under the entry, where a live `ayni.reaction` names it too (UI-CONV-024)
292
+ const key = m.role === 'activity' && meta.entryId ? meta.entryId : String(m.id);
290
293
  if (meta.reactions?.length)
291
- out[String(m.id)] = meta.reactions.map((r) => ({ emoji: r.emoji, by: r.by }));
294
+ out[key] = meta.reactions.map((r) => ({ emoji: r.emoji, by: r.by }));
292
295
  }
293
296
  return out;
294
297
  }
package/dist/types.d.ts CHANGED
@@ -106,8 +106,12 @@ export interface SessionRecord {
106
106
  }
107
107
  export interface ReplyQuote {
108
108
  id: string;
109
+ /** the words of the quoted message; empty for a media message that has none of its own */
109
110
  text: string;
110
111
  role: 'user' | 'assistant';
112
+ /** what the quoted message is, when it is a media message (PROF-RUN-009) */
113
+ media?: 'image' | 'voice' | 'video' | 'file';
114
+ filename?: string;
111
115
  }
112
116
  export type TranscriptMessage = {
113
117
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ayni.org/ayni-ui-core",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Framework-independent core of the Ayni UI (UI-SHELL-004): agents and controllers, trust resolution, sessions and binding, the conversation reducer, the local store and sync; shared by the browser UI and the Ayni app",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",