@pmate/chat-sdk 0.2.3 → 0.3.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/README.md +29 -0
- package/dist/assets/{ChatInput.stories-pjQKncGw.js → ChatInput.stories-BQcIQDVE.js} +232 -24
- package/dist/assets/{Color-AVL7NMMY-CKkkQz33.js → Color-AVL7NMMY-Q9rY0eHd.js} +1 -1
- package/dist/assets/{DocsRenderer-PQXLIZUC-CVx0v9rK.js → DocsRenderer-PQXLIZUC-B5DlnesB.js} +4 -4
- package/dist/assets/{client-CMlGEQEC.js → client-DTiYOHAb.js} +1 -1
- package/dist/assets/{iframe-Bo_J3UyC.js → iframe-1QrxCBqr.js} +3 -3
- package/dist/assets/{index-D9myAQ7a.js → index-CkqBtKlT.js} +1 -1
- package/dist/assets/{react-18-CVKdgGic.js → react-18-6BKf31wF.js} +1 -1
- package/dist/iframe.html +1 -1
- package/dist/index.html +1 -1
- package/dist/index.json +1 -1
- package/dist/project.json +1 -1
- package/lib/components/ChatInputDesktop.js +146 -141
- package/lib/components/ChatInputMobile.js +12 -6
- package/lib/components/MentionMenu.d.ts +15 -0
- package/lib/components/MentionMenu.js +16 -0
- package/lib/hooks/useMentions.d.ts +38 -0
- package/lib/hooks/useMentions.js +83 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/mentions/model.d.ts +28 -0
- package/lib/mentions/model.js +40 -0
- package/lib/runtime/chatClient.d.ts +2 -0
- package/lib/runtime/chatClient.js +1 -1
- package/lib/types/chatInput.d.ts +4 -0
- package/package.json +16 -17
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Host-owned identity; names need not be unique. */
|
|
2
|
+
export type MentionCandidate = {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
avatar?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
};
|
|
8
|
+
/** UTF-16 offsets [start, end) in the sent text, including @. */
|
|
9
|
+
export type ChatMention = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
};
|
|
15
|
+
export type MentionQuery = {
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
query: string;
|
|
19
|
+
};
|
|
20
|
+
export type MentionConfig = {
|
|
21
|
+
candidates: MentionCandidate[];
|
|
22
|
+
label?: string;
|
|
23
|
+
emptyText?: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function findMentionQuery(text: string, caret: number): MentionQuery | null;
|
|
26
|
+
export declare function filterMentionCandidates(candidates: MentionCandidate[], query: string): MentionCandidate[];
|
|
27
|
+
/** Rebase untouched mentions; editing any part of a mention removes its identity. */
|
|
28
|
+
export declare function rebaseMentions(previous: string, next: string, mentions: ChatMention[]): ChatMention[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function findMentionQuery(text, caret) {
|
|
2
|
+
const before = text.slice(0, caret);
|
|
3
|
+
// Require a word boundary so email addresses and URLs remain ordinary text.
|
|
4
|
+
const match = /(?:^|[\s([{,。!?、:;])@([^\s@]{0,64})$/u.exec(before);
|
|
5
|
+
if (!match)
|
|
6
|
+
return null;
|
|
7
|
+
return { start: caret - match[1].length - 1, end: caret, query: match[1] };
|
|
8
|
+
}
|
|
9
|
+
export function filterMentionCandidates(candidates, query) {
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
return candidates.filter(candidate => {
|
|
12
|
+
if (seen.has(candidate.id))
|
|
13
|
+
return false;
|
|
14
|
+
seen.add(candidate.id);
|
|
15
|
+
return candidate.name.toLocaleLowerCase().includes(query.toLocaleLowerCase());
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Rebase untouched mentions; editing any part of a mention removes its identity. */
|
|
19
|
+
export function rebaseMentions(previous, next, mentions) {
|
|
20
|
+
if (previous === next)
|
|
21
|
+
return mentions;
|
|
22
|
+
let start = 0;
|
|
23
|
+
while (start < previous.length && start < next.length && previous[start] === next[start])
|
|
24
|
+
start++;
|
|
25
|
+
let oldEnd = previous.length, newEnd = next.length;
|
|
26
|
+
while (oldEnd > start && newEnd > start && previous[oldEnd - 1] === next[newEnd - 1]) {
|
|
27
|
+
oldEnd--;
|
|
28
|
+
newEnd--;
|
|
29
|
+
}
|
|
30
|
+
const delta = newEnd - oldEnd;
|
|
31
|
+
return mentions.flatMap(mention => {
|
|
32
|
+
if (mention.end <= start)
|
|
33
|
+
return [mention];
|
|
34
|
+
if (mention.start >= oldEnd)
|
|
35
|
+
return [{ ...mention, start: mention.start + delta, end: mention.end + delta }];
|
|
36
|
+
return [];
|
|
37
|
+
}).filter(mention => next.slice(mention.start, mention.end) === `@${mention.name}`
|
|
38
|
+
&& (mention.start === 0 || /[\s([{,。!?、:;]/u.test(next[mention.start - 1]))
|
|
39
|
+
&& (mention.end === next.length || !/[\p{L}\p{N}_@]/u.test(next[mention.end])));
|
|
40
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ChatMention } from "../mentions/model";
|
|
1
2
|
import { type BotLoginSession } from "./botAuth.js";
|
|
2
3
|
import { type PmateBotKeyFile } from "./botKey.js";
|
|
3
4
|
import { type ChatBody, type ChatRuntimeMessage, type RealtimeClient } from "./protocol.js";
|
|
@@ -55,6 +56,7 @@ export declare class ChatClient {
|
|
|
55
56
|
groupId: string;
|
|
56
57
|
text: string;
|
|
57
58
|
recipients?: string[];
|
|
59
|
+
mentions?: ChatMention[];
|
|
58
60
|
}): Promise<ChatRuntimeMessage>;
|
|
59
61
|
sendChat(to: string, body: ChatBody, recipients?: string[]): Promise<ChatRuntimeMessage>;
|
|
60
62
|
private sendContactAction;
|
|
@@ -104,7 +104,7 @@ export class ChatClient {
|
|
|
104
104
|
kind: "message",
|
|
105
105
|
threadKind: "group",
|
|
106
106
|
contentKind: "text",
|
|
107
|
-
content: { appId: this.options.appId, groupId: input.groupId, text: input.text },
|
|
107
|
+
content: { appId: this.options.appId, groupId: input.groupId, text: input.text, ...(input.mentions?.length ? { mentions: input.mentions } : {}) },
|
|
108
108
|
}, input.recipients);
|
|
109
109
|
}
|
|
110
110
|
async sendChat(to, body, recipients) {
|
package/lib/types/chatInput.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MentionConfig, ChatMention } from "../mentions/model";
|
|
1
2
|
import type { ClipboardEventHandler, ComponentType, DragEventHandler, ReactNode } from "react";
|
|
2
3
|
import type { ChatAttachment } from "./attachment";
|
|
3
4
|
import type { ChatVoiceConfig } from "./voice";
|
|
@@ -120,6 +121,8 @@ export type ChatImageConfig = {
|
|
|
120
121
|
uploadImage?: (file: File) => Promise<ChatImageUploadResult>;
|
|
121
122
|
};
|
|
122
123
|
export type ChatInputBaseProps = {
|
|
124
|
+
/** Group members supplied by the host; omit for ordinary text input. */
|
|
125
|
+
mentions?: MentionConfig;
|
|
123
126
|
/** Use a taller composer with actions in a bottom toolbar. */
|
|
124
127
|
multiline?: boolean;
|
|
125
128
|
value?: string;
|
|
@@ -131,6 +134,7 @@ export type ChatInputBaseProps = {
|
|
|
131
134
|
onDrop?: DragEventHandler<HTMLTextAreaElement>;
|
|
132
135
|
onSend?: (payload: {
|
|
133
136
|
text: string;
|
|
137
|
+
mentions?: ChatMention[];
|
|
134
138
|
attachments: ChatAttachment[];
|
|
135
139
|
transcript?: string;
|
|
136
140
|
audio?: Blob;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmate/chat-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Cross-platform PMate chat input components, voice input primitives, and ASR helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -29,15 +29,6 @@
|
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
|
-
"scripts": {
|
|
33
|
-
"clean": "rimraf lib",
|
|
34
|
-
"build": "pnpm run clean && tsc -p tsconfig.build.json && cp src/voice/recorder-processor.js lib/voice/recorder-processor.js",
|
|
35
|
-
"prepack": "pnpm run build",
|
|
36
|
-
"npm:publish": "pnpm run build && npm publish --access public --registry https://registry.npmjs.org/",
|
|
37
|
-
"test": "vitest run",
|
|
38
|
-
"storybook": "storybook dev -p 6009",
|
|
39
|
-
"build-storybook": "storybook build --output-dir dist"
|
|
40
|
-
},
|
|
41
32
|
"dependencies": {
|
|
42
33
|
"@noble/hashes": "^2.4.0",
|
|
43
34
|
"@emoji-mart/data": "^1.2.1",
|
|
@@ -52,15 +43,23 @@
|
|
|
52
43
|
"devDependencies": {
|
|
53
44
|
"@storybook/addon-docs": "^9.0.17",
|
|
54
45
|
"@storybook/react-vite": "9.0.17",
|
|
55
|
-
"@tailwindcss/vite": "
|
|
56
|
-
"@types/react": "
|
|
57
|
-
"@types/react-dom": "
|
|
58
|
-
"react": "
|
|
59
|
-
"react-dom": "
|
|
46
|
+
"@tailwindcss/vite": "^4.1.3",
|
|
47
|
+
"@types/react": "^19.2.7",
|
|
48
|
+
"@types/react-dom": "^19.2.3",
|
|
49
|
+
"react": "^19.1.1",
|
|
50
|
+
"react-dom": "^19.1.1",
|
|
60
51
|
"rimraf": "^6.1.2",
|
|
61
52
|
"storybook": "^9.0.17",
|
|
62
|
-
"tailwindcss": "
|
|
53
|
+
"tailwindcss": "^4.0.14",
|
|
63
54
|
"typescript": "5.9.2",
|
|
64
55
|
"vitest": "^4.0.17"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"clean": "rimraf lib",
|
|
59
|
+
"build": "pnpm run clean && tsc -p tsconfig.build.json && cp src/voice/recorder-processor.js lib/voice/recorder-processor.js",
|
|
60
|
+
"npm:publish": "pnpm run build && npm publish --access public --registry https://registry.npmjs.org/",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"storybook": "storybook dev -p 6009",
|
|
63
|
+
"build-storybook": "storybook build --output-dir dist"
|
|
65
64
|
}
|
|
66
|
-
}
|
|
65
|
+
}
|