pixelon 0.6.2 → 0.7.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.
@@ -0,0 +1,17 @@
1
+ import { type FC } from 'react';
2
+ import './ChatNotice.scss';
3
+ export type ChatNoticeType = 'success' | 'warning' | 'error' | 'info';
4
+ export type ChatNoticeMode = 'temporary' | 'static';
5
+ export interface ChatNoticeData {
6
+ id: string;
7
+ type: ChatNoticeType;
8
+ message: string;
9
+ mode: ChatNoticeMode;
10
+ duration?: number;
11
+ }
12
+ export interface ChatNoticeProps {
13
+ notice: ChatNoticeData;
14
+ onClose: (id: string) => void;
15
+ }
16
+ export declare const ChatNotice: FC<ChatNoticeProps>;
17
+ export default ChatNotice;
@@ -90,6 +90,17 @@ export interface ChatConfig {
90
90
  * agente no tiene por qué conocerla). Sin ella, `map_view` muestra su estado de error.
91
91
  */
92
92
  googleMapsApiKey?: string;
93
+ /**
94
+ * SPEC-015 — aviso temporal (5s, sin X) que se muestra automáticamente encima del input, una
95
+ * vez por instancia del chat — al entrar, sea una conversación nueva o con historial (no mira
96
+ * `messages` para nada). El texto y el tipo los pone el host — pixelon sigue sin saber de copy
97
+ * ni de dominio. Sin este campo, no se muestra nada. El nombre quedó de cuando solo aplicaba a
98
+ * chats vacíos; el campo ya no distingue eso.
99
+ */
100
+ newChatNotice?: {
101
+ message: string;
102
+ type?: 'success' | 'warning' | 'error' | 'info';
103
+ };
93
104
  }
94
105
  export declare const CHAT_CONFIG_DEFAULTS: ChatConfig;
