@sansavision/yousaid-react 0.0.0-stage → 0.2.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/CHANGELOG.md +13 -0
- package/LICENSE +6 -0
- package/README.md +105 -2
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +15 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +441 -0
- package/dist/proof-prompt.d.ts +57 -0
- package/dist/proof-prompt.d.ts.map +1 -0
- package/dist/proof-prompt.js +106 -0
- package/dist/proof.d.ts +113 -0
- package/dist/proof.d.ts.map +1 -0
- package/dist/proof.js +383 -0
- package/dist/rooms.d.ts +158 -0
- package/dist/rooms.d.ts.map +1 -0
- package/dist/rooms.js +442 -0
- package/package.json +48 -4
package/dist/rooms.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { AgentRunView, HumanHandoff, PendingApprovalView, RealtimeStatus, RoomCanvasView, RoomDetail, RoomMemberView, RoomRealtimeEvent } from "@sansavision/yousaid";
|
|
2
|
+
/**
|
|
3
|
+
* Live Room events over the realtime WebSocket: ordered by sequence, deduped,
|
|
4
|
+
* resumed from the last processed sequence after reconnects, and rebuilt from
|
|
5
|
+
* a snapshot when the client fell far behind. Falls back to HTTP catch-up
|
|
6
|
+
* polling where WebSocket is unavailable.
|
|
7
|
+
* @experimental
|
|
8
|
+
*/
|
|
9
|
+
export declare function useRoomEvents(roomId: string | null, options?: {
|
|
10
|
+
onEvent?: (event: RoomRealtimeEvent) => void;
|
|
11
|
+
pollMs?: number;
|
|
12
|
+
}): {
|
|
13
|
+
events: RoomRealtimeEvent[];
|
|
14
|
+
status: RealtimeStatus;
|
|
15
|
+
lastSequence: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* One agent run, polled until it reaches a terminal state, with cancel.
|
|
19
|
+
* @experimental
|
|
20
|
+
*/
|
|
21
|
+
export declare function useAgentRun(runId: string | null, options?: {
|
|
22
|
+
pollMs?: number;
|
|
23
|
+
}): {
|
|
24
|
+
run: AgentRunView | null;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
error: string;
|
|
27
|
+
refresh: () => Promise<AgentRunView | null>;
|
|
28
|
+
cancel: () => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Actions a teammate is reviewing in Rooms you can see. Customers only see that a review is pending.
|
|
32
|
+
* @experimental
|
|
33
|
+
*/
|
|
34
|
+
export declare function usePendingApprovals(roomId?: string, options?: {
|
|
35
|
+
pollMs?: number;
|
|
36
|
+
}): {
|
|
37
|
+
approvals: PendingApprovalView[];
|
|
38
|
+
loading: boolean;
|
|
39
|
+
error: string;
|
|
40
|
+
refresh: () => Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The customer-visible Room Canvas. Internal fields never reach the SDK.
|
|
44
|
+
* @experimental
|
|
45
|
+
*/
|
|
46
|
+
export declare function useRoomCanvas(roomId: string | null): {
|
|
47
|
+
canvas: RoomCanvasView | null;
|
|
48
|
+
loading: boolean;
|
|
49
|
+
error: string;
|
|
50
|
+
refresh: () => Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Customers in the Room (staff identities are never exposed to the SDK).
|
|
54
|
+
* @experimental
|
|
55
|
+
*/
|
|
56
|
+
export declare function useRoomMembers(roomId: string | null): {
|
|
57
|
+
members: RoomMemberView[];
|
|
58
|
+
memberCount: number;
|
|
59
|
+
loading: boolean;
|
|
60
|
+
error: string;
|
|
61
|
+
refresh: () => Promise<void>;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* "Talk to a person": request, show the acknowledgement and SLA, or withdraw.
|
|
65
|
+
* @experimental
|
|
66
|
+
*/
|
|
67
|
+
export declare function useRequestHuman(roomId: string | null): {
|
|
68
|
+
handoff: HumanHandoff | null;
|
|
69
|
+
active: boolean;
|
|
70
|
+
pending: boolean;
|
|
71
|
+
error: string;
|
|
72
|
+
request: (reason?: string) => Promise<HumanHandoff | null>;
|
|
73
|
+
withdraw: () => Promise<void>;
|
|
74
|
+
refresh: () => Promise<void>;
|
|
75
|
+
};
|
|
76
|
+
/** @experimental */
|
|
77
|
+
export type CanvasEntry = {
|
|
78
|
+
key: string;
|
|
79
|
+
label: string;
|
|
80
|
+
kind: "text";
|
|
81
|
+
value: string;
|
|
82
|
+
} | {
|
|
83
|
+
key: string;
|
|
84
|
+
label: string;
|
|
85
|
+
kind: "list";
|
|
86
|
+
value: string[];
|
|
87
|
+
} | {
|
|
88
|
+
key: string;
|
|
89
|
+
label: string;
|
|
90
|
+
kind: "pairs";
|
|
91
|
+
value: Array<[string, string]>;
|
|
92
|
+
};
|
|
93
|
+
/** @experimental */
|
|
94
|
+
export declare const DEFAULT_CANVAS_LABELS: Record<string, string>;
|
|
95
|
+
/**
|
|
96
|
+
* Turn Canvas content into labeled, renderable entries. Empty fields are skipped; unknown fields keep a readable label.
|
|
97
|
+
* @experimental
|
|
98
|
+
*/
|
|
99
|
+
export declare function canvasEntries(content: Record<string, unknown>, labels?: Record<string, string>): CanvasEntry[];
|
|
100
|
+
/**
|
|
101
|
+
* Renders Canvas fields as readable sections, never raw JSON. Themeable through --ys-* variables and className.
|
|
102
|
+
* @experimental
|
|
103
|
+
*/
|
|
104
|
+
export declare function RoomCanvas({ detail, canvas, labels, className, title, emptyMessage, }: {
|
|
105
|
+
detail?: Pick<RoomDetail, "canvas">;
|
|
106
|
+
canvas?: Pick<RoomCanvasView, "content" | "version"> | null;
|
|
107
|
+
labels?: Record<string, string>;
|
|
108
|
+
className?: string;
|
|
109
|
+
title?: string;
|
|
110
|
+
emptyMessage?: string;
|
|
111
|
+
}): import("react").JSX.Element;
|
|
112
|
+
/** @experimental */
|
|
113
|
+
export type MentionAgent = {
|
|
114
|
+
alias: string;
|
|
115
|
+
label: string;
|
|
116
|
+
description: string;
|
|
117
|
+
};
|
|
118
|
+
/** @experimental */
|
|
119
|
+
export declare const DEFAULT_MENTION_AGENTS: MentionAgent[];
|
|
120
|
+
/** @experimental */
|
|
121
|
+
export declare function filterAgents(agents: MentionAgent[], query: string): MentionAgent[];
|
|
122
|
+
/**
|
|
123
|
+
* Keyboard model for the mention listbox: returns the next active index, a selection, or a close request.
|
|
124
|
+
* @experimental
|
|
125
|
+
*/
|
|
126
|
+
export declare function mentionMenuKey(key: string, activeIndex: number, count: number): {
|
|
127
|
+
activeIndex: number;
|
|
128
|
+
select?: boolean;
|
|
129
|
+
close?: boolean;
|
|
130
|
+
handled: boolean;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Interactive @agent picker: a filterable listbox with arrow-key, Home/End,
|
|
134
|
+
* Enter/Tab and Escape handling. Controlled (activeIndex + onActiveIndexChange,
|
|
135
|
+
* for use inside a composer that keeps focus) or uncontrolled.
|
|
136
|
+
* @experimental
|
|
137
|
+
*/
|
|
138
|
+
export declare function AgentMentionMenu({ agents, query, onSelect, onClose, activeIndex: controlledIndex, onActiveIndexChange, id, className, }: {
|
|
139
|
+
agents?: MentionAgent[];
|
|
140
|
+
query?: string;
|
|
141
|
+
onSelect?: (agent: MentionAgent) => void;
|
|
142
|
+
onClose?: () => void;
|
|
143
|
+
activeIndex?: number;
|
|
144
|
+
onActiveIndexChange?: (index: number) => void;
|
|
145
|
+
id?: string;
|
|
146
|
+
className?: string;
|
|
147
|
+
}): import("react").JSX.Element;
|
|
148
|
+
/**
|
|
149
|
+
* A persistent, honest way to reach a person. Shows the acknowledgement and expected response time once requested.
|
|
150
|
+
* @experimental
|
|
151
|
+
*/
|
|
152
|
+
export declare function RequestHumanButton({ roomId, className }: {
|
|
153
|
+
roomId: string;
|
|
154
|
+
className?: string;
|
|
155
|
+
}): import("react").JSX.Element;
|
|
156
|
+
/** @experimental */
|
|
157
|
+
export declare const roomAgentStyles = ".ys-canvas{display:grid;gap:8px}.ys-canvas h4{margin:0;font-size:12px;font-weight:750}.ys-canvas dl{margin:0;display:grid;gap:10px}.ys-canvas dt{color:var(--ys-muted,#aaa6b2);font-size:10px;text-transform:uppercase;letter-spacing:.08em}.ys-canvas dd{margin:3px 0 0}.ys-canvas dd p{margin:0}.ys-canvas ul{margin:0;padding-left:18px}.ys-canvas__empty{margin:0;color:var(--ys-muted,#aaa6b2)}.ys-mentions{list-style:none;margin:6px 0 0;padding:4px;border:1px solid var(--ys-line,#2c2b34);border-radius:12px;background:var(--ys-panel,#19191f);max-width:360px}.ys-mentions li{display:grid;gap:2px;padding:8px 10px;border-radius:9px;cursor:pointer}.ys-mentions li.is-active{background:var(--ys-inset,#212129)}.ys-mentions small{color:var(--ys-muted,#aaa6b2)}.ys-mentions--empty{margin:6px 0 0;color:var(--ys-muted,#aaa6b2);font-size:12px}.ys-human{display:grid;gap:6px;margin-top:10px}.ys-human>button,.ys-human form button{justify-self:start;border:1px solid var(--ys-line,#2c2b34);border-radius:10px;background:transparent;color:var(--ys-text,#f4f1eb);padding:8px 11px;font-weight:700;cursor:pointer}.ys-human.is-requested{padding:10px;border-radius:12px;background:#18342c;color:var(--ys-mint,#6eddbb)}.ys-human p{margin:0}.ys-human form{display:grid;gap:8px}.ys-human form span{display:flex;gap:8px}.ys-human textarea{width:100%;min-height:60px;border:1px solid var(--ys-line,#2c2b34);border-radius:10px;background:#0e0e13;color:var(--ys-text,#f4f1eb);padding:8px}";
|
|
158
|
+
//# sourceMappingURL=rooms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rooms.d.ts","sourceRoot":"","sources":["../src/rooms.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,UAAU,EACV,cAAc,EAEd,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAQ9B;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO;;;;EAoDhF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO;;;;;;EAqClF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO;;;;;EAiBrF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;;;;;EAclD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;;;;;;EAenD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;;;;;;;;EA0BpD;AAID,oBAAoB;AACpB,MAAM,MAAM,WAAW,GACnB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC7D;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,CAAC;AAElF,oBAAoB;AACpB,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAcxD,CAAC;AAuBF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,WAAW,EAAE,CA2BlH;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,MAAM,EACN,MAAM,EACN,SAAc,EACd,KAAsB,EACtB,YAA2D,GAC5D,EAAE;IACD,MAAM,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,+BA8BA;AAED,oBAAoB;AACpB,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AACjF,oBAAoB;AACpB,eAAO,MAAM,sBAAsB,EAAE,YAAY,EAIhD,CAAC;AAEF,oBAAoB;AACpB,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAGlF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAS5J;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAA+B,EAC/B,KAAU,EACV,QAAQ,EACR,OAAO,EACP,WAAW,EAAE,eAAe,EAC5B,mBAAmB,EACnB,EAAE,EACF,SAAc,GACf,EAAE;IACD,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BA0CA;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,SAAc,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAiCpG;AAED,oBAAoB;AACpB,eAAO,MAAM,eAAe,07CAA07C,CAAC"}
|
package/dist/rooms.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useId, useRef, useState, } from "react";
|
|
4
|
+
import { useYouSaid } from "./context.js";
|
|
5
|
+
/* Headless hooks (PRD 12.3). Each hook works without the bundled components. */
|
|
6
|
+
const MAX_EVENTS = 500;
|
|
7
|
+
const TERMINAL_RUN_STATES = new Set(["completed", "failed", "cancelled", "approval_needed"]);
|
|
8
|
+
/**
|
|
9
|
+
* Live Room events over the realtime WebSocket: ordered by sequence, deduped,
|
|
10
|
+
* resumed from the last processed sequence after reconnects, and rebuilt from
|
|
11
|
+
* a snapshot when the client fell far behind. Falls back to HTTP catch-up
|
|
12
|
+
* polling where WebSocket is unavailable.
|
|
13
|
+
* @experimental
|
|
14
|
+
*/
|
|
15
|
+
export function useRoomEvents(roomId, options = {}) {
|
|
16
|
+
const client = useYouSaid();
|
|
17
|
+
const [events, setEvents] = useState([]);
|
|
18
|
+
const [status, setStatus] = useState("idle");
|
|
19
|
+
const [lastSequence, setLastSequence] = useState(0);
|
|
20
|
+
const onEvent = useRef(options.onEvent);
|
|
21
|
+
onEvent.current = options.onEvent;
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!roomId)
|
|
24
|
+
return;
|
|
25
|
+
setEvents([]);
|
|
26
|
+
setLastSequence(0);
|
|
27
|
+
const handlers = {
|
|
28
|
+
onEvent: (event) => {
|
|
29
|
+
setEvents((current) => [...current, event].slice(-MAX_EVENTS));
|
|
30
|
+
setLastSequence(event.sequence);
|
|
31
|
+
onEvent.current?.(event);
|
|
32
|
+
},
|
|
33
|
+
onSnapshot: (snapshot) => {
|
|
34
|
+
setEvents(snapshot.events.slice(-MAX_EVENTS));
|
|
35
|
+
setLastSequence(snapshot.sequence);
|
|
36
|
+
},
|
|
37
|
+
onStatus: setStatus,
|
|
38
|
+
};
|
|
39
|
+
const realtime = client.realtime(roomId, handlers);
|
|
40
|
+
if (typeof globalThis.WebSocket === "function") {
|
|
41
|
+
realtime.start();
|
|
42
|
+
return () => realtime.stop();
|
|
43
|
+
}
|
|
44
|
+
let stopped = false;
|
|
45
|
+
let timer;
|
|
46
|
+
const poll = async () => {
|
|
47
|
+
try {
|
|
48
|
+
let page = await client.roomEvents(roomId, realtime.lastSequence);
|
|
49
|
+
realtime.ingest(page);
|
|
50
|
+
while (page.hasMore && !stopped) {
|
|
51
|
+
page = await client.roomEvents(roomId, page.nextSequence);
|
|
52
|
+
realtime.ingest(page);
|
|
53
|
+
}
|
|
54
|
+
setStatus("open");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
setStatus("reconnecting");
|
|
58
|
+
}
|
|
59
|
+
if (!stopped)
|
|
60
|
+
timer = setTimeout(poll, options.pollMs ?? 5_000);
|
|
61
|
+
};
|
|
62
|
+
void poll();
|
|
63
|
+
return () => {
|
|
64
|
+
stopped = true;
|
|
65
|
+
if (timer)
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
};
|
|
68
|
+
}, [client, roomId, options.pollMs]);
|
|
69
|
+
return { events, status, lastSequence };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One agent run, polled until it reaches a terminal state, with cancel.
|
|
73
|
+
* @experimental
|
|
74
|
+
*/
|
|
75
|
+
export function useAgentRun(runId, options = {}) {
|
|
76
|
+
const client = useYouSaid();
|
|
77
|
+
const [run, setRun] = useState(null);
|
|
78
|
+
const [error, setError] = useState("");
|
|
79
|
+
const [loading, setLoading] = useState(Boolean(runId));
|
|
80
|
+
const refresh = useCallback(async () => {
|
|
81
|
+
if (!runId)
|
|
82
|
+
return null;
|
|
83
|
+
try {
|
|
84
|
+
const next = await client.getAgentRun(runId);
|
|
85
|
+
setRun(next);
|
|
86
|
+
setError("");
|
|
87
|
+
return next;
|
|
88
|
+
}
|
|
89
|
+
catch (reason) {
|
|
90
|
+
setError(reason instanceof Error ? reason.message : "The agent run is unavailable.");
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
setLoading(false);
|
|
95
|
+
}
|
|
96
|
+
}, [client, runId]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!runId) {
|
|
99
|
+
setRun(null);
|
|
100
|
+
setLoading(false);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
let stopped = false;
|
|
104
|
+
let timer;
|
|
105
|
+
const tick = async () => {
|
|
106
|
+
const next = await refresh();
|
|
107
|
+
if (!stopped && (!next || !TERMINAL_RUN_STATES.has(next.status)))
|
|
108
|
+
timer = setTimeout(tick, options.pollMs ?? 1_500);
|
|
109
|
+
};
|
|
110
|
+
setLoading(true);
|
|
111
|
+
void tick();
|
|
112
|
+
return () => { stopped = true; if (timer)
|
|
113
|
+
clearTimeout(timer); };
|
|
114
|
+
}, [runId, refresh, options.pollMs]);
|
|
115
|
+
const cancel = useCallback(async () => {
|
|
116
|
+
if (!runId)
|
|
117
|
+
return;
|
|
118
|
+
try {
|
|
119
|
+
setRun(await client.cancelAgentRun(runId));
|
|
120
|
+
}
|
|
121
|
+
catch (reason) {
|
|
122
|
+
setError(reason instanceof Error ? reason.message : "The run could not be cancelled.");
|
|
123
|
+
}
|
|
124
|
+
}, [client, runId]);
|
|
125
|
+
return { run, loading, error, refresh, cancel };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Actions a teammate is reviewing in Rooms you can see. Customers only see that a review is pending.
|
|
129
|
+
* @experimental
|
|
130
|
+
*/
|
|
131
|
+
export function usePendingApprovals(roomId, options = {}) {
|
|
132
|
+
const client = useYouSaid();
|
|
133
|
+
const [approvals, setApprovals] = useState([]);
|
|
134
|
+
const [loading, setLoading] = useState(true);
|
|
135
|
+
const [error, setError] = useState("");
|
|
136
|
+
const refresh = useCallback(async () => {
|
|
137
|
+
try {
|
|
138
|
+
setApprovals(await client.listPendingApprovals(roomId));
|
|
139
|
+
setError("");
|
|
140
|
+
}
|
|
141
|
+
catch (reason) {
|
|
142
|
+
setError(reason instanceof Error ? reason.message : "Approvals could not be loaded.");
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
setLoading(false);
|
|
146
|
+
}
|
|
147
|
+
}, [client, roomId]);
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
void refresh();
|
|
150
|
+
if (!options.pollMs)
|
|
151
|
+
return;
|
|
152
|
+
const timer = setInterval(() => void refresh(), options.pollMs);
|
|
153
|
+
return () => clearInterval(timer);
|
|
154
|
+
}, [refresh, options.pollMs]);
|
|
155
|
+
return { approvals, loading, error, refresh };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The customer-visible Room Canvas. Internal fields never reach the SDK.
|
|
159
|
+
* @experimental
|
|
160
|
+
*/
|
|
161
|
+
export function useRoomCanvas(roomId) {
|
|
162
|
+
const client = useYouSaid();
|
|
163
|
+
const [canvas, setCanvas] = useState(null);
|
|
164
|
+
const [loading, setLoading] = useState(Boolean(roomId));
|
|
165
|
+
const [error, setError] = useState("");
|
|
166
|
+
const refresh = useCallback(async () => {
|
|
167
|
+
if (!roomId) {
|
|
168
|
+
setCanvas(null);
|
|
169
|
+
setLoading(false);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
setLoading(true);
|
|
173
|
+
try {
|
|
174
|
+
setCanvas(await client.getRoomCanvas(roomId));
|
|
175
|
+
setError("");
|
|
176
|
+
}
|
|
177
|
+
catch (reason) {
|
|
178
|
+
setError(reason instanceof Error ? reason.message : "The Canvas could not be loaded.");
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
setLoading(false);
|
|
182
|
+
}
|
|
183
|
+
}, [client, roomId]);
|
|
184
|
+
useEffect(() => { void refresh(); }, [refresh]);
|
|
185
|
+
return { canvas, loading, error, refresh };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Customers in the Room (staff identities are never exposed to the SDK).
|
|
189
|
+
* @experimental
|
|
190
|
+
*/
|
|
191
|
+
export function useRoomMembers(roomId) {
|
|
192
|
+
const client = useYouSaid();
|
|
193
|
+
const [members, setMembers] = useState([]);
|
|
194
|
+
const [memberCount, setMemberCount] = useState(0);
|
|
195
|
+
const [loading, setLoading] = useState(Boolean(roomId));
|
|
196
|
+
const [error, setError] = useState("");
|
|
197
|
+
const refresh = useCallback(async () => {
|
|
198
|
+
if (!roomId) {
|
|
199
|
+
setMembers([]);
|
|
200
|
+
setMemberCount(0);
|
|
201
|
+
setLoading(false);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
setLoading(true);
|
|
205
|
+
try {
|
|
206
|
+
const result = await client.listRoomMembers(roomId);
|
|
207
|
+
setMembers(result.members);
|
|
208
|
+
setMemberCount(result.memberCount);
|
|
209
|
+
setError("");
|
|
210
|
+
}
|
|
211
|
+
catch (reason) {
|
|
212
|
+
setError(reason instanceof Error ? reason.message : "Members could not be loaded.");
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
setLoading(false);
|
|
216
|
+
}
|
|
217
|
+
}, [client, roomId]);
|
|
218
|
+
useEffect(() => { void refresh(); }, [refresh]);
|
|
219
|
+
return { members, memberCount, loading, error, refresh };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* "Talk to a person": request, show the acknowledgement and SLA, or withdraw.
|
|
223
|
+
* @experimental
|
|
224
|
+
*/
|
|
225
|
+
export function useRequestHuman(roomId) {
|
|
226
|
+
const client = useYouSaid();
|
|
227
|
+
const [handoff, setHandoff] = useState(null);
|
|
228
|
+
const [pending, setPending] = useState(false);
|
|
229
|
+
const [error, setError] = useState("");
|
|
230
|
+
const refresh = useCallback(async () => {
|
|
231
|
+
if (!roomId) {
|
|
232
|
+
setHandoff(null);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
setHandoff(await client.getHumanHandoff(roomId));
|
|
237
|
+
}
|
|
238
|
+
catch { /* the button stays available; a request surfaces its own error */ }
|
|
239
|
+
}, [client, roomId]);
|
|
240
|
+
useEffect(() => { void refresh(); }, [refresh]);
|
|
241
|
+
const request = useCallback(async (reason = "") => {
|
|
242
|
+
if (!roomId)
|
|
243
|
+
return null;
|
|
244
|
+
setPending(true);
|
|
245
|
+
setError("");
|
|
246
|
+
try {
|
|
247
|
+
const next = await client.requestHuman(roomId, reason);
|
|
248
|
+
setHandoff(next);
|
|
249
|
+
return next;
|
|
250
|
+
}
|
|
251
|
+
catch (reason) {
|
|
252
|
+
setError(reason instanceof Error ? reason.message : "Your request could not be sent. Try again.");
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
setPending(false);
|
|
257
|
+
}
|
|
258
|
+
}, [client, roomId]);
|
|
259
|
+
const withdraw = useCallback(async () => {
|
|
260
|
+
if (!roomId)
|
|
261
|
+
return;
|
|
262
|
+
setPending(true);
|
|
263
|
+
setError("");
|
|
264
|
+
try {
|
|
265
|
+
await client.withdrawHumanHandoff(roomId);
|
|
266
|
+
setHandoff(null);
|
|
267
|
+
}
|
|
268
|
+
catch (reason) {
|
|
269
|
+
setError(reason instanceof Error ? reason.message : "The request could not be withdrawn.");
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
setPending(false);
|
|
273
|
+
}
|
|
274
|
+
}, [client, roomId]);
|
|
275
|
+
return { handoff, active: Boolean(handoff && (handoff.status === "open" || handoff.status === "acknowledged")), pending, error, request, withdraw, refresh };
|
|
276
|
+
}
|
|
277
|
+
/** @experimental */
|
|
278
|
+
export const DEFAULT_CANVAS_LABELS = {
|
|
279
|
+
purpose: "Why this Room exists",
|
|
280
|
+
outcome: "Done means",
|
|
281
|
+
currentState: "Where things stand",
|
|
282
|
+
confirmedFacts: "Confirmed",
|
|
283
|
+
hypotheses: "Working theories",
|
|
284
|
+
openQuestions: "Open questions",
|
|
285
|
+
affected: "Who is affected",
|
|
286
|
+
decisions: "Decisions",
|
|
287
|
+
promises: "Commitments",
|
|
288
|
+
actions: "Next actions",
|
|
289
|
+
release: "Release",
|
|
290
|
+
verification: "Verification",
|
|
291
|
+
canonicalAnswer: "Answer",
|
|
292
|
+
};
|
|
293
|
+
const CANVAS_ORDER = Object.keys(DEFAULT_CANVAS_LABELS);
|
|
294
|
+
function humanize(key) {
|
|
295
|
+
const spaced = key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim().toLowerCase();
|
|
296
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
297
|
+
}
|
|
298
|
+
function itemText(value) {
|
|
299
|
+
if (typeof value === "string")
|
|
300
|
+
return value;
|
|
301
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
302
|
+
return String(value);
|
|
303
|
+
if (value && typeof value === "object") {
|
|
304
|
+
const record = value;
|
|
305
|
+
const primary = record.title ?? record.statement ?? record.text ?? record.summary ?? record.name;
|
|
306
|
+
if (typeof primary === "string") {
|
|
307
|
+
const status = typeof record.status === "string" ? ` (${record.status.replaceAll("_", " ")})` : "";
|
|
308
|
+
return `${primary}${status}`;
|
|
309
|
+
}
|
|
310
|
+
return Object.entries(record).filter(([, item]) => item !== null && item !== undefined && item !== "").map(([key, item]) => `${humanize(key)}: ${itemText(item)}`).join(", ");
|
|
311
|
+
}
|
|
312
|
+
return "";
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Turn Canvas content into labeled, renderable entries. Empty fields are skipped; unknown fields keep a readable label.
|
|
316
|
+
* @experimental
|
|
317
|
+
*/
|
|
318
|
+
export function canvasEntries(content, labels = {}) {
|
|
319
|
+
const merged = { ...DEFAULT_CANVAS_LABELS, ...labels };
|
|
320
|
+
const keys = [...CANVAS_ORDER.filter((key) => key in content), ...Object.keys(content).filter((key) => !CANVAS_ORDER.includes(key) && key !== "fieldVisibility").sort()];
|
|
321
|
+
const entries = [];
|
|
322
|
+
for (const key of keys) {
|
|
323
|
+
const value = content[key];
|
|
324
|
+
const label = merged[key] ?? humanize(key);
|
|
325
|
+
if (value === null || value === undefined || value === "")
|
|
326
|
+
continue;
|
|
327
|
+
if (Array.isArray(value)) {
|
|
328
|
+
const items = value.map(itemText).filter(Boolean);
|
|
329
|
+
if (items.length)
|
|
330
|
+
entries.push({ key, label, kind: "list", value: items });
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (typeof value === "object") {
|
|
334
|
+
const record = value;
|
|
335
|
+
if (key === "verification") {
|
|
336
|
+
if (record.attempted !== true && !record.result)
|
|
337
|
+
continue;
|
|
338
|
+
entries.push({ key, label, kind: "text", value: record.attempted === true ? `Checked${record.result ? `: ${String(record.result).replaceAll("_", " ")}` : ""}${typeof record.note === "string" && record.note ? `. ${record.note}` : ""}` : String(record.result) });
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const pairs = Object.entries(record).filter(([, item]) => item !== null && item !== undefined && item !== "").map(([name, item]) => [humanize(name), itemText(item)]).filter(([, item]) => item);
|
|
342
|
+
if (pairs.length)
|
|
343
|
+
entries.push({ key, label, kind: "pairs", value: pairs });
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
entries.push({ key, label, kind: "text", value: itemText(value) });
|
|
347
|
+
}
|
|
348
|
+
return entries;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Renders Canvas fields as readable sections, never raw JSON. Themeable through --ys-* variables and className.
|
|
352
|
+
* @experimental
|
|
353
|
+
*/
|
|
354
|
+
export function RoomCanvas({ detail, canvas, labels, className = "", title = "Room outcome", emptyMessage = "The team has not filled in the Canvas yet.", }) {
|
|
355
|
+
const content = canvas?.content ?? detail?.canvas?.content ?? {};
|
|
356
|
+
const version = canvas?.version ?? detail?.canvas?.version;
|
|
357
|
+
const entries = canvasEntries(content, labels);
|
|
358
|
+
const headingId = useId();
|
|
359
|
+
return (_jsxs("section", { className: `ys-canvas ${className}`.trim(), "aria-labelledby": headingId, "data-canvas-version": version ?? undefined, children: [_jsx("h4", { id: headingId, children: title }), entries.length ? (_jsx("dl", { children: entries.map((entry) => (_jsxs("div", { "data-canvas-field": entry.key, children: [_jsx("dt", { children: entry.label }), _jsx("dd", { children: entry.kind === "text" ? (_jsx("p", { children: entry.value })) : entry.kind === "list" ? (_jsx("ul", { children: entry.value.map((item, index) => _jsx("li", { children: item }, `${entry.key}-${index}`)) })) : (_jsx("ul", { children: entry.value.map(([name, item]) => _jsxs("li", { children: [_jsx("strong", { children: name }), " ", item] }, `${entry.key}-${name}`)) })) })] }, entry.key))) })) : (_jsx("p", { className: "ys-canvas__empty", children: emptyMessage }))] }));
|
|
360
|
+
}
|
|
361
|
+
/** @experimental */
|
|
362
|
+
export const DEFAULT_MENTION_AGENTS = [
|
|
363
|
+
{ alias: "support", label: "@support", description: "Answers from approved help and this Room" },
|
|
364
|
+
{ alias: "product", label: "@product", description: "Summarizes needs, trade-offs and open questions" },
|
|
365
|
+
{ alias: "incident", label: "@incident", description: "Structures what is known about an outage or bug" },
|
|
366
|
+
];
|
|
367
|
+
/** @experimental */
|
|
368
|
+
export function filterAgents(agents, query) {
|
|
369
|
+
const needle = query.trim().toLowerCase().replace(/^@/, "");
|
|
370
|
+
return agents.filter((agent) => !needle || agent.alias.startsWith(needle) || agent.label.toLowerCase().includes(needle));
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Keyboard model for the mention listbox: returns the next active index, a selection, or a close request.
|
|
374
|
+
* @experimental
|
|
375
|
+
*/
|
|
376
|
+
export function mentionMenuKey(key, activeIndex, count) {
|
|
377
|
+
if (!count)
|
|
378
|
+
return { activeIndex: 0, handled: key === "Escape", ...(key === "Escape" ? { close: true } : {}) };
|
|
379
|
+
if (key === "ArrowDown")
|
|
380
|
+
return { activeIndex: (activeIndex + 1) % count, handled: true };
|
|
381
|
+
if (key === "ArrowUp")
|
|
382
|
+
return { activeIndex: (activeIndex - 1 + count) % count, handled: true };
|
|
383
|
+
if (key === "Home")
|
|
384
|
+
return { activeIndex: 0, handled: true };
|
|
385
|
+
if (key === "End")
|
|
386
|
+
return { activeIndex: count - 1, handled: true };
|
|
387
|
+
if (key === "Enter" || key === "Tab")
|
|
388
|
+
return { activeIndex, select: true, handled: true };
|
|
389
|
+
if (key === "Escape")
|
|
390
|
+
return { activeIndex, close: true, handled: true };
|
|
391
|
+
return { activeIndex, handled: false };
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Interactive @agent picker: a filterable listbox with arrow-key, Home/End,
|
|
395
|
+
* Enter/Tab and Escape handling. Controlled (activeIndex + onActiveIndexChange,
|
|
396
|
+
* for use inside a composer that keeps focus) or uncontrolled.
|
|
397
|
+
* @experimental
|
|
398
|
+
*/
|
|
399
|
+
export function AgentMentionMenu({ agents = DEFAULT_MENTION_AGENTS, query = "", onSelect, onClose, activeIndex: controlledIndex, onActiveIndexChange, id, className = "", }) {
|
|
400
|
+
const generatedId = useId();
|
|
401
|
+
const listId = id ?? `ys-mentions-${generatedId}`;
|
|
402
|
+
const matches = filterAgents(agents, query);
|
|
403
|
+
const [localIndex, setLocalIndex] = useState(0);
|
|
404
|
+
const activeIndex = Math.min(controlledIndex ?? localIndex, Math.max(0, matches.length - 1));
|
|
405
|
+
const setIndex = (index) => { setLocalIndex(index); onActiveIndexChange?.(index); };
|
|
406
|
+
function onKeyDown(event) {
|
|
407
|
+
const result = mentionMenuKey(event.key, activeIndex, matches.length);
|
|
408
|
+
if (!result.handled)
|
|
409
|
+
return;
|
|
410
|
+
event.preventDefault();
|
|
411
|
+
if (result.close) {
|
|
412
|
+
onClose?.();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (result.select && matches[activeIndex]) {
|
|
416
|
+
onSelect?.(matches[activeIndex]);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
setIndex(result.activeIndex);
|
|
420
|
+
}
|
|
421
|
+
if (!matches.length)
|
|
422
|
+
return _jsxs("p", { className: `ys-mentions ys-mentions--empty ${className}`.trim(), role: "status", children: ["No agent matches @", query, "."] });
|
|
423
|
+
return (_jsx("ul", { id: listId, role: "listbox", "aria-label": "Mention an agent", "aria-activedescendant": `${listId}-${matches[activeIndex]?.alias ?? ""}`, tabIndex: 0, className: `ys-mentions ${className}`.trim(), onKeyDown: onKeyDown, children: matches.map((agent, index) => (_jsxs("li", { id: `${listId}-${agent.alias}`, role: "option", "aria-selected": index === activeIndex, className: index === activeIndex ? "is-active" : "", onMouseEnter: () => setIndex(index), onMouseDown: (event) => { event.preventDefault(); onSelect?.(agent); }, children: [_jsx("strong", { children: agent.label }), _jsx("small", { children: agent.description })] }, agent.alias))) }));
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* A persistent, honest way to reach a person. Shows the acknowledgement and expected response time once requested.
|
|
427
|
+
* @experimental
|
|
428
|
+
*/
|
|
429
|
+
export function RequestHumanButton({ roomId, className = "" }) {
|
|
430
|
+
const human = useRequestHuman(roomId);
|
|
431
|
+
const [asking, setAsking] = useState(false);
|
|
432
|
+
const [reason, setReason] = useState("");
|
|
433
|
+
if (human.active && human.handoff) {
|
|
434
|
+
return (_jsxs("div", { className: `ys-human is-requested ${className}`.trim(), role: "status", "aria-live": "polite", children: [_jsx("strong", { children: human.handoff.acknowledged ? "A teammate has your request" : "A person has been asked to help" }), _jsx("p", { children: human.handoff.message }), _jsx("button", { type: "button", onClick: () => void human.withdraw(), disabled: human.pending, children: "Withdraw request" }), human.error ? _jsx("p", { role: "alert", children: human.error }) : null] }));
|
|
435
|
+
}
|
|
436
|
+
return (_jsxs("div", { className: `ys-human ${className}`.trim(), children: [asking ? (_jsxs("form", { onSubmit: (event) => { event.preventDefault(); void human.request(reason).then((result) => { if (result) {
|
|
437
|
+
setAsking(false);
|
|
438
|
+
setReason("");
|
|
439
|
+
} }); }, children: [_jsxs("label", { children: ["What should the team know? (optional)", _jsx("textarea", { value: reason, maxLength: 1_000, onChange: (event) => setReason(event.target.value) })] }), _jsxs("span", { children: [_jsx("button", { type: "submit", disabled: human.pending, children: human.pending ? "Sending…" : "Ask for a person" }), _jsx("button", { type: "button", onClick: () => setAsking(false), children: "Cancel" })] })] })) : (_jsx("button", { type: "button", onClick: () => setAsking(true), "aria-haspopup": "dialog", children: "Talk to a person" })), human.error ? _jsx("p", { role: "alert", children: human.error }) : null] }));
|
|
440
|
+
}
|
|
441
|
+
/** @experimental */
|
|
442
|
+
export const roomAgentStyles = `.ys-canvas{display:grid;gap:8px}.ys-canvas h4{margin:0;font-size:12px;font-weight:750}.ys-canvas dl{margin:0;display:grid;gap:10px}.ys-canvas dt{color:var(--ys-muted,#aaa6b2);font-size:10px;text-transform:uppercase;letter-spacing:.08em}.ys-canvas dd{margin:3px 0 0}.ys-canvas dd p{margin:0}.ys-canvas ul{margin:0;padding-left:18px}.ys-canvas__empty{margin:0;color:var(--ys-muted,#aaa6b2)}.ys-mentions{list-style:none;margin:6px 0 0;padding:4px;border:1px solid var(--ys-line,#2c2b34);border-radius:12px;background:var(--ys-panel,#19191f);max-width:360px}.ys-mentions li{display:grid;gap:2px;padding:8px 10px;border-radius:9px;cursor:pointer}.ys-mentions li.is-active{background:var(--ys-inset,#212129)}.ys-mentions small{color:var(--ys-muted,#aaa6b2)}.ys-mentions--empty{margin:6px 0 0;color:var(--ys-muted,#aaa6b2);font-size:12px}.ys-human{display:grid;gap:6px;margin-top:10px}.ys-human>button,.ys-human form button{justify-self:start;border:1px solid var(--ys-line,#2c2b34);border-radius:10px;background:transparent;color:var(--ys-text,#f4f1eb);padding:8px 11px;font-weight:700;cursor:pointer}.ys-human.is-requested{padding:10px;border-radius:12px;background:#18342c;color:var(--ys-mint,#6eddbb)}.ys-human p{margin:0}.ys-human form{display:grid;gap:8px}.ys-human form span{display:flex;gap:8px}.ys-human textarea{width:100%;min-height:60px;border:1px solid var(--ys-line,#2c2b34);border-radius:10px;background:#0e0e13;color:var(--ys-text,#f4f1eb);padding:8px}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sansavision/yousaid-react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "React 19 components and hooks for YouSaid: provider, launcher, inbox and lifecycle receipts.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": ">=19"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"react": {
|
|
32
|
+
"optional": true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@sansavision/yousaid": "^0.2.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/Sansa-Organisation/yousaid.git",
|
|
41
|
+
"directory": "packages/react"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://yousaid.dev/docs/sdk",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20.0.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
}
|
|
50
|
+
}
|