@supcoflow/chat 0.1.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/ChatDeIntake.d.ts +11 -0
- package/dist/ChatDeIntake.d.ts.map +1 -0
- package/dist/ChatDeIntake.js +47 -0
- package/dist/ChatDeIntake.js.map +1 -0
- package/dist/estado-del-widget.d.ts +40 -0
- package/dist/estado-del-widget.d.ts.map +1 -0
- package/dist/estado-del-widget.js +40 -0
- package/dist/estado-del-widget.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Supcoflow
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @supcoflow/chat
|
|
2
|
+
|
|
3
|
+
El chat de intake de Supcoflow, como componente de React. Se monta en el sitio de un cliente y
|
|
4
|
+
conversa con un agente que orienta sobre el producto y, cuando hay un bug de verdad, abre un
|
|
5
|
+
reporte en Supcoflow.
|
|
6
|
+
|
|
7
|
+
## Requisitos
|
|
8
|
+
|
|
9
|
+
- **React 19** (`peerDependencies: react >=19`)
|
|
10
|
+
- Un proyecto de Supcoflow con **el chat activado** — que exige una skill aprobada y la lista de
|
|
11
|
+
Origins admitidos escrita. Si el chat está apagado, el widget **no se dibuja** (ver abajo).
|
|
12
|
+
|
|
13
|
+
## Instalación
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i @supcoflow/chat
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Mientras el paquete no esté publicado, se instala del tarball que produce `npm pack -w @supcoflow/chat`:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm i /ruta/al/supcoflow-chat-0.1.0.tgz
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Uso
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { ChatDeIntake } from "@supcoflow/chat";
|
|
29
|
+
|
|
30
|
+
<ChatDeIntake
|
|
31
|
+
tenantId="tnt_xxx"
|
|
32
|
+
projectId="proj_xxx"
|
|
33
|
+
backendUrl="https://chat.supcoflow.dev"
|
|
34
|
+
contexto={{ url: location.href, versionApp: "1.4.2" }} // opcional
|
|
35
|
+
/>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Los tres identificadores **son públicos** — modelo DSN. La contención no es un secreto: es la
|
|
39
|
+
lista de Origins admitidos del proyecto más el tope de mensajes por día, los dos del lado del
|
|
40
|
+
motor. Poner el `tenantId` en el HTML de un sitio público es el uso previsto.
|
|
41
|
+
|
|
42
|
+
`contexto` es opcional y viaja al reporte: sirve para que el issue diga en qué URL y con qué
|
|
43
|
+
versión pasó.
|
|
44
|
+
|
|
45
|
+
## Lo que hace cuando algo falla: nada visible
|
|
46
|
+
|
|
47
|
+
El widget **se oculta** —no muestra un error— si el chat está apagado, si el tope del día se
|
|
48
|
+
agotó, si el Origin del sitio no está admitido o si el backend no contesta. Es deliberado: quien
|
|
49
|
+
está del otro lado es un usuario final del producto del cliente, y no tiene nada que hacer con
|
|
50
|
+
"el proyecto tiene el tope agotado". Si esperabas ver el chat y no aparece, mirá la respuesta de
|
|
51
|
+
`GET {backendUrl}/api/chat/estado?tenant_id=…&project_id=…`, que dice la causa por status: 403
|
|
52
|
+
(chat apagado u Origin no admitido), 429 (tope agotado), 404 (proyecto desconocido), 502 (backend
|
|
53
|
+
sin poder hablar con el motor).
|
|
54
|
+
|
|
55
|
+
## Lo que este paquete NO hace
|
|
56
|
+
|
|
57
|
+
- No persiste la conversación: el historial vive en el navegador y viaja entero en cada pedido.
|
|
58
|
+
- No le da seguimiento del reporte al que reportó. Es a propósito — el único lugar donde un
|
|
59
|
+
reporte se ve es la consola del operador.
|
|
60
|
+
- No trae estilos configurables todavía: el panel usa estilos en línea y hereda la tipografía del
|
|
61
|
+
sitio.
|
|
62
|
+
|
|
63
|
+
## Nota para quien lo consuma sin bundler
|
|
64
|
+
|
|
65
|
+
El paquete emite ESM con imports relativos sin extensión (`./ChatDeIntake`), que cualquier bundler
|
|
66
|
+
—Next, Vite, webpack— resuelve. Node ESM en crudo, sin bundler, no: es un componente de cliente,
|
|
67
|
+
así que ese caso no existe en la práctica, pero queda dicho.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ContextoDelReporte {
|
|
2
|
+
url?: string;
|
|
3
|
+
versionApp?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function ChatDeIntake(props: {
|
|
6
|
+
tenantId: string;
|
|
7
|
+
projectId: string;
|
|
8
|
+
backendUrl: string;
|
|
9
|
+
contexto?: ContextoDelReporte;
|
|
10
|
+
}): import("react").JSX.Element | null;
|
|
11
|
+
//# sourceMappingURL=ChatDeIntake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatDeIntake.d.ts","sourceRoot":"","sources":["../src/ChatDeIntake.tsx"],"names":[],"mappings":"AAaA,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,sCAoEA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useChat } from "@ai-sdk/react";
|
|
4
|
+
import { DefaultChatTransport } from "ai";
|
|
5
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { decidirVisibilidad, generarCanalId, mostrarIndicadorDeEscritura, normalizarBackendUrl, } from "./estado-del-widget";
|
|
7
|
+
export function ChatDeIntake(props) {
|
|
8
|
+
const [visibilidad, setVisibilidad] = useState("oculto");
|
|
9
|
+
const [entrada, setEntrada] = useState("");
|
|
10
|
+
// Un id de canal por montaje: identifica la conversación en `reportado_por_externo`
|
|
11
|
+
// sin identificar a la persona (el chat es anónimo, §9 del diseño).
|
|
12
|
+
const canalId = useMemo(() => generarCanalId(), []);
|
|
13
|
+
const backendUrl = useMemo(() => normalizarBackendUrl(props.backendUrl), [props.backendUrl]);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
let vivo = true;
|
|
16
|
+
fetch(`${backendUrl}/api/chat/estado?tenant_id=${encodeURIComponent(props.tenantId)}&project_id=${encodeURIComponent(props.projectId)}`)
|
|
17
|
+
.then((r) => r.json())
|
|
18
|
+
.then((cuerpo) => { if (vivo)
|
|
19
|
+
setVisibilidad(decidirVisibilidad({ respuesta: cuerpo, huboError: false })); })
|
|
20
|
+
.catch(() => { if (vivo)
|
|
21
|
+
setVisibilidad(decidirVisibilidad({ respuesta: null, huboError: true })); });
|
|
22
|
+
return () => { vivo = false; };
|
|
23
|
+
}, [backendUrl, props.tenantId, props.projectId]);
|
|
24
|
+
const { messages, sendMessage, status, error } = useChat({
|
|
25
|
+
transport: new DefaultChatTransport({
|
|
26
|
+
api: `${backendUrl}/api/chat`,
|
|
27
|
+
body: { tenant_id: props.tenantId, project_id: props.projectId, canal_id: canalId },
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
// La misma política que el montaje: un error en medio de la conversación repliega el panel.
|
|
31
|
+
useEffect(() => { if (error !== undefined)
|
|
32
|
+
setVisibilidad("oculto"); }, [error]);
|
|
33
|
+
if (visibilidad === "oculto")
|
|
34
|
+
return null;
|
|
35
|
+
// Entre Enviar y el primer token del stream pasan 4-8 s en el peor caso (hallazgo de la
|
|
36
|
+
// pasada visual: el dueño lo leyó como "se rompió el botón" con la pantalla en silencio).
|
|
37
|
+
// El indicador cubre ese hueco sin prometer nada sobre CUÁNTO va a tardar.
|
|
38
|
+
const mostrarIndicador = mostrarIndicadorDeEscritura({ status, ultimoMensaje: messages[messages.length - 1] });
|
|
39
|
+
return (_jsxs("div", { style: { border: "1px solid #ccc", borderRadius: 8, maxWidth: 420, display: "flex", flexDirection: "column", fontFamily: "inherit" }, children: [_jsxs("div", { style: { padding: 12, overflowY: "auto", maxHeight: 420, display: "flex", flexDirection: "column", gap: 8 }, children: [messages.map((m) => (_jsx("div", { style: { alignSelf: m.role === "user" ? "flex-end" : "flex-start", whiteSpace: "pre-wrap" }, children: m.parts.map((p, i) => (p.type === "text" ? _jsx("span", { children: p.text }, i) : null)) }, m.id))), mostrarIndicador && (_jsx("div", { style: { alignSelf: "flex-start", whiteSpace: "pre-wrap" }, "aria-live": "polite", children: "\u2026" }))] }), _jsxs("form", { style: { display: "flex", gap: 8, padding: 12, borderTop: "1px solid #ccc" }, onSubmit: (e) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
if (entrada.trim().length === 0 || status !== "ready")
|
|
42
|
+
return;
|
|
43
|
+
sendMessage({ text: entrada });
|
|
44
|
+
setEntrada("");
|
|
45
|
+
}, children: [_jsx("input", { style: { flex: 1 }, value: entrada, onChange: (e) => setEntrada(e.target.value), placeholder: "Pregunt\u00E1 o cont\u00E1 qu\u00E9 pas\u00F3\u2026", "aria-label": "Mensaje" }), _jsx("button", { type: "submit", disabled: status !== "ready", children: "Enviar" })] })] }));
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=ChatDeIntake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatDeIntake.js","sourceRoot":"","sources":["../src/ChatDeIntake.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type Visibilidad = "visible" | "oculto";
|
|
2
|
+
/** `crypto.randomUUID` no existe en un contexto no-seguro (un sitio embebido por HTTP
|
|
3
|
+
* plano, no HTTPS) — sin este chequeo, el `useMemo` del widget lanza en el render y
|
|
4
|
+
* tira abajo el host entero, justo lo opuesto a la promesa de "se oculta" del §8. El
|
|
5
|
+
* fallback no necesita ser criptográfico: es un id de canal anónimo, no un secreto. */
|
|
6
|
+
export declare function generarCanalId(): string;
|
|
7
|
+
/** Una barra final en `backendUrl` ("https://x.com/", la forma más natural de
|
|
8
|
+
* escribirlo) deja `//` duplicado en las URLs armadas más abajo: el motor responde con
|
|
9
|
+
* un 308 sin headers de CORS y el navegador lo bloquea sin explicar que el problema es
|
|
10
|
+
* la barra. Mismo patrón que `motor.ts` usa para su propia base URL. */
|
|
11
|
+
export declare function normalizarBackendUrl(backendUrl: string): string;
|
|
12
|
+
/** §8 del diseño: sin backend, sin tope, o ante cualquier duda, el widget NO se dibuja.
|
|
13
|
+
* Ocultarse es lo honesto: no hay nada que decirle al usuario final sobre el estado interno
|
|
14
|
+
* del proyecto de otra empresa. */
|
|
15
|
+
export declare function decidirVisibilidad(h: {
|
|
16
|
+
respuesta: {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
} | null;
|
|
19
|
+
huboError: boolean;
|
|
20
|
+
}): Visibilidad;
|
|
21
|
+
export type EstadoDelChat = "submitted" | "streaming" | "ready" | "error";
|
|
22
|
+
interface ParteMinima {
|
|
23
|
+
type: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
}
|
|
26
|
+
interface MensajeMinimo {
|
|
27
|
+
role: string;
|
|
28
|
+
parts: ParteMinima[];
|
|
29
|
+
}
|
|
30
|
+
/** Entre Enviar y el primer token del stream pasan 4-8 s en el peor caso (medido en la pasada
|
|
31
|
+
* visual del demo) — el backend responde perfecto, pero un silencio así se lee como "se rompió
|
|
32
|
+
* el botón", no como "está pensando". Mostramos un indicador mientras el pedido está en vuelo
|
|
33
|
+
* ("submitted") y mientras ya empezó a transmitir pero todavía no llegó texto del asistente
|
|
34
|
+
* ("streaming" sin parts de texto no vacíos en el último mensaje). */
|
|
35
|
+
export declare function mostrarIndicadorDeEscritura(h: {
|
|
36
|
+
status: EstadoDelChat;
|
|
37
|
+
ultimoMensaje: MensajeMinimo | undefined;
|
|
38
|
+
}): boolean;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=estado-del-widget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estado-del-widget.d.ts","sourceRoot":"","sources":["../src/estado-del-widget.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE/C;;;uFAGuF;AACvF,wBAAgB,cAAc,IAAI,MAAM,CAKvC;AAED;;;wEAGwE;AACxE,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;mCAEmC;AACnC,wBAAgB,kBAAkB,CAAC,CAAC,EAAE;IACpC,SAAS,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;CACpB,GAAG,WAAW,CAGd;AAED,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,CAAC;AAE1E,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED;;;;sEAIsE;AACtE,wBAAgB,2BAA2B,CAAC,CAAC,EAAE;IAC7C,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,aAAa,GAAG,SAAS,CAAC;CAC1C,GAAG,OAAO,CAOV"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** `crypto.randomUUID` no existe en un contexto no-seguro (un sitio embebido por HTTP
|
|
2
|
+
* plano, no HTTPS) — sin este chequeo, el `useMemo` del widget lanza en el render y
|
|
3
|
+
* tira abajo el host entero, justo lo opuesto a la promesa de "se oculta" del §8. El
|
|
4
|
+
* fallback no necesita ser criptográfico: es un id de canal anónimo, no un secreto. */
|
|
5
|
+
export function generarCanalId() {
|
|
6
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
7
|
+
return crypto.randomUUID();
|
|
8
|
+
}
|
|
9
|
+
return `canal_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
10
|
+
}
|
|
11
|
+
/** Una barra final en `backendUrl` ("https://x.com/", la forma más natural de
|
|
12
|
+
* escribirlo) deja `//` duplicado en las URLs armadas más abajo: el motor responde con
|
|
13
|
+
* un 308 sin headers de CORS y el navegador lo bloquea sin explicar que el problema es
|
|
14
|
+
* la barra. Mismo patrón que `motor.ts` usa para su propia base URL. */
|
|
15
|
+
export function normalizarBackendUrl(backendUrl) {
|
|
16
|
+
return backendUrl.replace(/\/$/, "");
|
|
17
|
+
}
|
|
18
|
+
/** §8 del diseño: sin backend, sin tope, o ante cualquier duda, el widget NO se dibuja.
|
|
19
|
+
* Ocultarse es lo honesto: no hay nada que decirle al usuario final sobre el estado interno
|
|
20
|
+
* del proyecto de otra empresa. */
|
|
21
|
+
export function decidirVisibilidad(h) {
|
|
22
|
+
if (h.huboError || h.respuesta === null || !h.respuesta.ok)
|
|
23
|
+
return "oculto";
|
|
24
|
+
return "visible";
|
|
25
|
+
}
|
|
26
|
+
/** Entre Enviar y el primer token del stream pasan 4-8 s en el peor caso (medido en la pasada
|
|
27
|
+
* visual del demo) — el backend responde perfecto, pero un silencio así se lee como "se rompió
|
|
28
|
+
* el botón", no como "está pensando". Mostramos un indicador mientras el pedido está en vuelo
|
|
29
|
+
* ("submitted") y mientras ya empezó a transmitir pero todavía no llegó texto del asistente
|
|
30
|
+
* ("streaming" sin parts de texto no vacíos en el último mensaje). */
|
|
31
|
+
export function mostrarIndicadorDeEscritura(h) {
|
|
32
|
+
if (h.status === "submitted")
|
|
33
|
+
return true;
|
|
34
|
+
if (h.status !== "streaming")
|
|
35
|
+
return false;
|
|
36
|
+
const yaHayTexto = h.ultimoMensaje?.role === "assistant" &&
|
|
37
|
+
h.ultimoMensaje.parts.some((p) => p.type === "text" && (p.text?.length ?? 0) > 0);
|
|
38
|
+
return !yaHayTexto;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=estado-del-widget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estado-del-widget.js","sourceRoot":"","sources":["../src/estado-del-widget.ts"],"names":[],"mappings":"AAEA;;;uFAGuF;AACvF,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QAC7E,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,SAAS,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACtE,CAAC;AAED;;;wEAGwE;AACxE,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,OAAO,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;mCAEmC;AACnC,MAAM,UAAU,kBAAkB,CAAC,CAGlC;IACC,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE;QAAE,OAAO,QAAQ,CAAC;IAC5E,OAAO,SAAS,CAAC;AACnB,CAAC;AAcD;;;;sEAIsE;AACtE,MAAM,UAAU,2BAA2B,CAAC,CAG3C;IACC,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAC1C,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,UAAU,GACd,CAAC,CAAC,aAAa,EAAE,IAAI,KAAK,WAAW;QACrC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpF,OAAO,CAAC,UAAU,CAAC;AACrB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA2B,MAAM,gBAAgB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@supcoflow/chat",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "El chat de intake de Supcoflow como componente de React: orienta sobre el producto y abre el reporte cuando el problema es un bug.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/supcoflow/sdk.git",
|
|
9
|
+
"directory": "packages/widget"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://supcoflow.dev",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"supcoflow",
|
|
14
|
+
"chat",
|
|
15
|
+
"intake",
|
|
16
|
+
"bug-report",
|
|
17
|
+
"react",
|
|
18
|
+
"widget"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.build.json",
|
|
34
|
+
"prepare": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": ">=19"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@ai-sdk/react": "^4.0.0",
|
|
41
|
+
"ai": "^7.0.67"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/react": "19.2.18",
|
|
45
|
+
"typescript": "^5.7.0",
|
|
46
|
+
"vitest": "^4.0.0"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|