@wopr-network/platform-ui-core 1.1.10 → 1.1.11
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/package.json
CHANGED
|
@@ -2,11 +2,12 @@ import { render, screen } from "@testing-library/react";
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { ChatMessage } from "@/components/chat/chat-message";
|
|
4
4
|
import type { ChatMessage as ChatMessageType } from "@/lib/chat/types";
|
|
5
|
+
import { uuid } from "@/lib/uuid";
|
|
5
6
|
|
|
6
7
|
function msg(
|
|
7
8
|
overrides: Partial<ChatMessageType> & { role: ChatMessageType["role"]; content: string },
|
|
8
9
|
): ChatMessageType {
|
|
9
|
-
return { id:
|
|
10
|
+
return { id: uuid(), timestamp: Date.now(), ...overrides };
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
describe("ChatMessage", () => {
|
|
@@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
|
|
|
3
3
|
import { beforeAll, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { ChatPanel } from "@/components/chat/chat-panel";
|
|
5
5
|
import type { ChatMessage as ChatMessageType } from "@/lib/chat/types";
|
|
6
|
+
import { uuid } from "@/lib/uuid";
|
|
6
7
|
|
|
7
8
|
// jsdom does not implement scrollIntoView — polyfill it for ChatPanel's auto-scroll useEffect
|
|
8
9
|
beforeAll(() => {
|
|
@@ -12,7 +13,7 @@ beforeAll(() => {
|
|
|
12
13
|
function msg(
|
|
13
14
|
overrides: Partial<ChatMessageType> & { role: ChatMessageType["role"]; content: string },
|
|
14
15
|
): ChatMessageType {
|
|
15
|
-
return { id:
|
|
16
|
+
return { id: uuid(), timestamp: Date.now(), ...overrides };
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const baseProps = {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { apiFetch, apiFetchRaw } from "@/lib/api";
|
|
5
5
|
import type { ChatMessage } from "@/lib/chat/types";
|
|
6
|
+
import { uuid } from "@/lib/uuid";
|
|
6
7
|
|
|
7
8
|
interface PluginSetupState {
|
|
8
9
|
isOpen: boolean;
|
|
@@ -74,7 +75,7 @@ export function usePluginSetupChat(
|
|
|
74
75
|
const controller = new AbortController();
|
|
75
76
|
abortRef.current = controller;
|
|
76
77
|
|
|
77
|
-
const sessionId =
|
|
78
|
+
const sessionId = uuid();
|
|
78
79
|
sessionIdRef.current = sessionId;
|
|
79
80
|
|
|
80
81
|
const params = new URLSearchParams({ pluginId, botId, sessionId });
|
|
@@ -125,7 +126,7 @@ export function usePluginSetupChat(
|
|
|
125
126
|
messages: [
|
|
126
127
|
...s.messages,
|
|
127
128
|
{
|
|
128
|
-
id:
|
|
129
|
+
id: uuid(),
|
|
129
130
|
role: "bot" as const,
|
|
130
131
|
content: event.delta,
|
|
131
132
|
timestamp: Date.now(),
|
|
@@ -144,7 +145,7 @@ export function usePluginSetupChat(
|
|
|
144
145
|
messages: [
|
|
145
146
|
...s.messages,
|
|
146
147
|
{
|
|
147
|
-
id:
|
|
148
|
+
id: uuid(),
|
|
148
149
|
role: "event" as const,
|
|
149
150
|
content: reason,
|
|
150
151
|
timestamp: Date.now(),
|
|
@@ -186,7 +187,7 @@ export function usePluginSetupChat(
|
|
|
186
187
|
messages: [
|
|
187
188
|
...s.messages,
|
|
188
189
|
{
|
|
189
|
-
id:
|
|
190
|
+
id: uuid(),
|
|
190
191
|
role: "user" as const,
|
|
191
192
|
content: text,
|
|
192
193
|
timestamp: Date.now(),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { storageKey } from "../brand-config";
|
|
3
|
+
import { uuid } from "../uuid";
|
|
3
4
|
import type { ChatMessage } from "./types";
|
|
4
5
|
|
|
5
6
|
const HISTORY_KEY = storageKey("chat-history");
|
|
@@ -23,12 +24,12 @@ export function getSessionId(): string {
|
|
|
23
24
|
try {
|
|
24
25
|
const existing = localStorage.getItem(SESSION_KEY);
|
|
25
26
|
if (existing) return existing;
|
|
26
|
-
const id =
|
|
27
|
+
const id = uuid();
|
|
27
28
|
localStorage.setItem(SESSION_KEY, id);
|
|
28
29
|
return id;
|
|
29
30
|
} catch {
|
|
30
31
|
// localStorage blocked (private browsing) — use ephemeral session ID
|
|
31
|
-
return
|
|
32
|
+
return uuid();
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
package/src/lib/chat/use-chat.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { openChatStream, sendChatMessage } from "@/lib/api";
|
|
5
5
|
import { eventName } from "@/lib/brand-config";
|
|
6
|
+
import { uuid } from "@/lib/uuid";
|
|
6
7
|
import { clearChatHistory, getSessionId, loadChatHistory, saveChatHistory } from "./chat-store";
|
|
7
8
|
import type { ChatEvent, ChatMessage, ChatMode } from "./types";
|
|
8
9
|
|
|
@@ -76,7 +77,7 @@ export function useChat(): UseChatReturn {
|
|
|
76
77
|
if (data.type === "text") {
|
|
77
78
|
setIsTyping(true);
|
|
78
79
|
if (pendingBotMsgRef.current === null) {
|
|
79
|
-
pendingBotMsgRef.current =
|
|
80
|
+
pendingBotMsgRef.current = uuid();
|
|
80
81
|
}
|
|
81
82
|
const msgId = pendingBotMsgRef.current;
|
|
82
83
|
setMessages((prev) => {
|
|
@@ -105,7 +106,7 @@ export function useChat(): UseChatReturn {
|
|
|
105
106
|
setIsTyping(false);
|
|
106
107
|
pendingBotMsgRef.current = null;
|
|
107
108
|
addMessage({
|
|
108
|
-
id:
|
|
109
|
+
id: uuid(),
|
|
109
110
|
role: "bot",
|
|
110
111
|
content: `Error: ${data.message}`,
|
|
111
112
|
timestamp: Date.now(),
|
|
@@ -165,7 +166,7 @@ export function useChat(): UseChatReturn {
|
|
|
165
166
|
if (!trimmed) return;
|
|
166
167
|
|
|
167
168
|
const userMsg: ChatMessage = {
|
|
168
|
-
id:
|
|
169
|
+
id: uuid(),
|
|
169
170
|
role: "user",
|
|
170
171
|
content: trimmed,
|
|
171
172
|
timestamp: Date.now(),
|
|
@@ -177,7 +178,7 @@ export function useChat(): UseChatReturn {
|
|
|
177
178
|
sendChatMessage(sessionId.current, trimmed).catch(() => {
|
|
178
179
|
setIsTyping(false);
|
|
179
180
|
addMessage({
|
|
180
|
-
id:
|
|
181
|
+
id: uuid(),
|
|
181
182
|
role: "bot",
|
|
182
183
|
content: "Sorry, your message could not be sent. Please try again.",
|
|
183
184
|
timestamp: Date.now(),
|
|
@@ -190,7 +191,7 @@ export function useChat(): UseChatReturn {
|
|
|
190
191
|
const addEventMarker = useCallback(
|
|
191
192
|
(text: string) => {
|
|
192
193
|
addMessage({
|
|
193
|
-
id:
|
|
194
|
+
id: uuid(),
|
|
194
195
|
role: "event",
|
|
195
196
|
content: text,
|
|
196
197
|
timestamp: Date.now(),
|
|
@@ -227,7 +228,7 @@ export function useChat(): UseChatReturn {
|
|
|
227
228
|
const notify = useCallback(
|
|
228
229
|
(text: string) => {
|
|
229
230
|
addMessage({
|
|
230
|
-
id:
|
|
231
|
+
id: uuid(),
|
|
231
232
|
role: "bot",
|
|
232
233
|
content: text,
|
|
233
234
|
timestamp: Date.now(),
|
package/src/lib/uuid.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a v4 UUID that works in all contexts.
|
|
3
|
+
*
|
|
4
|
+
* crypto.randomUUID() requires a secure context (HTTPS or localhost).
|
|
5
|
+
* On HTTP with a non-localhost domain (e.g. local dev via /etc/hosts),
|
|
6
|
+
* it throws. This utility falls back to crypto.getRandomValues() which
|
|
7
|
+
* works everywhere, including non-secure contexts.
|
|
8
|
+
*/
|
|
9
|
+
export function uuid(): string {
|
|
10
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
11
|
+
return crypto.randomUUID();
|
|
12
|
+
}
|
|
13
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
14
|
+
const bytes = new Uint8Array(16);
|
|
15
|
+
crypto.getRandomValues(bytes);
|
|
16
|
+
// Set version (4) and variant (RFC 4122)
|
|
17
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
18
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
19
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
20
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
21
|
+
}
|
|
22
|
+
// Last resort: Math.random (not cryptographically secure, but won't crash)
|
|
23
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
24
|
+
const r = (Math.random() * 16) | 0;
|
|
25
|
+
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
|
26
|
+
});
|
|
27
|
+
}
|