95
106
  export interface ChatConfigProviderProps {
@@ -48,6 +48,8 @@ export declare const CHAT_EVENT: {
48
48
  readonly REOPEN_CHAT: "reopenChat";
49
49
  readonly CHAT_STATUS: "chatStatus";
50
50
  readonly CONVERSATIONS_UPDATED: "conversationsUpdated";
51
+ readonly NOTICE_SHOW: "noticeShow";
52
+ readonly NOTICE_DISMISS: "noticeDismiss";
51
53
  };
52
54
  export type ChatEvents = {
53
55
  /** UI component interaction (select, confirm, etc.) */
@@ -192,6 +194,21 @@ export type ChatEvents = {
192
194
  conversationsUpdated: {
193
195
  items: unknown[];
194
196
  };
197
+ /** SPEC-015 — Consumer/Chat -> Chat: muestra un aviso encima del input. `mode` por omisión
198
+ * 'temporary' (se autocierra en `duration` ms, default 4000). Hay DOS slots independientes, uno
199
+ * por `mode` — un `temporary` reemplaza al `temporary` anterior, un `static` al `static`
200
+ * anterior; no compiten entre sí. */
201
+ noticeShow: {
202
+ id: string;
203
+ type: 'success' | 'warning' | 'error' | 'info';
204
+ message: string;
205
+ mode?: 'temporary' | 'static';
206
+ duration?: number;
207
+ };
208
+ /** SPEC-015 — cierra el aviso con este id, si sigue siendo el que está mostrando ese slot. */
209
+ noticeDismiss: {
210
+ id: string;
211
+ };
195
212
  /** ReactiveForm -> Consumer: trace info for observability */
196
213
  formTrace: {
197
214
  chatId: string;
package/dist/index.esm.js CHANGED
@@ -1881,7 +1881,12 @@ const CHAT_EVENT = {
1881
1881
  // SPEC-029 B1
1882
1882
  CHAT_STATUS: "chatStatus",
1883
1883
  // SPEC-005
1884
- CONVERSATIONS_UPDATED: "conversationsUpdated"
1884
+ CONVERSATIONS_UPDATED: "conversationsUpdated",
1885
+ // SPEC-015 (pixelon) — aviso encima del input: éxito/warning/error/info, temporal o fijo
1886
+ // (`static`). Cualquiera puede emitirlo (el propio Chat, para el disclaimer de IA; el host, para
1887
+ // redirigir aquí sus toasts mientras el chat está en pantalla — ver towing-web SPEC-015).
1888
+ NOTICE_SHOW: "noticeShow",
1889
+ NOTICE_DISMISS: "noticeDismiss"
1885
1890
  };
1886
1891
  const chatEventBus = createEventBus();
1887
1892
 
@@ -18913,6 +18918,42 @@ const NewMessageDivider = ({
18913
18918
  ] });
18914
18919
  };
18915
18920
 
18921
+ const DEFAULT_DURATION_MS = 4e3;
18922
+ const NOTICE_ICONS = {
18923
+ success: "CHECK",
18924
+ error: "CLOSE",
18925
+ warning: "BELL",
18926
+ info: "HELP"
18927
+ };
18928
+ const ChatNotice = ({ notice, onClose }) => {
18929
+ const timeoutRef = useRef(null);
18930
+ useEffect(() => {
18931
+ if (notice.mode !== "temporary") return void 0;
18932
+ timeoutRef.current = setTimeout(() => onClose(notice.id), notice.duration ?? DEFAULT_DURATION_MS);
18933
+ return () => {
18934
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
18935
+ };
18936
+ }, [notice.id, notice.mode, notice.duration]);
18937
+ const cls = [
18938
+ `${COMPONENT_PREFIX$1}chat-notice`,
18939
+ `${COMPONENT_PREFIX$1}chat-notice--${notice.type}`
18940
+ ].join(" ");
18941
+ return /* @__PURE__ */ jsxs("div", { className: cls, role: "status", "aria-live": "polite", children: [
18942
+ /* @__PURE__ */ jsx("span", { className: `${COMPONENT_PREFIX$1}chat-notice__icon`, children: /* @__PURE__ */ jsx(Icon, { type: LUMI_ICON_TYPES[NOTICE_ICONS[notice.type]], size: "small" }) }),
18943
+ /* @__PURE__ */ jsx("span", { className: `${COMPONENT_PREFIX$1}chat-notice__message`, children: notice.message }),
18944
+ notice.mode === "static" && /* @__PURE__ */ jsx(
18945
+ "button",
18946
+ {
18947
+ type: "button",
18948
+ className: `${COMPONENT_PREFIX$1}chat-notice__close`,
18949
+ onClick: () => onClose(notice.id),
18950
+ "aria-label": "Cerrar aviso",
18951
+ children: /* @__PURE__ */ jsx(Icon, { type: LUMI_ICON_TYPES.CLOSE, size: "small" })
18952
+ }
18953
+ )
18954
+ ] });
18955
+ };
18956
+
18916
18957
  const useAppHeight$2 = (enabled = true) => {
18917
18958
  useEffect(() => {
18918
18959
  if (!enabled) return;
@@ -19782,6 +19823,8 @@ const Chat = ({
19782
19823
  const [isClosed, setIsClosed] = useState(false);
19783
19824
  const [readOnly, setReadOnly] = useState(false);
19784
19825
  const [readOnlyReason, setReadOnlyReason] = useState(null);
19826
+ const [noticiaTemporal, setNoticiaTemporal] = useState(null);
19827
+ const [noticiaFija, setNoticiaFija] = useState(null);
19785
19828
  useEffect(() => {
19786
19829
  const unsubMessages = eventBus.on(CHAT_EVENT.MESSAGES_UPDATED, ({ messages: newMessages }) => {
19787
19830
  setMessages(newMessages);
@@ -19824,6 +19867,47 @@ const Chat = ({
19824
19867
  unsubStatus();
19825
19868
  };
19826
19869
  }, [eventBus]);
19870
+ useEffect(() => {
19871
+ const unsubShow = eventBus.on(CHAT_EVENT.NOTICE_SHOW, (n) => {
19872
+ const notice = {
19873
+ id: n.id,
19874
+ type: n.type,
19875
+ message: n.message,
19876
+ mode: n.mode ?? "temporary",
19877
+ duration: n.duration
19878
+ };
19879
+ if (notice.mode === "static") {
19880
+ setNoticiaFija(notice);
19881
+ } else {
19882
+ setNoticiaTemporal(notice);
19883
+ }
19884
+ });
19885
+ const unsubDismiss = eventBus.on(CHAT_EVENT.NOTICE_DISMISS, ({ id }) => {
19886
+ setNoticiaTemporal((cur) => cur?.id === id ? null : cur);
19887
+ setNoticiaFija((cur) => cur?.id === id ? null : cur);
19888
+ });
19889
+ return () => {
19890
+ unsubShow();
19891
+ unsubDismiss();
19892
+ };
19893
+ }, [eventBus]);
19894
+ const cerrarAviso = (id) => {
19895
+ setNoticiaTemporal((cur) => cur?.id === id ? null : cur);
19896
+ setNoticiaFija((cur) => cur?.id === id ? null : cur);
19897
+ };
19898
+ const avisoNuevoChatMostrado = useRef(false);
19899
+ useEffect(() => {
19900
+ if (avisoNuevoChatMostrado.current) return;
19901
+ if (!resolvedConfig.newChatNotice) return;
19902
+ avisoNuevoChatMostrado.current = true;
19903
+ eventBus.emit(CHAT_EVENT.NOTICE_SHOW, {
19904
+ id: "new-chat-disclaimer",
19905
+ type: resolvedConfig.newChatNotice.type ?? "info",
19906
+ message: resolvedConfig.newChatNotice.message,
19907
+ mode: "temporary",
19908
+ duration: 5e3
19909
+ });
19910
+ }, [eventBus, resolvedConfig.newChatNotice]);
19827
19911
  const handleSend = (content, files) => {
19828
19912
  eventBus.emit(CHAT_EVENT.SEND_MESSAGE, { content, files });
19829
19913
  };
@@ -19982,6 +20066,19 @@ const Chat = ({
19982
20066
  className: `${COMPONENT_PREFIX$1}footer`,
19983
20067
  onClick: (e) => handleFooterClick(e, COMPONENT_PREFIX$1),
19984
20068
  children: [
20069
+ (noticiaFija || noticiaTemporal) && /* @__PURE__ */ jsxs(
20070
+ "div",
20071
+ {
20072
+ className: [
20073
+ `${COMPONENT_PREFIX$1}chat-notice-slot`,
20074
+ !isClosed && !readOnly ? `${COMPONENT_PREFIX$1}chat-notice-slot--anchored` : ""
20075
+ ].filter(Boolean).join(" "),
20076
+ children: [
20077
+ noticiaFija && /* @__PURE__ */ jsx(ChatNotice, { notice: noticiaFija, onClose: cerrarAviso }, noticiaFija.id),
20078
+ noticiaTemporal && /* @__PURE__ */ jsx(ChatNotice, { notice: noticiaTemporal, onClose: cerrarAviso }, noticiaTemporal.id)
20079
+ ]
20080
+ }
20081
+ ),
19985
20082
  isClosed ? (
19986
20083
  // SPEC-028 T7 — chat cerrado: input off + botón "Continuar chat".
19987
20084
  /* @__PURE__ */ jsxs("div", { className: `${COMPONENT_PREFIX$1}closed-banner`, children: [
package/dist/index.js CHANGED
@@ -1902,7 +1902,12 @@ const CHAT_EVENT = {
1902
1902
  // SPEC-029 B1
1903
1903
  CHAT_STATUS: "chatStatus",
1904
1904
  // SPEC-005
1905
- CONVERSATIONS_UPDATED: "conversationsUpdated"
1905
+ CONVERSATIONS_UPDATED: "conversationsUpdated",
1906
+ // SPEC-015 (pixelon) — aviso encima del input: éxito/warning/error/info, temporal o fijo
1907
+ // (`static`). Cualquiera puede emitirlo (el propio Chat, para el disclaimer de IA; el host, para
1908
+ // redirigir aquí sus toasts mientras el chat está en pantalla — ver towing-web SPEC-015).
1909
+ NOTICE_SHOW: "noticeShow",
1910
+ NOTICE_DISMISS: "noticeDismiss"
1906
1911
  };
1907
1912
  const chatEventBus = createEventBus();
1908
1913
 
@@ -18934,6 +18939,42 @@ const NewMessageDivider = ({
18934
18939
  ] });
18935
18940
  };
18936
18941
 
18942
+ const DEFAULT_DURATION_MS = 4e3;
18943
+ const NOTICE_ICONS = {
18944
+ success: "CHECK",
18945
+ error: "CLOSE",
18946
+ warning: "BELL",
18947
+ info: "HELP"
18948
+ };
18949
+ const ChatNotice = ({ notice, onClose }) => {
18950
+ const timeoutRef = React.useRef(null);
18951
+ React.useEffect(() => {
18952
+ if (notice.mode !== "temporary") return void 0;
18953
+ timeoutRef.current = setTimeout(() => onClose(notice.id), notice.duration ?? DEFAULT_DURATION_MS);
18954
+ return () => {
18955
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
18956
+ };
18957
+ }, [notice.id, notice.mode, notice.duration]);
18958
+ const cls = [
18959
+ `${COMPONENT_PREFIX$1}chat-notice`,
18960
+ `${COMPONENT_PREFIX$1}chat-notice--${notice.type}`
18961
+ ].join(" ");
18962
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cls, role: "status", "aria-live": "polite", children: [
18963
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${COMPONENT_PREFIX$1}chat-notice__icon`, children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { type: LUMI_ICON_TYPES[NOTICE_ICONS[notice.type]], size: "small" }) }),
18964
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${COMPONENT_PREFIX$1}chat-notice__message`, children: notice.message }),
18965
+ notice.mode === "static" && /* @__PURE__ */ jsxRuntime.jsx(
18966
+ "button",
18967
+ {
18968
+ type: "button",
18969
+ className: `${COMPONENT_PREFIX$1}chat-notice__close`,
18970
+ onClick: () => onClose(notice.id),
18971
+ "aria-label": "Cerrar aviso",
18972
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { type: LUMI_ICON_TYPES.CLOSE, size: "small" })
18973
+ }
18974
+ )
18975
+ ] });
18976
+ };
18977
+
18937
18978
  const useAppHeight$2 = (enabled = true) => {
18938
18979
  React.useEffect(() => {
18939
18980
  if (!enabled) return;
@@ -19803,6 +19844,8 @@ const Chat = ({
19803
19844
  const [isClosed, setIsClosed] = React.useState(false);
19804
19845
  const [readOnly, setReadOnly] = React.useState(false);
19805
19846
  const [readOnlyReason, setReadOnlyReason] = React.useState(null);
19847
+ const [noticiaTemporal, setNoticiaTemporal] = React.useState(null);
19848
+ const [noticiaFija, setNoticiaFija] = React.useState(null);
19806
19849
  React.useEffect(() => {
19807
19850
  const unsubMessages = eventBus.on(CHAT_EVENT.MESSAGES_UPDATED, ({ messages: newMessages }) => {
19808
19851
  setMessages(newMessages);
@@ -19845,6 +19888,47 @@ const Chat = ({
19845
19888
  unsubStatus();
19846
19889
  };
19847
19890
  }, [eventBus]);
19891
+ React.useEffect(() => {
19892
+ const unsubShow = eventBus.on(CHAT_EVENT.NOTICE_SHOW, (n) => {
19893
+ const notice = {
19894
+ id: n.id,
19895
+ type: n.type,
19896
+ message: n.message,
19897
+ mode: n.mode ?? "temporary",
19898
+ duration: n.duration
19899
+ };
19900
+ if (notice.mode === "static") {
19901
+ setNoticiaFija(notice);
19902
+ } else {
19903
+ setNoticiaTemporal(notice);
19904
+ }
19905
+ });
19906
+ const unsubDismiss = eventBus.on(CHAT_EVENT.NOTICE_DISMISS, ({ id }) => {
19907
+ setNoticiaTemporal((cur) => cur?.id === id ? null : cur);
19908
+ setNoticiaFija((cur) => cur?.id === id ? null : cur);
19909
+ });
19910
+ return () => {
19911
+ unsubShow();
19912
+ unsubDismiss();
19913
+ };
19914
+ }, [eventBus]);
19915
+ const cerrarAviso = (id) => {
19916
+ setNoticiaTemporal((cur) => cur?.id === id ? null : cur);
19917
+ setNoticiaFija((cur) => cur?.id === id ? null : cur);
19918
+ };
19919
+ const avisoNuevoChatMostrado = React.useRef(false);
19920
+ React.useEffect(() => {
19921
+ if (avisoNuevoChatMostrado.current) return;
19922
+ if (!resolvedConfig.newChatNotice) return;
19923
+ avisoNuevoChatMostrado.current = true;
19924
+ eventBus.emit(CHAT_EVENT.NOTICE_SHOW, {
19925
+ id: "new-chat-disclaimer",
19926
+ type: resolvedConfig.newChatNotice.type ?? "info",
19927
+ message: resolvedConfig.newChatNotice.message,
19928
+ mode: "temporary",
19929
+ duration: 5e3
19930
+ });
19931
+ }, [eventBus, resolvedConfig.newChatNotice]);
19848
19932
  const handleSend = (content, files) => {
19849
19933
  eventBus.emit(CHAT_EVENT.SEND_MESSAGE, { content, files });
19850
19934
  };
@@ -20003,6 +20087,19 @@ const Chat = ({
20003
20087
  className: `${COMPONENT_PREFIX$1}footer`,
20004
20088
  onClick: (e) => handleFooterClick(e, COMPONENT_PREFIX$1),
20005
20089
  children: [
20090
+ (noticiaFija || noticiaTemporal) && /* @__PURE__ */ jsxRuntime.jsxs(
20091
+ "div",
20092
+ {
20093
+ className: [
20094
+ `${COMPONENT_PREFIX$1}chat-notice-slot`,
20095
+ !isClosed && !readOnly ? `${COMPONENT_PREFIX$1}chat-notice-slot--anchored` : ""
20096
+ ].filter(Boolean).join(" "),
20097
+ children: [
20098
+ noticiaFija && /* @__PURE__ */ jsxRuntime.jsx(ChatNotice, { notice: noticiaFija, onClose: cerrarAviso }, noticiaFija.id),
20099
+ noticiaTemporal && /* @__PURE__ */ jsxRuntime.jsx(ChatNotice, { notice: noticiaTemporal, onClose: cerrarAviso }, noticiaTemporal.id)
20100
+ ]
20101
+ }
20102
+ ),
20006
20103
  isClosed ? (
20007
20104
  // SPEC-028 T7 — chat cerrado: input off + botón "Continuar chat".
20008
20105
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${COMPONENT_PREFIX$1}closed-banner`, children: [