@lilaquadrat/chat 0.1.2 → 0.1.4

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.
@@ -14561,7 +14561,8 @@ const categoryColors = {
14561
14561
  plugins: "background-color: #3f2d56; color: #FFF",
14562
14562
  startup: "background-color: #5A8C99; color: #FFF",
14563
14563
  auth: "background-color: #5A8C99; color: #FFF",
14564
- websocket: "background-color: #5A8C99; color: #FFF"
14564
+ websocket: "background-color: #5A8C99; color: #FFF",
14565
+ session: "background-color: #7a5a99; color: #FFF"
14565
14566
  // Add other predefined categories and colors as needed
14566
14567
  };
14567
14568
  const logger = new Proxy({}, {
@@ -14841,6 +14842,16 @@ const useUserStore = /* @__PURE__ */ defineStore("user", () => {
14841
14842
  });
14842
14843
  const useMainStore = /* @__PURE__ */ defineStore("main", () => {
14843
14844
  const leftTarget = /* @__PURE__ */ ref("conversations");
14845
+ watch(leftTarget, (value) => {
14846
+ try {
14847
+ if (value) {
14848
+ localStorage.setItem("studio-chat:left-target", value);
14849
+ } else {
14850
+ localStorage.removeItem("studio-chat:left-target");
14851
+ }
14852
+ } catch {
14853
+ }
14854
+ });
14844
14855
  const mainTarget = /* @__PURE__ */ ref("chat");
14845
14856
  const state = /* @__PURE__ */ ref("disconnected");
14846
14857
  const fullscreen = /* @__PURE__ */ ref(false);
@@ -14998,6 +15009,17 @@ const useUsersCacheStore = /* @__PURE__ */ defineStore("usersCache", () => {
14998
15009
  const useConversationsStore = /* @__PURE__ */ defineStore("conversations", () => {
14999
15010
  const conversations = /* @__PURE__ */ ref([]);
15000
15011
  const activeConversation = /* @__PURE__ */ ref();
15012
+ watch(activeConversation, (conversation) => {
15013
+ try {
15014
+ const id = conversation?._id ?? conversation?.id;
15015
+ if (id) {
15016
+ localStorage.setItem("studio-chat:active-conversation", id);
15017
+ } else {
15018
+ localStorage.removeItem("studio-chat:active-conversation");
15019
+ }
15020
+ } catch {
15021
+ }
15022
+ });
15001
15023
  const unreadConversations = computed(
15002
15024
  () => conversations.value.filter((conversation) => conversation.unread)
15003
15025
  );
@@ -15066,16 +15088,35 @@ const useConversationsStore = /* @__PURE__ */ defineStore("conversations", () =>
15066
15088
  const response = await sdk.conversations.list();
15067
15089
  const newConversations = response.data || [];
15068
15090
  newConversations.forEach((newConv) => {
15069
- const exists = conversations.value.some(
15091
+ const existing = conversations.value.find(
15070
15092
  (existingConv) => existingConv._id === newConv._id || existingConv.id === newConv.id && newConv.id
15071
15093
  );
15072
- if (!exists) {
15094
+ if (existing) {
15095
+ existing.unreadCount = newConv.unreadCount;
15096
+ existing.unread = newConv.unread;
15097
+ existing.lastMessage = newConv.lastMessage;
15098
+ existing.messagesCount = newConv.messagesCount;
15099
+ existing.participant = { ...existing.participant, ...newConv.participant };
15100
+ } else {
15073
15101
  conversations.value.push(newConv);
15074
15102
  }
15075
15103
  useUsersCacheStore().ingest(newConv.participants);
15076
15104
  });
15077
15105
  updateAllUnread();
15078
15106
  }
15107
+ function refreshConversations() {
15108
+ StudioSDK.flushCache("conversations", "list");
15109
+ StudioSDK.flushCache("messages", "list");
15110
+ return getAllConversations();
15111
+ }
15112
+ async function handleReconnect() {
15113
+ await refreshConversations();
15114
+ const active = activeConversation.value;
15115
+ if (active && !active.isNew && active._id) {
15116
+ const messages = await getMessagesFromApi(active._id);
15117
+ addMessagesToConversation(messages, active._id);
15118
+ }
15119
+ }
15079
15120
  async function getMessagesFromApi(conversation, beforeId) {
15080
15121
  const sdk = new StudioSDK("chat", sdkOptions2());
15081
15122
  const response = await sdk.messages.list(conversation, beforeId);
@@ -15291,6 +15332,8 @@ const useConversationsStore = /* @__PURE__ */ defineStore("conversations", () =>
15291
15332
  }
15292
15333
  return {
15293
15334
  getAllConversations,
15335
+ refreshConversations,
15336
+ handleReconnect,
15294
15337
  conversations,
15295
15338
  unreadConversations,
15296
15339
  totalUnreadCount,
@@ -15333,21 +15376,22 @@ const useConversationsStore = /* @__PURE__ */ defineStore("conversations", () =>
15333
15376
  };
15334
15377
  });
15335
15378
  const _hoisted_1$e = { class: "conversations-module" };
15336
- const _hoisted_2$a = ["onClick"];
15379
+ const _hoisted_2$b = ["onClick"];
15337
15380
  const _hoisted_3$6 = { class: "time" };
15338
- const _hoisted_4$3 = {
15339
- key: 3,
15381
+ const _hoisted_4$3 = { class: "message-unread-container" };
15382
+ const _hoisted_5$3 = {
15383
+ key: 0,
15340
15384
  class: "lastMessage"
15341
15385
  };
15342
- const _hoisted_5$3 = {
15343
- key: 4,
15386
+ const _hoisted_6$2 = {
15387
+ key: 1,
15344
15388
  class: "unread"
15345
15389
  };
15346
- const _hoisted_6$2 = {
15347
- key: 5,
15390
+ const _hoisted_7$2 = {
15391
+ key: 3,
15348
15392
  class: "draft"
15349
15393
  };
15350
- const _sfc_main$j = /* @__PURE__ */ defineComponent({
15394
+ const _sfc_main$k = /* @__PURE__ */ defineComponent({
15351
15395
  __name: "conversations.module",
15352
15396
  setup(__props) {
15353
15397
  const conversationsStore = useConversationsStore();
@@ -15355,14 +15399,13 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
15355
15399
  const userStore = useUserStore();
15356
15400
  watch(() => userStore.user, (user) => {
15357
15401
  if (!user) return;
15402
+ console.log("set user");
15358
15403
  conversationsStore.cleanConversations();
15359
15404
  conversationsStore.getAllConversations();
15360
15405
  }, { immediate: true });
15361
15406
  const conversations = computed(() => {
15362
15407
  const safeConversations = hardCopy(conversationsStore.conversations);
15363
- return safeConversations.sort((a, b) => {
15364
- return dayjs(a.lastMessage?.history.created).isBefore(dayjs(b.lastMessage?.history.created)) ? 1 : -1;
15365
- }).map((conversation) => ({
15408
+ return safeConversations.sort((a, b) => dayjs(a.lastMessage?.history.created).isBefore(dayjs(b.lastMessage?.history.created)) ? 1 : -1).map((conversation) => ({
15366
15409
  ...conversation,
15367
15410
  isActive: activeConversation.value?._id === conversation._id && conversation._id || activeConversation.value?.id === conversation.id && conversation.id
15368
15411
  }));
@@ -15401,16 +15444,18 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
15401
15444
  "remove-user": ""
15402
15445
  }, null, 8, ["participants"])),
15403
15446
  createBaseVNode("span", _hoisted_3$6, toDisplayString$1(unref(dayjs)(conversation.lastMessage?.history.created).fromNow()), 1),
15404
- conversation.lastMessage ? (openBlock(), createElementBlock("p", _hoisted_4$3, toDisplayString$1(conversation.lastMessage.message), 1)) : createCommentVNode("", true),
15405
- conversation.unread ? (openBlock(), createElementBlock("span", _hoisted_5$3, toDisplayString$1(conversation.unreadCount), 1)) : createCommentVNode("", true),
15406
- conversation.isNew ? (openBlock(), createElementBlock("span", _hoisted_6$2, "draft")) : createCommentVNode("", true)
15407
- ], 10, _hoisted_2$a);
15447
+ createBaseVNode("section", _hoisted_4$3, [
15448
+ conversation.lastMessage ? (openBlock(), createElementBlock("p", _hoisted_5$3, toDisplayString$1(conversation.lastMessage.message), 1)) : createCommentVNode("", true),
15449
+ conversation.unread ? (openBlock(), createElementBlock("span", _hoisted_6$2, toDisplayString$1(conversation.unreadCount), 1)) : createCommentVNode("", true)
15450
+ ]),
15451
+ conversation.isNew ? (openBlock(), createElementBlock("span", _hoisted_7$2, "draft")) : createCommentVNode("", true)
15452
+ ], 10, _hoisted_2$b);
15408
15453
  }), 128))
15409
15454
  ]);
15410
15455
  };
15411
15456
  }
15412
15457
  });
15413
- const _style_0$f = ".conversations-module[data-v-31a1e26e]{display:grid;grid-auto-rows:max-content}.conversations-module .single-conversation[data-v-31a1e26e]{padding:20px;gap:5px;cursor:pointer;display:grid;grid-template-columns:1fr 1fr}.conversations-module .single-conversation .time[data-v-31a1e26e]{justify-self:end}.conversations-module .single-conversation .lastMessage[data-v-31a1e26e]{grid-column:1 / 3;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.conversations-module .single-conversation[data-v-31a1e26e]:hover{background-color:var(--scm-color-surface)}.conversations-module .single-conversation.active[data-v-31a1e26e]{background-color:color-mix(in srgb,var(--scm-color-surface) 85%,var(--scm-color-muted))}.conversations-module .single-conversation.support[data-v-31a1e26e]{font-weight:600;border-left:3px solid var(--scm-color-primary)}";
15458
+ const _style_0$g = ".conversations-module[data-v-c7853fe0]{display:grid;grid-auto-rows:max-content}.conversations-module .single-conversation[data-v-c7853fe0]{padding:20px;gap:5px;cursor:pointer;display:grid;grid-template-columns:1fr 1fr}.conversations-module .single-conversation .time[data-v-c7853fe0]{justify-self:end}.conversations-module .single-conversation .message-unread-container[data-v-c7853fe0]{display:grid;grid-column:1 / 3;grid-template-columns:5fr 1fr}.conversations-module .single-conversation .message-unread-container .lastMessage[data-v-c7853fe0]{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.conversations-module .single-conversation .message-unread-container .unread[data-v-c7853fe0]{align-self:center;justify-self:end;padding:0 4px;border-radius:8px;background-color:var(--scm-color-own);color:#fff;font-size:.8rem}.conversations-module .single-conversation[data-v-c7853fe0]:hover{background-color:var(--scm-color-surface)}.conversations-module .single-conversation.active[data-v-c7853fe0]{background-color:color-mix(in srgb,var(--scm-color-surface) 85%,var(--scm-color-muted))}.conversations-module .single-conversation.support[data-v-c7853fe0]{font-weight:600;border-left:3px solid var(--scm-color-primary)}";
15414
15459
  const _export_sfc = (sfc, props) => {
15415
15460
  const target = sfc.__vccOpts || sfc;
15416
15461
  for (const [key, val] of props) {
@@ -15418,8 +15463,8 @@ const _export_sfc = (sfc, props) => {
15418
15463
  }
15419
15464
  return target;
15420
15465
  };
15421
- const ConversationsModule = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["styles", [_style_0$f]], ["__scopeId", "data-v-31a1e26e"]]);
15422
- const _sfc_main$i = /* @__PURE__ */ defineComponent({
15466
+ const ConversationsModule = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["styles", [_style_0$g]], ["__scopeId", "data-v-c7853fe0"]]);
15467
+ const _sfc_main$j = /* @__PURE__ */ defineComponent({
15423
15468
  __name: "chats.module",
15424
15469
  setup(__props) {
15425
15470
  const conversationsStore = useConversationsStore();
@@ -15437,11 +15482,13 @@ class WebSocketManager {
15437
15482
  retryCount = 0;
15438
15483
  retryDelay = 1e3;
15439
15484
  retryTimeoutId = null;
15485
+ connectTimeoutId = null;
15440
15486
  // Number of active consumers (components/composables) holding the socket
15441
15487
  refCount = 0;
15442
15488
  onConnectionChange;
15443
15489
  heartbeatIntervalId = null;
15444
15490
  lastActivity = 0;
15491
+ recoveryListenersInstalled = false;
15445
15492
  constructor() {
15446
15493
  }
15447
15494
  static getInstance() {
@@ -15472,12 +15519,9 @@ class WebSocketManager {
15472
15519
  url,
15473
15520
  maxRetries = 50,
15474
15521
  maxRetryDelay = 3e4,
15475
- onMessage,
15476
- onOpen,
15477
- onClose,
15478
- onError,
15479
15522
  heartbeatIntervalMs = 3e4,
15480
15523
  inactivityTimeoutMs = 6e4,
15524
+ connectTimeoutMs = 1e4,
15481
15525
  buildPingMessage
15482
15526
  } = options;
15483
15527
  this.currentOptions = {
@@ -15486,8 +15530,10 @@ class WebSocketManager {
15486
15530
  maxRetryDelay,
15487
15531
  heartbeatIntervalMs,
15488
15532
  inactivityTimeoutMs,
15533
+ connectTimeoutMs,
15489
15534
  buildPingMessage
15490
15535
  };
15536
+ this.installRecoveryListeners();
15491
15537
  if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {
15492
15538
  logger.websocket("WS already connecting/open; reusing existing socket");
15493
15539
  return;
@@ -15496,33 +15542,76 @@ class WebSocketManager {
15496
15542
  clearTimeout(this.retryTimeoutId);
15497
15543
  this.retryTimeoutId = null;
15498
15544
  }
15545
+ if (this.socket) this.detachHandlers(this.socket);
15499
15546
  const ws = new WebSocket(url);
15500
15547
  this.socket = ws;
15548
+ this.clearConnectTimeout();
15549
+ this.connectTimeoutId = setTimeout(() => {
15550
+ if (ws.readyState === WebSocket.CONNECTING) {
15551
+ logger.websocket("connect timeout: handshake not completed, aborting socket");
15552
+ try {
15553
+ ws.close();
15554
+ } catch {
15555
+ }
15556
+ }
15557
+ }, connectTimeoutMs);
15501
15558
  ws.onopen = () => {
15559
+ this.clearConnectTimeout();
15502
15560
  this.retryCount = 0;
15503
15561
  this.lastActivity = Date.now();
15504
15562
  logger.websocket(`connected (readyState ${ws.readyState})`);
15505
15563
  this.onConnectionChange?.(true);
15506
- onOpen?.();
15564
+ this.currentOptions?.onOpen?.();
15507
15565
  this.startHeartbeat();
15508
15566
  };
15509
15567
  ws.onmessage = (event) => {
15510
15568
  this.lastActivity = Date.now();
15511
- onMessage?.(event.data);
15569
+ this.currentOptions?.onMessage?.(event.data);
15512
15570
  };
15513
15571
  ws.onerror = (error) => {
15514
15572
  console.error("WebSocket error:", error);
15515
- onError?.(error);
15573
+ this.currentOptions?.onError?.(error);
15516
15574
  };
15517
15575
  ws.onclose = (ev) => {
15576
+ this.clearConnectTimeout();
15518
15577
  this.stopHeartbeat();
15519
15578
  const everOpened = ev.code !== 1006 || ws.readyState === WebSocket.CLOSED;
15520
15579
  logger.websocket(`disconnected (code ${ev.code}, clean ${ev.wasClean}, everOpened ${everOpened})`);
15521
15580
  this.onConnectionChange?.(false);
15522
- onClose?.(ev);
15581
+ this.currentOptions?.onClose?.(ev);
15523
15582
  if (this.refCount > 0) this.attemptReconnect();
15524
15583
  };
15525
15584
  }
15585
+ detachHandlers(ws) {
15586
+ ws.onopen = null;
15587
+ ws.onmessage = null;
15588
+ ws.onerror = null;
15589
+ ws.onclose = null;
15590
+ }
15591
+ clearConnectTimeout() {
15592
+ if (this.connectTimeoutId) {
15593
+ clearTimeout(this.connectTimeoutId);
15594
+ this.connectTimeoutId = null;
15595
+ }
15596
+ }
15597
+ /**
15598
+ * after wake/offline the retry chain may be exhausted or dead — coming back online or
15599
+ * refocusing the tab is the moment to start over with a fresh socket
15600
+ */
15601
+ installRecoveryListeners() {
15602
+ if (this.recoveryListenersInstalled || typeof window === "undefined") return;
15603
+ this.recoveryListenersInstalled = true;
15604
+ window.addEventListener("online", () => this.recover("online"));
15605
+ document.addEventListener("visibilitychange", () => {
15606
+ if (document.visibilityState === "visible") this.recover("visible");
15607
+ });
15608
+ }
15609
+ recover(reason) {
15610
+ if (this.refCount === 0 || !this.currentOptions) return;
15611
+ if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) return;
15612
+ logger.websocket(`recovery (${reason}): socket not open, reconnecting`);
15613
+ this.reconnect();
15614
+ }
15526
15615
  startHeartbeat() {
15527
15616
  this.stopHeartbeat();
15528
15617
  const heartbeatIntervalMs = this.currentOptions?.heartbeatIntervalMs ?? 3e4;
@@ -15602,15 +15691,18 @@ class WebSocketManager {
15602
15691
  }
15603
15692
  _teardownSocket(reason) {
15604
15693
  this.stopHeartbeat();
15694
+ this.clearConnectTimeout();
15605
15695
  if (this.retryTimeoutId) {
15606
15696
  clearTimeout(this.retryTimeoutId);
15607
15697
  this.retryTimeoutId = null;
15608
15698
  }
15609
15699
  const ws = this.socket;
15610
15700
  this.socket = null;
15611
- if (ws && ws.readyState === WebSocket.OPEN) {
15701
+ if (!ws) return;
15702
+ this.detachHandlers(ws);
15703
+ if (ws.readyState === WebSocket.OPEN) {
15612
15704
  ws.close(1e3, reason);
15613
- } else if (ws && ws.readyState === WebSocket.CONNECTING) {
15705
+ } else if (ws.readyState === WebSocket.CONNECTING) {
15614
15706
  try {
15615
15707
  ws.close();
15616
15708
  } catch {
@@ -15621,7 +15713,6 @@ class WebSocketManager {
15621
15713
  if (this.socket && this.isConnected) {
15622
15714
  try {
15623
15715
  this.socket.send(JSON.stringify(message));
15624
- this.lastActivity = Date.now();
15625
15716
  return true;
15626
15717
  } catch {
15627
15718
  return false;
@@ -15630,32 +15721,323 @@ class WebSocketManager {
15630
15721
  return false;
15631
15722
  }
15632
15723
  }
15724
+ let getTokenImpl = null;
15725
+ function setGetToken(fn) {
15726
+ getTokenImpl = fn;
15727
+ }
15728
+ async function resolveToken() {
15729
+ if (!getTokenImpl) return void 0;
15730
+ return await getTokenImpl();
15731
+ }
15732
+ const PROBE_TYPE = "session:probe";
15733
+ const PROBE_INTERVAL_MS = 45e3;
15734
+ const REAUTH_MIN_INTERVAL_MS = 2e3;
15735
+ const REAUTH_MAX_ATTEMPTS = 3;
15736
+ const REAUTH_BACKOFF_MS = 2e3;
15737
+ const QUEUE_MAX = 100;
15738
+ const PENDING_MAX = 50;
15739
+ const PENDING_MAX_AGE_MS = 5 * 60 * 1e3;
15740
+ function textMatches(sent, echoed) {
15741
+ if (typeof sent !== "string" || typeof echoed !== "string") return false;
15742
+ return sent === echoed || sent.slice(0, 1e4) === echoed;
15743
+ }
15744
+ class ChatSession {
15745
+ transport;
15746
+ options = null;
15747
+ state = "disconnected";
15748
+ everReady = false;
15749
+ lostSinceReady = false;
15750
+ reauthAttempts = 0;
15751
+ lastAuthSentAt = 0;
15752
+ reauthTimerId = null;
15753
+ /** backlog waiting for the session to become ready */
15754
+ queue = [];
15755
+ /** sent but not yet confirmed by a server echo */
15756
+ pending = [];
15757
+ probeIntervalId = null;
15758
+ constructor(transport) {
15759
+ this.transport = transport;
15760
+ }
15761
+ get sessionState() {
15762
+ return this.state;
15763
+ }
15764
+ start(options) {
15765
+ this.options = options;
15766
+ this.installProbe();
15767
+ this.setState("connecting");
15768
+ this.transport.connect({
15769
+ url: options.url,
15770
+ maxRetries: Infinity,
15771
+ // chat is the product — never stop trying
15772
+ maxRetryDelay: 3e4,
15773
+ onOpen: () => this.handleOpen(),
15774
+ onMessage: (raw) => this.handleFrame(raw),
15775
+ onClose: () => this.handleClose()
15776
+ });
15777
+ if (this.transport.isConnected && this.state === "connecting") this.handleOpen();
15778
+ }
15779
+ /**
15780
+ * Send a frame through the session. Returns false only for unqueued frames that could
15781
+ * not be sent right now; queued frames always succeed eventually or expire.
15782
+ */
15783
+ send(payload, sendOptions = {}) {
15784
+ const entry = { payload, ...sendOptions };
15785
+ if (this.state === "ready" && this.transport.isConnected) return this.dispatch(entry);
15786
+ if (!entry.queue) return false;
15787
+ this.enqueue(entry);
15788
+ return true;
15789
+ }
15790
+ /** teardown (tests / HMR) — the socket itself is refcounted by the transport */
15791
+ destroy() {
15792
+ this.clearReauthTimer();
15793
+ if (this.probeIntervalId) {
15794
+ clearInterval(this.probeIntervalId);
15795
+ this.probeIntervalId = null;
15796
+ }
15797
+ if (typeof window !== "undefined") {
15798
+ window.removeEventListener("online", this.probeNow);
15799
+ document.removeEventListener("visibilitychange", this.onVisibilityChange);
15800
+ }
15801
+ this.queue = [];
15802
+ this.pending = [];
15803
+ this.setState("disconnected");
15804
+ }
15805
+ handleOpen() {
15806
+ this.clearReauthTimer();
15807
+ this.reauthAttempts = 0;
15808
+ this.setState("authenticating");
15809
+ this.authenticate();
15810
+ }
15811
+ handleClose() {
15812
+ this.clearReauthTimer();
15813
+ if (this.everReady) this.lostSinceReady = true;
15814
+ this.setState("connecting");
15815
+ }
15816
+ async authenticate() {
15817
+ this.lastAuthSentAt = Date.now();
15818
+ let token;
15819
+ try {
15820
+ token = await resolveToken();
15821
+ } catch (error) {
15822
+ logger.session(`token resolution failed: ${error instanceof Error ? error.message : error}`);
15823
+ }
15824
+ token ??= this.options?.fallbackToken?.();
15825
+ if (!token) {
15826
+ logger.session("no auth token available; retrying");
15827
+ this.scheduleReauth(REAUTH_BACKOFF_MS);
15828
+ return;
15829
+ }
15830
+ if (!this.transport.send({ type: "authenticate", token })) return;
15831
+ this.setReady();
15832
+ }
15833
+ setReady() {
15834
+ const restored = this.everReady && this.lostSinceReady;
15835
+ this.everReady = true;
15836
+ this.lostSinceReady = false;
15837
+ this.setState("ready");
15838
+ if (restored) {
15839
+ this.requeuePending();
15840
+ this.options?.onSessionRestored?.();
15841
+ }
15842
+ this.flushQueue();
15843
+ }
15844
+ setState(next) {
15845
+ if (this.state === next) return;
15846
+ this.state = next;
15847
+ logger.session(`state → ${next}`);
15848
+ this.options?.onStateChange?.(next);
15849
+ }
15850
+ handleFrame(raw) {
15851
+ let frame;
15852
+ try {
15853
+ frame = JSON.parse(raw);
15854
+ } catch {
15855
+ logger.session("unparseable frame received; ignored");
15856
+ return;
15857
+ }
15858
+ if (!frame || typeof frame.type !== "string") return;
15859
+ if (frame.type === "pong" || frame.type === "welcome") return;
15860
+ if (frame.type === "authenticated") {
15861
+ this.reauthAttempts = 0;
15862
+ if (this.state !== "ready") this.setReady();
15863
+ return;
15864
+ }
15865
+ if (frame.type === "session:alive") {
15866
+ this.reauthAttempts = 0;
15867
+ return;
15868
+ }
15869
+ if (frame.type === "error") {
15870
+ this.handleError(frame);
15871
+ return;
15872
+ }
15873
+ this.reauthAttempts = 0;
15874
+ this.confirmEcho(frame);
15875
+ try {
15876
+ this.options?.onMessage?.(frame);
15877
+ } catch (error) {
15878
+ logger.session(`message handler failed: ${error instanceof Error ? error.message : error}`);
15879
+ }
15880
+ }
15881
+ handleError(frame) {
15882
+ const error = String(frame.error ?? "");
15883
+ const messageType = typeof frame.messageType === "string" ? frame.messageType : void 0;
15884
+ if (error === "UNKNOWN_CLIENT") {
15885
+ this.handleSessionLost(messageType ?? "unknown");
15886
+ return;
15887
+ }
15888
+ if (error === "AUTH_FAILED") {
15889
+ this.handleAuthFailed();
15890
+ return;
15891
+ }
15892
+ if (messageType === PROBE_TYPE) {
15893
+ this.reauthAttempts = 0;
15894
+ return;
15895
+ }
15896
+ if (messageType) this.settleRejected(messageType);
15897
+ logger.session(`server error ${error}${messageType ? ` (${messageType})` : ""}`);
15898
+ this.options?.onMessage?.(frame);
15899
+ }
15900
+ handleSessionLost(context) {
15901
+ this.lostSinceReady = true;
15902
+ if (this.state === "ready") this.setState("authenticating");
15903
+ if (this.state !== "authenticating") return;
15904
+ if (this.reauthAttempts >= REAUTH_MAX_ATTEMPTS) {
15905
+ logger.session("re-authentication keeps failing; forcing reconnect");
15906
+ this.hardReconnect();
15907
+ return;
15908
+ }
15909
+ logger.session(`session lost (${context}); re-authenticating`);
15910
+ this.reauthAttempts++;
15911
+ const wait = REAUTH_MIN_INTERVAL_MS - (Date.now() - this.lastAuthSentAt);
15912
+ if (wait > 0) this.scheduleReauth(wait);
15913
+ else this.authenticate();
15914
+ }
15915
+ handleAuthFailed() {
15916
+ this.lostSinceReady = true;
15917
+ if (this.state === "ready") this.setState("authenticating");
15918
+ this.reauthAttempts++;
15919
+ if (this.reauthAttempts > REAUTH_MAX_ATTEMPTS) {
15920
+ logger.session("authentication failing repeatedly; forcing reconnect");
15921
+ this.hardReconnect();
15922
+ return;
15923
+ }
15924
+ logger.session(`authentication failed (attempt ${this.reauthAttempts}); retrying with a fresh token`);
15925
+ this.scheduleReauth(REAUTH_BACKOFF_MS * this.reauthAttempts);
15926
+ }
15927
+ hardReconnect() {
15928
+ this.clearReauthTimer();
15929
+ this.reauthAttempts = 0;
15930
+ this.setState("connecting");
15931
+ this.transport.reconnect();
15932
+ }
15933
+ scheduleReauth(delayMs) {
15934
+ if (this.reauthTimerId) return;
15935
+ this.reauthTimerId = setTimeout(() => {
15936
+ this.reauthTimerId = null;
15937
+ if (this.state === "authenticating" && this.transport.isConnected) this.authenticate();
15938
+ }, delayMs);
15939
+ }
15940
+ clearReauthTimer() {
15941
+ if (this.reauthTimerId) {
15942
+ clearTimeout(this.reauthTimerId);
15943
+ this.reauthTimerId = null;
15944
+ }
15945
+ }
15946
+ /** server echoes settle tracked sends: own message echo / conversation:new with our client id */
15947
+ confirmEcho(frame) {
15948
+ if (frame.type === "message") {
15949
+ const ownUser = this.options?.ownUserId?.();
15950
+ if (!ownUser || frame.user !== ownUser) return;
15951
+ const index = this.pending.findIndex((entry) => entry.payload.type === "message:add" && entry.payload.conversation === frame.conversation && textMatches(entry.payload.message, frame.message));
15952
+ if (index !== -1) this.pending.splice(index, 1);
15953
+ return;
15954
+ }
15955
+ if (frame.type === "conversation:new" && frame.id) {
15956
+ const index = this.pending.findIndex((entry) => entry.payload.type === "conversation:start" && entry.payload.id === frame.id);
15957
+ if (index !== -1) this.pending.splice(index, 1);
15958
+ }
15959
+ }
15960
+ settleRejected(messageType) {
15961
+ const index = this.pending.findIndex((entry) => entry.payload.type === messageType);
15962
+ if (index !== -1) this.pending.splice(index, 1);
15963
+ }
15964
+ dispatch(entry) {
15965
+ if (!this.transport.send(entry.payload)) {
15966
+ if (entry.queue) this.queue.unshift(entry);
15967
+ return false;
15968
+ }
15969
+ if (entry.track) {
15970
+ this.pending.push({ ...entry, sentAt: Date.now() });
15971
+ if (this.pending.length > PENDING_MAX) this.pending.splice(0, this.pending.length - PENDING_MAX);
15972
+ }
15973
+ return true;
15974
+ }
15975
+ enqueue(entry) {
15976
+ if (entry.replaceKey) this.queue = this.queue.filter((queued) => queued.replaceKey !== entry.replaceKey);
15977
+ this.queue.push(entry);
15978
+ if (this.queue.length > QUEUE_MAX) {
15979
+ this.queue.splice(0, this.queue.length - QUEUE_MAX);
15980
+ logger.session("outbound queue overflow; oldest entries dropped");
15981
+ }
15982
+ }
15983
+ /** unconfirmed sends from before the loss go to the front of the backlog for resend */
15984
+ requeuePending() {
15985
+ if (!this.pending.length) return;
15986
+ const now = Date.now();
15987
+ const fresh = this.pending.filter((entry) => now - (entry.sentAt ?? now) < PENDING_MAX_AGE_MS);
15988
+ const dropped = this.pending.length - fresh.length;
15989
+ if (dropped) logger.session(`${dropped} unconfirmed frame(s) too old to resend; dropped`);
15990
+ this.pending = [];
15991
+ this.queue = [...fresh, ...this.queue];
15992
+ }
15993
+ flushQueue() {
15994
+ if (this.state !== "ready" || !this.queue.length) return;
15995
+ const backlog = this.queue;
15996
+ this.queue = [];
15997
+ logger.session(`flushing ${backlog.length} queued frame(s)`);
15998
+ for (let index = 0; index < backlog.length; index++) {
15999
+ if (!this.dispatch(backlog[index])) {
16000
+ this.queue.push(...backlog.slice(index + 1).filter((rest) => rest.queue));
16001
+ return;
16002
+ }
16003
+ }
16004
+ }
16005
+ installProbe() {
16006
+ if (this.probeIntervalId || typeof window === "undefined") return;
16007
+ this.probeIntervalId = setInterval(() => this.probe(), PROBE_INTERVAL_MS);
16008
+ window.addEventListener("online", this.probeNow);
16009
+ document.addEventListener("visibilitychange", this.onVisibilityChange);
16010
+ }
16011
+ probeNow = () => this.probe();
16012
+ onVisibilityChange = () => {
16013
+ if (document.visibilityState === "visible") this.probe();
16014
+ };
16015
+ /**
16016
+ * the registry entry can die without any local traffic (eviction, server cleanup) and
16017
+ * an idle client would never notice — it just stops receiving messages. the periodic
16018
+ * probe forces the server to reveal a dead session via UNKNOWN_CLIENT.
16019
+ */
16020
+ probe() {
16021
+ if (this.state !== "ready" || !this.transport.isConnected) return;
16022
+ if (typeof document !== "undefined" && document.visibilityState !== "visible") return;
16023
+ this.transport.send({ type: PROBE_TYPE });
16024
+ }
16025
+ }
16026
+ const chatSession = new ChatSession(WebSocketManager.getInstance());
15633
16027
  function useWebSocket() {
15634
16028
  const wsManager = WebSocketManager.getInstance();
15635
- const mainStore = useMainStore();
15636
16029
  wsManager.acquire();
15637
- wsManager.onConnectionChange = (connected) => {
15638
- mainStore.state = connected ? "connected" : "disconnected";
15639
- };
15640
- const connect = (options) => wsManager.connect(options);
15641
- const disconnect = () => wsManager.disconnect();
15642
- const reconnect = () => wsManager.reconnect();
15643
- const conversationStart = (message, participants, name, id, operationMode) => wsManager.send({ type: "conversation:start", message, participants, name, id, operationMode });
15644
- const conversationLastRead = (conversation, lastReadId) => wsManager.send({ type: "conversation:lastRead", conversation, lastReadId });
15645
- const messageAdd = (message, conversation) => wsManager.send({ type: "message:add", message, conversation });
15646
- const authenticate = (token) => wsManager.send({ type: "authenticate", token });
15647
- const conversationTyping = (conversation, typing) => wsManager.send({ type: "conversation:typing", conversation, typing });
16030
+ const conversationStart = (message, participants, name, id, operationMode) => chatSession.send({ type: "conversation:start", message, participants, name, id, operationMode }, { queue: true, track: true });
16031
+ const conversationLastRead = (conversation, lastReadId) => chatSession.send({ type: "conversation:lastRead", conversation, lastReadId }, { queue: true, replaceKey: `lastRead:${conversation}` });
16032
+ const messageAdd = (message, conversation) => chatSession.send({ type: "message:add", message, conversation }, { queue: true, track: true });
16033
+ const conversationTyping = (conversation, typing) => chatSession.send({ type: "conversation:typing", conversation, typing });
15648
16034
  onUnmounted(() => {
15649
16035
  wsManager.release();
15650
16036
  });
15651
16037
  return {
15652
- connect,
15653
- disconnect,
15654
- reconnect,
15655
16038
  conversationStart,
15656
16039
  conversationLastRead,
15657
16040
  messageAdd,
15658
- authenticate,
15659
16041
  conversationTyping
15660
16042
  };
15661
16043
  }
@@ -15729,14 +16111,6 @@ function handleMessages(message) {
15729
16111
  }
15730
16112
  function setGetUsers(fn) {
15731
16113
  }
15732
- let getTokenImpl = null;
15733
- function setGetToken(fn) {
15734
- getTokenImpl = fn;
15735
- }
15736
- async function resolveToken() {
15737
- if (!getTokenImpl) return void 0;
15738
- return await getTokenImpl();
15739
- }
15740
16114
  function getName(name, type, namespace) {
15741
16115
  return `${name}-${type}`;
15742
16116
  }
@@ -16056,8 +16430,37 @@ function groupMessages(messages, user) {
16056
16430
  });
16057
16431
  return Object.values(dateGroups);
16058
16432
  }
16433
+ function useChatVisibility(element) {
16434
+ const mainStore = useMainStore();
16435
+ const elementVisible = /* @__PURE__ */ ref(false);
16436
+ const documentVisible = /* @__PURE__ */ ref(typeof document === "undefined" || document.visibilityState === "visible");
16437
+ const overlayed = computed(() => mainStore.viewMode === "compact" && !!mainStore.leftTarget);
16438
+ const chatVisible = computed(() => elementVisible.value && documentVisible.value && !overlayed.value);
16439
+ let observer;
16440
+ function onVisibilityChange() {
16441
+ documentVisible.value = document.visibilityState === "visible";
16442
+ }
16443
+ if (typeof document !== "undefined") document.addEventListener("visibilitychange", onVisibilityChange);
16444
+ watch(element, (target) => {
16445
+ observer?.disconnect();
16446
+ observer = void 0;
16447
+ if (!target || typeof IntersectionObserver === "undefined") {
16448
+ elementVisible.value = !!target;
16449
+ return;
16450
+ }
16451
+ observer = new IntersectionObserver(([entry]) => {
16452
+ elementVisible.value = entry.isIntersecting;
16453
+ });
16454
+ observer.observe(target);
16455
+ }, { immediate: true });
16456
+ onBeforeUnmount(() => {
16457
+ observer?.disconnect();
16458
+ if (typeof document !== "undefined") document.removeEventListener("visibilitychange", onVisibilityChange);
16459
+ });
16460
+ return { chatVisible };
16461
+ }
16059
16462
  const _hoisted_1$d = { class: "chat-messages" };
16060
- const _hoisted_2$9 = { class: "date-separator" };
16463
+ const _hoisted_2$a = { class: "date-separator" };
16061
16464
  const _hoisted_3$5 = { class: "message-content" };
16062
16465
  const _hoisted_4$2 = ["id"];
16063
16466
  const _hoisted_5$2 = {
@@ -16066,7 +16469,7 @@ const _hoisted_5$2 = {
16066
16469
  };
16067
16470
  const SCROLL_DEBOUNCE_MS = 100;
16068
16471
  const SCROLL_EDGE_PERCENT = 10;
16069
- const _sfc_main$h = /* @__PURE__ */ defineComponent({
16472
+ const _sfc_main$i = /* @__PURE__ */ defineComponent({
16070
16473
  __name: "chat.module",
16071
16474
  props: {
16072
16475
  _id: { type: String },
@@ -16093,6 +16496,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16093
16496
  const newChatExists = /* @__PURE__ */ ref(false);
16094
16497
  const existingChat = /* @__PURE__ */ ref(null);
16095
16498
  const mainStore = useMainStore();
16499
+ const { chatVisible } = useChatVisibility(chatMessagesRef);
16096
16500
  let scrollDebounceTimer = null;
16097
16501
  let typingIdleTimer = null;
16098
16502
  let lastTypingSent = 0;
@@ -16163,12 +16567,16 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16163
16567
  }
16164
16568
  }
16165
16569
  function updateLastRead() {
16570
+ if (!chatVisible.value) return;
16166
16571
  if (conversation.value?.participant?.lastReadId < conversation.value?.lastMessage?.id) {
16167
16572
  conversationLastRead(props._id, conversation.value.lastMessage.id);
16168
16573
  conversationsStore.setLastReadId(props._id, conversation.value.lastMessage.id);
16169
16574
  conversationsStore.updateUnread(props._id);
16170
16575
  }
16171
16576
  }
16577
+ watch(chatVisible, (visible) => {
16578
+ if (visible) updateLastRead();
16579
+ });
16172
16580
  function scrollToBottom() {
16173
16581
  if (chatMessagesRef.value) {
16174
16582
  chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
@@ -16301,7 +16709,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16301
16709
  key: dateGroup.date,
16302
16710
  class: "messages-group"
16303
16711
  }, [
16304
- createBaseVNode("div", _hoisted_2$9, toDisplayString$1(dateGroup.date), 1),
16712
+ createBaseVNode("div", _hoisted_2$a, toDisplayString$1(dateGroup.date), 1),
16305
16713
  (openBlock(true), createElementBlock(Fragment, null, renderList(dateGroup.userTimeGroups, (group, index) => {
16306
16714
  return openBlock(), createElementBlock("div", {
16307
16715
  key: index,
@@ -16350,8 +16758,8 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16350
16758
  };
16351
16759
  }
16352
16760
  });
16353
- const _style_0$e = ".chat-module[data-v-5c5c2e96]{display:grid;height:100%;grid-template-rows:max-content 1fr max-content;overflow:hidden}.chat-module.isNew[data-v-5c5c2e96]{grid-template-rows:max-content 1fr max-content}.chat-module .notice-partial[data-v-5c5c2e96]{position:absolute;align-self:center;justify-self:center;z-index:100}.chat-module .chat-messages-scroll-container[data-v-5c5c2e96]{overflow-y:auto;position:relative;padding-bottom:50px;display:grid}.chat-module .chat-messages-scroll-container .chat-messages[data-v-5c5c2e96]{display:grid;grid-auto-rows:max-content;align-content:end}.chat-module .chat-messages-scroll-container .chat-messages .messages-group[data-v-5c5c2e96]{display:grid;gap:20px;margin:0 20px;grid-auto-rows:max-content;align-content:end}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message[data-v-5c5c2e96]{display:grid;grid-template-rows:auto auto;gap:10px;justify-self:start}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.system[data-v-5c5c2e96]{width:100%;text-align:center;align-content:center;justify-content:center}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.system header[data-v-5c5c2e96]{justify-content:center;grid-template-columns:1fr}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.system header time[data-v-5c5c2e96]{text-align:center;color:var(--scm-color-muted);font-size:.75em}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.system .message-content p[data-v-5c5c2e96]{background-color:transparent;padding:0;color:var(--scm-color-muted);font-size:.75em;white-space:pre-wrap}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message header[data-v-5c5c2e96]{display:grid;grid-template-columns:max-content max-content;gap:10px;width:100%;font-size:.875em}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message header time[data-v-5c5c2e96]{text-align:right}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message .message-content[data-v-5c5c2e96]{display:grid;gap:4px}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message .message-content p[data-v-5c5c2e96]{padding:10px;border-radius:5px;background-color:var(--scm-color-primary);color:#fff;justify-self:start;white-space:pre-wrap;font-size:1em}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.isUser[data-v-5c5c2e96]{justify-self:end}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.isUser p[data-v-5c5c2e96]{justify-self:end;background-color:var(--scm-color-own);color:#fff}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.isUser header[data-v-5c5c2e96]{grid-template-columns:1fr}.chat-module .typing-indicator[data-v-5c5c2e96]{padding:4px 20px;font-size:.75em;color:var(--scm-color-muted);font-style:italic}.chat-module .date-separator[data-v-5c5c2e96]{text-align:center;padding:20px 0;font-size:.875em;font-weight:700}";
16354
- const ChatModule = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["styles", [_style_0$e]], ["__scopeId", "data-v-5c5c2e96"]]);
16761
+ const _style_0$f = ".chat-module[data-v-6c9f82e3]{display:grid;height:100%;grid-template-rows:max-content 1fr max-content;overflow:hidden}.chat-module.isNew[data-v-6c9f82e3]{grid-template-rows:max-content 1fr max-content}.chat-module .notice-partial[data-v-6c9f82e3]{position:absolute;align-self:center;justify-self:center;z-index:100}.chat-module .chat-messages-scroll-container[data-v-6c9f82e3]{overflow-y:auto;position:relative;padding-bottom:50px;display:grid}.chat-module .chat-messages-scroll-container .chat-messages[data-v-6c9f82e3]{display:grid;grid-auto-rows:max-content;align-content:end}.chat-module .chat-messages-scroll-container .chat-messages .messages-group[data-v-6c9f82e3]{display:grid;gap:20px;margin:0 20px;grid-auto-rows:max-content;align-content:end}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message[data-v-6c9f82e3]{display:grid;grid-template-rows:auto auto;gap:10px;justify-self:start}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.system[data-v-6c9f82e3]{width:100%;text-align:center;align-content:center;justify-content:center}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.system header[data-v-6c9f82e3]{justify-content:center;grid-template-columns:1fr}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.system header time[data-v-6c9f82e3]{text-align:center;color:var(--scm-color-muted);font-size:.75em}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.system .message-content p[data-v-6c9f82e3]{background-color:transparent;padding:0;color:var(--scm-color-muted);font-size:.75em;white-space:pre-wrap}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message header[data-v-6c9f82e3]{display:grid;grid-template-columns:max-content max-content;gap:10px;width:100%;font-size:.875em}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message header time[data-v-6c9f82e3]{text-align:right}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message .message-content[data-v-6c9f82e3]{display:grid;gap:4px}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message .message-content p[data-v-6c9f82e3]{padding:10px;border-radius:5px;background-color:var(--scm-color-primary);color:#fff;justify-self:start;white-space:pre-wrap;font-size:1em}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.isUser[data-v-6c9f82e3]{justify-self:end}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.isUser p[data-v-6c9f82e3]{justify-self:end;background-color:var(--scm-color-own);color:#fff}.chat-module .chat-messages-scroll-container .chat-messages .messages-group .message.isUser header[data-v-6c9f82e3]{grid-template-columns:1fr}.chat-module .typing-indicator[data-v-6c9f82e3]{padding:4px 20px;font-size:.75em;color:var(--scm-color-muted);font-style:italic}.chat-module .date-separator[data-v-6c9f82e3]{text-align:center;padding:20px 0;font-size:.875em;font-weight:700}";
16762
+ const ChatModule = /* @__PURE__ */ _export_sfc(_sfc_main$i, [["styles", [_style_0$f]], ["__scopeId", "data-v-6c9f82e3"]]);
16355
16763
  function sdkOptions() {
16356
16764
  const mainStore = useMainStore();
16357
16765
  return {
@@ -16380,7 +16788,7 @@ const useContactsStore = /* @__PURE__ */ defineStore("contacts", () => {
16380
16788
  };
16381
16789
  });
16382
16790
  const _hoisted_1$c = { class: "contacts-module" };
16383
- const _hoisted_2$8 = {
16791
+ const _hoisted_2$9 = {
16384
16792
  key: 0,
16385
16793
  class: "controls"
16386
16794
  };
@@ -16396,7 +16804,7 @@ const _hoisted_8$1 = {
16396
16804
  key: 0,
16397
16805
  class: "selected-indicator"
16398
16806
  };
16399
- const _sfc_main$g = /* @__PURE__ */ defineComponent({
16807
+ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16400
16808
  __name: "contacts.module",
16401
16809
  setup(__props) {
16402
16810
  const mainStore = useMainStore();
@@ -16499,7 +16907,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
16499
16907
  const _component_button_partial = resolveComponent("button-partial");
16500
16908
  const _component_user_avatar_partial = resolveComponent("user-avatar-partial");
16501
16909
  return openBlock(), createElementBlock("article", _hoisted_1$c, [
16502
- isEdit.value ? (openBlock(), createElementBlock("section", _hoisted_2$8, [
16910
+ isEdit.value ? (openBlock(), createElementBlock("section", _hoisted_2$9, [
16503
16911
  createVNode(_component_button_partial, {
16504
16912
  variant: "primary",
16505
16913
  size: "sm",
@@ -16587,15 +16995,31 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
16587
16995
  };
16588
16996
  }
16589
16997
  });
16590
- const _style_0$d = ".contacts-module[data-v-045be51e]{height:100%;overflow:scroll}.contacts-module .controls[data-v-045be51e]{display:grid;grid-template-columns:1fr 1fr}.contacts-module .rights-panel[data-v-045be51e]{padding:10px 20px;border-bottom:1px solid var(--scm-color-surface)}.contacts-module .rights-panel h4[data-v-045be51e]{margin-bottom:10px}.contacts-module .rights-panel .participant-rights[data-v-045be51e]{display:grid;gap:5px;padding:10px 0}.contacts-module .rights-panel .participant-rights .rights[data-v-045be51e]{display:flex;flex-wrap:wrap;gap:10px}.contacts-module .rights-panel .participant-rights .rights .right-toggle[data-v-045be51e]{cursor:pointer;font-size:.8125em}.contacts-module .rights-panel .participant-rights .who em[data-v-045be51e]{color:var(--scm-color-muted)}.contacts-module .single-contact[data-v-045be51e]{padding:20px;cursor:pointer;display:flex;justify-content:space-between;align-items:center}.contacts-module .single-contact[data-v-045be51e]:hover{background-color:var(--scm-color-surface)}.contacts-module .single-contact.selected[data-v-045be51e]{background-color:color-mix(in srgb,var(--scm-color-primary) 15%,transparent)}.contacts-module .single-contact .selected-indicator[data-v-045be51e]{color:var(--scm-color-primary);font-weight:700}";
16591
- const ContactsModule = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["styles", [_style_0$d]], ["__scopeId", "data-v-045be51e"]]);
16592
- const _hoisted_1$b = { class: "participants-partial" };
16998
+ const _style_0$e = ".contacts-module[data-v-045be51e]{height:100%;overflow:scroll}.contacts-module .controls[data-v-045be51e]{display:grid;grid-template-columns:1fr 1fr}.contacts-module .rights-panel[data-v-045be51e]{padding:10px 20px;border-bottom:1px solid var(--scm-color-surface)}.contacts-module .rights-panel h4[data-v-045be51e]{margin-bottom:10px}.contacts-module .rights-panel .participant-rights[data-v-045be51e]{display:grid;gap:5px;padding:10px 0}.contacts-module .rights-panel .participant-rights .rights[data-v-045be51e]{display:flex;flex-wrap:wrap;gap:10px}.contacts-module .rights-panel .participant-rights .rights .right-toggle[data-v-045be51e]{cursor:pointer;font-size:.8125em}.contacts-module .rights-panel .participant-rights .who em[data-v-045be51e]{color:var(--scm-color-muted)}.contacts-module .single-contact[data-v-045be51e]{padding:20px;cursor:pointer;display:flex;justify-content:space-between;align-items:center}.contacts-module .single-contact[data-v-045be51e]:hover{background-color:var(--scm-color-surface)}.contacts-module .single-contact.selected[data-v-045be51e]{background-color:color-mix(in srgb,var(--scm-color-primary) 15%,transparent)}.contacts-module .single-contact .selected-indicator[data-v-045be51e]{color:var(--scm-color-primary);font-weight:700}";
16999
+ const ContactsModule = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["styles", [_style_0$e]], ["__scopeId", "data-v-045be51e"]]);
17000
+ const _sfc_main$g = /* @__PURE__ */ defineComponent({
17001
+ __name: "user-name.partial",
17002
+ props: {
17003
+ userId: { type: String }
17004
+ },
17005
+ setup(__props) {
17006
+ const props = __props;
17007
+ const usersCache = useUsersCacheStore();
17008
+ onMounted(() => {
17009
+ usersCache.ensure([props.userId]);
17010
+ });
17011
+ return (_ctx, _cache) => {
17012
+ return toDisplayString$1(unref(usersCache).name(props.userId));
17013
+ };
17014
+ }
17015
+ });
16593
17016
  const _sfc_main$f = /* @__PURE__ */ defineComponent({
16594
17017
  __name: "participants.partial",
16595
17018
  props: {
16596
17019
  participants: { type: Array },
16597
17020
  removeUser: { type: Boolean },
16598
- isNew: { type: Boolean }
17021
+ isNew: { type: Boolean },
17022
+ inline: { type: Boolean }
16599
17023
  },
16600
17024
  setup(__props) {
16601
17025
  const userStore = useUserStore();
@@ -16608,29 +17032,35 @@ const _sfc_main$f = /* @__PURE__ */ defineComponent({
16608
17032
  });
16609
17033
  return (_ctx, _cache) => {
16610
17034
  const _component_user_avatar_partial = resolveComponent("user-avatar-partial");
16611
- return openBlock(), createElementBlock("section", _hoisted_1$b, [
17035
+ return openBlock(), createElementBlock("section", {
17036
+ class: normalizeClass(["participants-partial", { "participants-partial--inline": __props.inline }])
17037
+ }, [
16612
17038
  __props.isNew && !filteredParticipants.value.length ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
16613
17039
  createTextVNode(toDisplayString$1(_ctx.$t("select contacts")), 1)
16614
17040
  ], 64)) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(filteredParticipants.value, (participant, index) => {
16615
17041
  return openBlock(), createElementBlock(Fragment, {
16616
17042
  key: unref(participantId)(participant)
16617
17043
  }, [
16618
- createVNode(_component_user_avatar_partial, {
17044
+ __props.inline ? (openBlock(), createBlock(_sfc_main$g, {
17045
+ key: 0,
16619
17046
  "user-id": unref(participantId)(participant)
16620
- }, null, 8, ["user-id"]),
16621
- index < filteredParticipants.value.length - 1 ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
17047
+ }, null, 8, ["user-id"])) : (openBlock(), createBlock(_component_user_avatar_partial, {
17048
+ key: 1,
17049
+ "user-id": unref(participantId)(participant)
17050
+ }, null, 8, ["user-id"])),
17051
+ index < filteredParticipants.value.length - 1 ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
16622
17052
  createTextVNode(toDisplayString$1(", "))
16623
17053
  ], 64)) : createCommentVNode("", true)
16624
17054
  ], 64);
16625
17055
  }), 128))
16626
- ]);
17056
+ ], 2);
16627
17057
  };
16628
17058
  }
16629
17059
  });
16630
- const _style_0$c = "";
16631
- const ParticipantsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["styles", [_style_0$c]], ["__scopeId", "data-v-f06a1cdf"]]);
16632
- const _hoisted_1$a = { class: "actions" };
16633
- const _hoisted_2$7 = { class: "left" };
17060
+ const _style_0$d = ".participants-partial--inline[data-v-ee2866aa]{font-size:inherit}";
17061
+ const ParticipantsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["styles", [_style_0$d]], ["__scopeId", "data-v-ee2866aa"]]);
17062
+ const _hoisted_1$b = { class: "actions" };
17063
+ const _hoisted_2$8 = { class: "left" };
16634
17064
  const _hoisted_3$3 = { class: "right" };
16635
17065
  const _sfc_main$e = /* @__PURE__ */ defineComponent({
16636
17066
  __name: "actions.partial",
@@ -16639,17 +17069,11 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
16639
17069
  const conversationsStore = useConversationsStore();
16640
17070
  const userStore = useUserStore();
16641
17071
  const hasChat = computed(() => mainStore.hasChat);
16642
- const showConversationsButton = computed(
16643
- () => hasChat.value || conversationsStore.conversations.length > 1
16644
- );
16645
17072
  const createConversation = () => {
16646
17073
  const tempId = conversationsStore.createNew([userStore.user], userStore.user);
16647
17074
  conversationsStore.activateConversation(tempId);
16648
- mainStore.showNewChat();
17075
+ mainStore.hideLeft();
16649
17076
  };
16650
- function showConversations() {
16651
- mainStore.showConversations();
16652
- }
16653
17077
  function close() {
16654
17078
  if (mainStore.leftTarget === "contacts-edit" || mainStore.leftTarget === "contacts-new") {
16655
17079
  mainStore.showConversations();
@@ -16658,18 +17082,13 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
16658
17082
  mainStore.closeCallback?.();
16659
17083
  }
16660
17084
  return (_ctx, _cache) => {
17085
+ const _component_conversations_button_partial = resolveComponent("conversations-button-partial");
16661
17086
  const _component_button_partial = resolveComponent("button-partial");
16662
- return openBlock(), createElementBlock("section", _hoisted_1$a, [
16663
- createBaseVNode("section", _hoisted_2$7, [
16664
- showConversationsButton.value ? (openBlock(), createBlock(_component_button_partial, {
16665
- key: 0,
16666
- icon: "chat",
16667
- variant: "secondary",
16668
- "aria-label": "Conversations",
16669
- onClick: showConversations
16670
- })) : createCommentVNode("", true),
17087
+ return openBlock(), createElementBlock("section", _hoisted_1$b, [
17088
+ createBaseVNode("section", _hoisted_2$8, [
17089
+ createVNode(_component_conversations_button_partial),
16671
17090
  hasChat.value ? (openBlock(), createBlock(_component_button_partial, {
16672
- key: 1,
17091
+ key: 0,
16673
17092
  icon: "add",
16674
17093
  "aria-label": "New chat",
16675
17094
  onClick: createConversation
@@ -16686,10 +17105,10 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
16686
17105
  };
16687
17106
  }
16688
17107
  });
16689
- const _style_0$b = ".actions[data-v-b803cf6d]{display:grid;grid-template-columns:max-content 1fr max-content;width:100%;grid-column:1/3}.actions .left[data-v-b803cf6d]{grid-column:1/2;display:flex}.actions .right[data-v-b803cf6d]{grid-column:3/4}";
16690
- const ActionsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["styles", [_style_0$b]], ["__scopeId", "data-v-b803cf6d"]]);
16691
- const _hoisted_1$9 = { class: "background-container" };
16692
- const _hoisted_2$6 = { key: 0 };
17108
+ const _style_0$c = ".actions[data-v-e7780b44]{display:grid;grid-template-columns:max-content 1fr max-content;width:100%;grid-column:1/3}.actions .left[data-v-e7780b44]{grid-column:1/2;display:flex}.actions .right[data-v-e7780b44]{grid-column:3/4}";
17109
+ const ActionsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["styles", [_style_0$c]], ["__scopeId", "data-v-e7780b44"]]);
17110
+ const _hoisted_1$a = { class: "background-container" };
17111
+ const _hoisted_2$7 = { key: 0 };
16693
17112
  const _sfc_main$d = /* @__PURE__ */ defineComponent({
16694
17113
  __name: "chat-header.partial",
16695
17114
  props: {
@@ -16760,7 +17179,7 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
16760
17179
  return openBlock(), createElementBlock("header", {
16761
17180
  class: normalizeClass(["chat-header", { isSupport: isSupport.value }])
16762
17181
  }, [
16763
- createBaseVNode("section", _hoisted_1$9, [
17182
+ createBaseVNode("section", _hoisted_1$a, [
16764
17183
  createBaseVNode("h3", null, [
16765
17184
  !isSupport.value ? (openBlock(), createBlock(_component_name_edit_partial, {
16766
17185
  key: 0,
@@ -16775,9 +17194,10 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
16775
17194
  createTextVNode(toDisplayString$1(_ctx.$t("support")), 1)
16776
17195
  ], 64)) : createCommentVNode("", true)
16777
17196
  ]),
16778
- !isSupport.value && conversationData.value ? (openBlock(), createElementBlock("h4", _hoisted_2$6, [
17197
+ !isSupport.value && conversationData.value ? (openBlock(), createElementBlock("h4", _hoisted_2$7, [
16779
17198
  createVNode(_component_participants_partial, {
16780
17199
  participants: conversationData.value.participants,
17200
+ inline: "",
16781
17201
  "remove-user": "",
16782
17202
  "is-new": isNew.value
16783
17203
  }, null, 8, ["participants", "is-new"]),
@@ -16785,13 +17205,9 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
16785
17205
  key: 0,
16786
17206
  size: "sm",
16787
17207
  variant: "ghost",
17208
+ icon: "edit",
16788
17209
  "aria-label": "Toggle edit mode",
16789
17210
  onClick: _cache[0] || (_cache[0] = ($event) => toggleEdit())
16790
- }, {
16791
- default: withCtx(() => [..._cache[1] || (_cache[1] = [
16792
- createTextVNode(" edit ", -1)
16793
- ])]),
16794
- _: 1
16795
17211
  })) : createCommentVNode("", true)
16796
17212
  ])) : createCommentVNode("", true),
16797
17213
  !isSupport.value && !isNew.value ? (openBlock(), createBlock(_component_chat_actions_partial, {
@@ -16803,8 +17219,8 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
16803
17219
  };
16804
17220
  }
16805
17221
  });
16806
- const _style_0$a = ".chat-header[data-v-beb2ed7a]{display:grid;padding:10px 20px}.chat-header .background-container[data-v-beb2ed7a]{display:grid;grid-template-columns:5fr 1fr;grid-template-rows:1fr 1fr;align-items:center;justify-items:start;padding:0 10px;background-color:var(--scm-color-surface);border-radius:5px}.chat-header .background-container h3[data-v-beb2ed7a],.chat-header .background-container h4[data-v-beb2ed7a]{grid-column:1 / 2}.chat-header .background-container h3[data-v-beb2ed7a]{grid-row:1}.chat-header .background-container h4[data-v-beb2ed7a]{display:grid;grid-row:2}.chat-header .background-container .chat-actions-partial[data-v-beb2ed7a]{justify-self:end;grid-row:1}";
16807
- const ChatHeaderPartial = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["styles", [_style_0$a]], ["__scopeId", "data-v-beb2ed7a"]]);
17222
+ const _style_0$b = ".chat-header[data-v-dd11b73a]{display:grid;padding:10px 20px}.chat-header .background-container[data-v-dd11b73a]{display:grid;grid-template-columns:5fr 1fr;grid-template-rows:max-content;align-content:start;padding:0 10px;background-color:var(--scm-color-surface);border-radius:5px}.chat-header .background-container h3[data-v-dd11b73a],.chat-header .background-container h4[data-v-dd11b73a]{margin:0;grid-column:1 / 2}.chat-header .background-container h3[data-v-dd11b73a]{grid-row:1;font-family:Open Sans;letter-spacing:-.1px}.chat-header .background-container h4[data-v-dd11b73a]{display:grid;grid-row:2}.chat-header .background-container .chat-actions-partial[data-v-dd11b73a]{justify-self:end;grid-row:1}";
17223
+ const ChatHeaderPartial = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["styles", [_style_0$b]], ["__scopeId", "data-v-dd11b73a"]]);
16808
17224
  const _sfc_main$c = /* @__PURE__ */ defineComponent({
16809
17225
  __name: "name-edit.partial",
16810
17226
  props: {
@@ -16848,17 +17264,13 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
16848
17264
  return openBlock(), createElementBlock(Fragment, null, [
16849
17265
  !edit.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
16850
17266
  renderSlot(_ctx.$slots, "default"),
16851
- _cache[2] || (_cache[2] = createTextVNode()),
17267
+ _cache[1] || (_cache[1] = createTextVNode()),
16852
17268
  createVNode(_component_button_partial, {
16853
17269
  size: "sm",
16854
17270
  variant: "ghost",
17271
+ icon: "edit",
16855
17272
  "aria-label": "Edit conversation name",
16856
17273
  onClick: toggleEdit
16857
- }, {
16858
- default: withCtx(() => [..._cache[1] || (_cache[1] = [
16859
- createTextVNode(" Edit ", -1)
16860
- ])]),
16861
- _: 1
16862
17274
  })
16863
17275
  ], 64)) : createCommentVNode("", true),
16864
17276
  edit.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
@@ -16878,7 +17290,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
16878
17290
  disabled: !name.value?.length,
16879
17291
  onClick: save
16880
17292
  }, {
16881
- default: withCtx(() => [..._cache[3] || (_cache[3] = [
17293
+ default: withCtx(() => [..._cache[2] || (_cache[2] = [
16882
17294
  createTextVNode(" Save ", -1)
16883
17295
  ])]),
16884
17296
  _: 1
@@ -16889,7 +17301,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
16889
17301
  "aria-label": "Cancel editing",
16890
17302
  onClick: toggleEdit
16891
17303
  }, {
16892
- default: withCtx(() => [..._cache[4] || (_cache[4] = [
17304
+ default: withCtx(() => [..._cache[3] || (_cache[3] = [
16893
17305
  createTextVNode(" X ", -1)
16894
17306
  ])]),
16895
17307
  _: 1
@@ -16899,7 +17311,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
16899
17311
  };
16900
17312
  }
16901
17313
  });
16902
- const _hoisted_1$8 = { class: "notice-partial" };
17314
+ const _hoisted_1$9 = { class: "notice-partial" };
16903
17315
  const _sfc_main$b = /* @__PURE__ */ defineComponent({
16904
17316
  __name: "notice.partial",
16905
17317
  props: {
@@ -16907,14 +17319,14 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
16907
17319
  },
16908
17320
  setup(__props) {
16909
17321
  return (_ctx, _cache) => {
16910
- return openBlock(), createElementBlock("section", _hoisted_1$8, [
17322
+ return openBlock(), createElementBlock("section", _hoisted_1$9, [
16911
17323
  renderSlot(_ctx.$slots, "default", {}, void 0, true)
16912
17324
  ]);
16913
17325
  };
16914
17326
  }
16915
17327
  });
16916
- const _style_0$9 = ".notice-partial[data-v-2ad9f6c8]{display:grid}";
16917
- const NoticePartial = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["styles", [_style_0$9]], ["__scopeId", "data-v-2ad9f6c8"]]);
17328
+ const _style_0$a = ".notice-partial[data-v-2ad9f6c8]{display:grid}";
17329
+ const NoticePartial = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["styles", [_style_0$a]], ["__scopeId", "data-v-2ad9f6c8"]]);
16918
17330
  const _sfc_main$a = /* @__PURE__ */ defineComponent({
16919
17331
  ...{ inheritAttrs: false },
16920
17332
  __name: "system-message.partial",
@@ -16958,8 +17370,8 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
16958
17370
  };
16959
17371
  }
16960
17372
  });
16961
- const _hoisted_1$7 = { class: "chat-actions-partial" };
16962
- const _hoisted_2$5 = { class: "options-container" };
17373
+ const _hoisted_1$8 = { class: "chat-actions-partial" };
17374
+ const _hoisted_2$6 = { class: "options-container" };
16963
17375
  const _sfc_main$9 = /* @__PURE__ */ defineComponent({
16964
17376
  __name: "chat-actions.partial",
16965
17377
  props: {
@@ -16990,7 +17402,7 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
16990
17402
  const _component_button_partial = resolveComponent("button-partial");
16991
17403
  const _component_overlay_container_partial = resolveComponent("overlay-container-partial");
16992
17404
  const _component_dialog_partial = resolveComponent("dialog-partial");
16993
- return openBlock(), createElementBlock("section", _hoisted_1$7, [
17405
+ return openBlock(), createElementBlock("section", _hoisted_1$8, [
16994
17406
  createVNode(_component_button_partial, {
16995
17407
  ref_key: "attachTo",
16996
17408
  ref: attachTo,
@@ -17010,7 +17422,7 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
17010
17422
  onClose: toggle
17011
17423
  }, {
17012
17424
  default: withCtx(() => [
17013
- createBaseVNode("section", _hoisted_2$5, [
17425
+ createBaseVNode("section", _hoisted_2$6, [
17014
17426
  createBaseVNode("ul", null, [
17015
17427
  createBaseVNode("li", null, toDisplayString$1(_ctx.$t("mute")), 1),
17016
17428
  createBaseVNode("li", null, toDisplayString$1(_ctx.$t("delete")), 1),
@@ -17051,8 +17463,8 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
17051
17463
  };
17052
17464
  }
17053
17465
  });
17054
- const _style_0$8 = ".chat-actions-partial[data-v-871e6a65]{display:grid}.options-container[data-v-871e6a65]{width:max-content;min-width:200px;background-color:var(--scm-color-surface);box-shadow:0 0 5px var(--scm-color-muted)}";
17055
- const ChatActionsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["styles", [_style_0$8]], ["__scopeId", "data-v-871e6a65"]]);
17466
+ const _style_0$9 = ".chat-actions-partial[data-v-871e6a65]{display:grid}.options-container[data-v-871e6a65]{width:max-content;min-width:200px;background-color:var(--scm-color-surface);box-shadow:0 0 5px var(--scm-color-muted)}";
17467
+ const ChatActionsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["styles", [_style_0$9]], ["__scopeId", "data-v-871e6a65"]]);
17056
17468
  const _sfc_main$8 = /* @__PURE__ */ defineComponent({
17057
17469
  __name: "overlay-container.partial",
17058
17470
  props: {
@@ -17187,10 +17599,10 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
17187
17599
  };
17188
17600
  }
17189
17601
  });
17190
- const _style_0$7 = ".overlay-background{position:fixed;top:0;left:0;display:grid;width:100%;height:100%;background-color:#00000080;z-index:1000}.overlay-background.none,.overlay-background.tablet{background-color:transparent}@media only screen and (min-width: 600px) and (max-width: 1023px){.overlay-background.tablet{background-color:#00000080}}.overlay-background.desktop{background-color:transparent}@media only screen and (min-width: 600px) and (max-width: 1023px),only screen and (min-width: 1024px){.overlay-background.desktop{background-color:#00000080}}.overlay-background .content-container{z-index:1100;position:absolute}";
17191
- const OverlayContainerPartial = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["styles", [_style_0$7]]]);
17192
- const _hoisted_1$6 = ["type", "disabled", "aria-busy", "aria-label"];
17193
- const _hoisted_2$4 = {
17602
+ const _style_0$8 = ".overlay-background{position:fixed;top:0;left:0;display:grid;width:100%;height:100%;background-color:#00000080;z-index:1000}.overlay-background.none,.overlay-background.tablet{background-color:transparent}@media only screen and (min-width: 600px) and (max-width: 1023px){.overlay-background.tablet{background-color:#00000080}}.overlay-background.desktop{background-color:transparent}@media only screen and (min-width: 600px) and (max-width: 1023px),only screen and (min-width: 1024px){.overlay-background.desktop{background-color:#00000080}}.overlay-background .content-container{z-index:1100;position:absolute}";
17603
+ const OverlayContainerPartial = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["styles", [_style_0$8]]]);
17604
+ const _hoisted_1$7 = ["type", "disabled", "aria-busy", "aria-label"];
17605
+ const _hoisted_2$5 = {
17194
17606
  key: 0,
17195
17607
  class: "scm-btn__spinner",
17196
17608
  "aria-hidden": "true"
@@ -17243,19 +17655,19 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
17243
17655
  class: normalizeClass(classes.value),
17244
17656
  onClick
17245
17657
  }, [
17246
- props.loading ? (openBlock(), createElementBlock("span", _hoisted_2$4)) : props.icon ? (openBlock(), createBlock(_component_icons_partial, {
17658
+ props.loading ? (openBlock(), createElementBlock("span", _hoisted_2$5)) : props.icon ? (openBlock(), createBlock(_component_icons_partial, {
17247
17659
  key: 1,
17248
17660
  type: props.icon
17249
17661
  }, null, 8, ["type"])) : (openBlock(), createElementBlock("span", _hoisted_3$2, [
17250
17662
  renderSlot(_ctx.$slots, "default", {}, void 0, true)
17251
17663
  ]))
17252
- ], 10, _hoisted_1$6);
17664
+ ], 10, _hoisted_1$7);
17253
17665
  };
17254
17666
  }
17255
17667
  });
17256
- const _style_0$6 = ".scm-btn[data-v-a79bdb66]{--scm-btn-bg: var(--scm-color-primary);--scm-btn-color: white;--scm-btn-radius: 0;--scm-btn-padding-y: 4px;--scm-btn-padding-x: 10px;--scm-btn-font-size: .875em;--scm-btn-height: 40px;--scm-btn-line-height: 42px;--scm-btn-transition: .12s ease-in-out;appearance:none;border:none;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;background-color:var(--scm-btn-bg);color:var(--scm-btn-color);font:inherit;padding:var(--scm-btn-padding-y) var(--scm-btn-padding-x);font-size:var(--scm-btn-font-size);border-radius:var(--scm-btn-radius);height:var(--scm-btn-height);line-height:var(--scm-btn-line-height);transition:background-color var(--scm-btn-transition),opacity var(--scm-btn-transition),box-shadow var(--scm-btn-transition);text-decoration:none;white-space:nowrap}.scm-btn[data-v-a79bdb66]:hover:not(:disabled){background-color:color-mix(in srgb,var(--scm-btn-bg) 85%,#000)}.scm-btn[data-v-a79bdb66]:active:not(:disabled){background-color:color-mix(in srgb,var(--scm-btn-bg) 70%,#000)}.scm-btn[data-v-a79bdb66]:focus-visible{outline:2px solid var(--scm-color-primary);outline-offset:2px}.scm-btn[data-v-a79bdb66]:disabled{opacity:.55;cursor:not-allowed}.scm-btn--secondary[data-v-a79bdb66]{--scm-btn-bg: var(--scm-color-surface);--scm-btn-color: var(--scm-color-muted)}.scm-btn--ghost[data-v-a79bdb66]{--scm-btn-bg: transparent;--scm-btn-color: var(--scm-color-primary)}.scm-btn--danger[data-v-a79bdb66]{--scm-btn-bg: var(--scm-color-danger);--scm-btn-color: white}.scm-btn--sm[data-v-a79bdb66]{--scm-btn-padding-y: 2px;--scm-btn-padding-x: 8px;--scm-btn-font-size: .75em}.scm-btn--lg[data-v-a79bdb66]{--scm-btn-padding-y: 8px;--scm-btn-padding-x: 16px;--scm-btn-font-size: 1em}.scm-btn--block[data-v-a79bdb66]{width:100%;display:inline-flex}.scm-btn__spinner[data-v-a79bdb66]{width:14px;height:14px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:scm-spin-a79bdb66 .6s linear infinite}.scm-btn--icon[data-v-a79bdb66]{--scm-btn-padding-x: 8px;width:var(--scm-btn-height)}.scm-btn--icon[data-v-a79bdb66] .icon-partial svg{stroke:currentColor}.scm-btn--icon[data-v-a79bdb66] .icon-partial svg.useFill{fill:currentColor}@keyframes scm-spin-a79bdb66{to{transform:rotate(360deg)}}";
17257
- const ButtonPartial = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["styles", [_style_0$6]], ["__scopeId", "data-v-a79bdb66"]]);
17258
- const _hoisted_1$5 = { class: "dialog-partial" };
17668
+ const _style_0$7 = ".scm-btn[data-v-a79bdb66]{--scm-btn-bg: var(--scm-color-primary);--scm-btn-color: white;--scm-btn-radius: 0;--scm-btn-padding-y: 4px;--scm-btn-padding-x: 10px;--scm-btn-font-size: .875em;--scm-btn-height: 40px;--scm-btn-line-height: 42px;--scm-btn-transition: .12s ease-in-out;appearance:none;border:none;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;background-color:var(--scm-btn-bg);color:var(--scm-btn-color);font:inherit;padding:var(--scm-btn-padding-y) var(--scm-btn-padding-x);font-size:var(--scm-btn-font-size);border-radius:var(--scm-btn-radius);height:var(--scm-btn-height);line-height:var(--scm-btn-line-height);transition:background-color var(--scm-btn-transition),opacity var(--scm-btn-transition),box-shadow var(--scm-btn-transition);text-decoration:none;white-space:nowrap}.scm-btn[data-v-a79bdb66]:hover:not(:disabled){background-color:color-mix(in srgb,var(--scm-btn-bg) 85%,#000)}.scm-btn[data-v-a79bdb66]:active:not(:disabled){background-color:color-mix(in srgb,var(--scm-btn-bg) 70%,#000)}.scm-btn[data-v-a79bdb66]:focus-visible{outline:2px solid var(--scm-color-primary);outline-offset:2px}.scm-btn[data-v-a79bdb66]:disabled{opacity:.55;cursor:not-allowed}.scm-btn--secondary[data-v-a79bdb66]{--scm-btn-bg: var(--scm-color-surface);--scm-btn-color: var(--scm-color-muted)}.scm-btn--ghost[data-v-a79bdb66]{--scm-btn-bg: transparent;--scm-btn-color: var(--scm-color-primary)}.scm-btn--danger[data-v-a79bdb66]{--scm-btn-bg: var(--scm-color-danger);--scm-btn-color: white}.scm-btn--sm[data-v-a79bdb66]{--scm-btn-padding-y: 2px;--scm-btn-padding-x: 8px;--scm-btn-font-size: .75em}.scm-btn--lg[data-v-a79bdb66]{--scm-btn-padding-y: 8px;--scm-btn-padding-x: 16px;--scm-btn-font-size: 1em}.scm-btn--block[data-v-a79bdb66]{width:100%;display:inline-flex}.scm-btn__spinner[data-v-a79bdb66]{width:14px;height:14px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:scm-spin-a79bdb66 .6s linear infinite}.scm-btn--icon[data-v-a79bdb66]{--scm-btn-padding-x: 8px;width:var(--scm-btn-height)}.scm-btn--icon[data-v-a79bdb66] .icon-partial svg{stroke:currentColor}.scm-btn--icon[data-v-a79bdb66] .icon-partial svg.useFill{fill:currentColor}@keyframes scm-spin-a79bdb66{to{transform:rotate(360deg)}}";
17669
+ const ButtonPartial = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["styles", [_style_0$7]], ["__scopeId", "data-v-a79bdb66"]]);
17670
+ const _hoisted_1$6 = { class: "dialog-partial" };
17259
17671
  const _sfc_main$6 = /* @__PURE__ */ defineComponent({
17260
17672
  __name: "dialog.partial",
17261
17673
  emits: ["accept", "decline"],
@@ -17269,7 +17681,7 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
17269
17681
  }
17270
17682
  return (_ctx, _cache) => {
17271
17683
  const _component_button_partial = resolveComponent("button-partial");
17272
- return openBlock(), createElementBlock("section", _hoisted_1$5, [
17684
+ return openBlock(), createElementBlock("section", _hoisted_1$6, [
17273
17685
  renderSlot(_ctx.$slots, "default", {}, void 0, true),
17274
17686
  createVNode(_component_button_partial, { onClick: onAccept }, {
17275
17687
  default: withCtx(() => [..._cache[0] || (_cache[0] = [
@@ -17287,10 +17699,10 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
17287
17699
  };
17288
17700
  }
17289
17701
  });
17290
- const _style_0$5 = ".dialog-partial[data-v-ae4dfdf7]{padding:20px;background-color:var(--scm-color-surface);box-shadow:0 0 5px var(--scm-color-muted)}";
17291
- const DialogPartial = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["styles", [_style_0$5]], ["__scopeId", "data-v-ae4dfdf7"]]);
17292
- const _hoisted_1$4 = { class: "chat-input" };
17293
- const _hoisted_2$3 = { class: "background-container" };
17702
+ const _style_0$6 = ".dialog-partial[data-v-ae4dfdf7]{padding:20px;background-color:var(--scm-color-surface);box-shadow:0 0 5px var(--scm-color-muted)}";
17703
+ const DialogPartial = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["styles", [_style_0$6]], ["__scopeId", "data-v-ae4dfdf7"]]);
17704
+ const _hoisted_1$5 = { class: "chat-input" };
17705
+ const _hoisted_2$4 = { class: "background-container" };
17294
17706
  const _hoisted_3$1 = ["value", "placeholder"];
17295
17707
  const _sfc_main$5 = /* @__PURE__ */ defineComponent({
17296
17708
  __name: "chat-input.partial",
@@ -17325,8 +17737,8 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
17325
17737
  watch(() => props.modelValue, () => nextTick(resize2));
17326
17738
  return (_ctx, _cache) => {
17327
17739
  const _component_button_partial = resolveComponent("button-partial");
17328
- return openBlock(), createElementBlock("div", _hoisted_1$4, [
17329
- createBaseVNode("section", _hoisted_2$3, [
17740
+ return openBlock(), createElementBlock("div", _hoisted_1$5, [
17741
+ createBaseVNode("section", _hoisted_2$4, [
17330
17742
  createBaseVNode("textarea", {
17331
17743
  ref_key: "textareaRef",
17332
17744
  ref: textareaRef,
@@ -17348,26 +17760,10 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
17348
17760
  };
17349
17761
  }
17350
17762
  });
17351
- const _style_0$4 = ".chat-input[data-v-b6ae32fa]{padding:20px}.chat-input .background-container[data-v-b6ae32fa]{border-radius:5px;display:grid;grid-template-columns:1fr max-content;grid-template-rows:max-content;background-color:var(--scm-color-surface)}.chat-input .background-container textarea[data-v-b6ae32fa]{border:0;padding:10px;background-color:var(--scm-color-surface);outline:none;font-size:1em}.chat-input .background-container button[data-v-b6ae32fa]{align-self:end}";
17352
- const ChatInputPartial = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["styles", [_style_0$4]], ["__scopeId", "data-v-b6ae32fa"]]);
17763
+ const _style_0$5 = ".chat-input[data-v-b6ae32fa]{padding:20px}.chat-input .background-container[data-v-b6ae32fa]{border-radius:5px;display:grid;grid-template-columns:1fr max-content;grid-template-rows:max-content;background-color:var(--scm-color-surface)}.chat-input .background-container textarea[data-v-b6ae32fa]{border:0;padding:10px;background-color:var(--scm-color-surface);outline:none;font-size:1em}.chat-input .background-container button[data-v-b6ae32fa]{align-self:end}";
17764
+ const ChatInputPartial = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["styles", [_style_0$5]], ["__scopeId", "data-v-b6ae32fa"]]);
17765
+ const _hoisted_1$4 = { class: "user-avatar" };
17353
17766
  const _sfc_main$4 = /* @__PURE__ */ defineComponent({
17354
- __name: "user-name.partial",
17355
- props: {
17356
- userId: { type: String }
17357
- },
17358
- setup(__props) {
17359
- const props = __props;
17360
- const usersCache = useUsersCacheStore();
17361
- onMounted(() => {
17362
- usersCache.ensure([props.userId]);
17363
- });
17364
- return (_ctx, _cache) => {
17365
- return toDisplayString$1(unref(usersCache).name(props.userId));
17366
- };
17367
- }
17368
- });
17369
- const _hoisted_1$3 = { class: "user-avatar" };
17370
- const _sfc_main$3 = /* @__PURE__ */ defineComponent({
17371
17767
  __name: "user-avatar.partial",
17372
17768
  props: {
17373
17769
  userId: { type: String }
@@ -17392,19 +17788,19 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
17392
17788
  usersCache.ensure([props.userId]);
17393
17789
  });
17394
17790
  return (_ctx, _cache) => {
17395
- return openBlock(), createElementBlock("span", _hoisted_1$3, [
17791
+ return openBlock(), createElementBlock("span", _hoisted_1$4, [
17396
17792
  createBaseVNode("span", {
17397
17793
  class: "user-avatar__circle",
17398
17794
  style: normalizeStyle({ backgroundColor: `hsl(${hue.value}, 45%, 60%)` })
17399
17795
  }, toDisplayString$1(initials.value), 5),
17400
- createVNode(_sfc_main$4, { "user-id": __props.userId }, null, 8, ["user-id"])
17796
+ createVNode(_sfc_main$g, { "user-id": __props.userId }, null, 8, ["user-id"])
17401
17797
  ]);
17402
17798
  };
17403
17799
  }
17404
17800
  });
17405
- const _style_0$3 = ".user-avatar[data-v-1fc1d2df]{display:inline-flex;align-items:center;gap:6px}.user-avatar__circle[data-v-1fc1d2df]{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;font-size:.625em;font-weight:600;color:#fff;flex-shrink:0;line-height:1}";
17406
- const UserAvatarPartial = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["styles", [_style_0$3]], ["__scopeId", "data-v-1fc1d2df"]]);
17407
- const _hoisted_1$2 = {
17801
+ const _style_0$4 = ".user-avatar[data-v-1fc1d2df]{display:inline-flex;align-items:center;gap:6px}.user-avatar__circle[data-v-1fc1d2df]{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;font-size:.625em;font-weight:600;color:#fff;flex-shrink:0;line-height:1}";
17802
+ const UserAvatarPartial = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["styles", [_style_0$4]], ["__scopeId", "data-v-1fc1d2df"]]);
17803
+ const _hoisted_1$3 = {
17408
17804
  key: 0,
17409
17805
  xmlns: "http://www.w3.org/2000/svg",
17410
17806
  class: "h-6 w-6",
@@ -17413,7 +17809,7 @@ const _hoisted_1$2 = {
17413
17809
  stroke: "currentColor",
17414
17810
  "stroke-width": "2"
17415
17811
  };
17416
- const _hoisted_2$2 = {
17812
+ const _hoisted_2$3 = {
17417
17813
  key: 1,
17418
17814
  xmlns: "http://www.w3.org/2000/svg",
17419
17815
  class: "h-6 w-6",
@@ -17875,7 +18271,7 @@ const _hoisted_53 = {
17875
18271
  stroke: "currentColor",
17876
18272
  class: "size-6"
17877
18273
  };
17878
- const _sfc_main$2 = /* @__PURE__ */ defineComponent({
18274
+ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
17879
18275
  __name: "icons.partial",
17880
18276
  props: {
17881
18277
  type: { type: String },
@@ -17894,14 +18290,14 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
17894
18290
  return openBlock(), createElementBlock("span", {
17895
18291
  class: normalizeClass(["icon-partial", [__props.type, __props.colorScheme || "colorScheme1", __props.size || "medium", rotateClass.value, { animate: __props.animate }]])
17896
18292
  }, [
17897
- __props.type === "arrow-right" ? (openBlock(), createElementBlock("svg", _hoisted_1$2, [..._cache[0] || (_cache[0] = [
18293
+ __props.type === "arrow-right" ? (openBlock(), createElementBlock("svg", _hoisted_1$3, [..._cache[0] || (_cache[0] = [
17898
18294
  createBaseVNode("path", {
17899
18295
  "stroke-linecap": "round",
17900
18296
  "stroke-linejoin": "round",
17901
18297
  d: "M9 5l7 7-7 7"
17902
18298
  }, null, -1)
17903
18299
  ])])) : createCommentVNode("", true),
17904
- __props.type === "arrow-left" ? (openBlock(), createElementBlock("svg", _hoisted_2$2, [..._cache[1] || (_cache[1] = [
18300
+ __props.type === "arrow-left" ? (openBlock(), createElementBlock("svg", _hoisted_2$3, [..._cache[1] || (_cache[1] = [
17905
18301
  createBaseVNode("path", {
17906
18302
  "stroke-linecap": "round",
17907
18303
  "stroke-linejoin": "round",
@@ -18293,24 +18689,58 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
18293
18689
  };
18294
18690
  }
18295
18691
  });
18296
- const _style_0$2 = ".icon-partial[data-v-4a547a78]{display:grid;align-self:center;justify-content:center}.icon-partial svg[data-v-4a547a78]{align-self:center;justify-self:center;stroke-width:1.5}.icon-partial.rotate90 svg[data-v-4a547a78]{transform:rotate(90deg)}.icon-partial.rotate180 svg[data-v-4a547a78]{transform:rotate(180deg)}.icon-partial.animate svg[data-v-4a547a78]{transition:transform .2s ease,fill .2s ease,stroke .2s ease}@keyframes upAndDown-4a547a78{0%{transform:translateY(0)}to{transform:translateY(-20px)}}.icon-partial.animate .upload .arrow-up[data-v-4a547a78]{animation:upAndDown-4a547a78 1s infinite ease-in-out}.icon-partial.big[data-v-4a547a78]{width:30px;height:30px}.icon-partial.big svg[data-v-4a547a78]{width:30px;height:30px}.icon-partial.larger[data-v-4a547a78]{width:26px;height:26px}.icon-partial.larger svg[data-v-4a547a78]{width:26px;height:26px}.icon-partial.large[data-v-4a547a78]{width:24px;height:24px}.icon-partial.large svg[data-v-4a547a78]{width:24px;height:24px}.icon-partial.xl[data-v-4a547a78]{width:60px;height:60px}.icon-partial.xl svg[data-v-4a547a78]{width:60px;height:60px}.icon-partial.medium[data-v-4a547a78]{width:20px;height:20px}.icon-partial.medium svg[data-v-4a547a78]{width:20px;height:20px}.icon-partial.small[data-v-4a547a78]{width:16px;height:16px}.icon-partial.small svg[data-v-4a547a78]{width:16px;height:16px}.icon-partial.smaller[data-v-4a547a78]{width:10px;height:10px}.icon-partial.smaller svg[data-v-4a547a78]{width:10px;height:10px}.icon-partial.colorScheme1 svg[data-v-4a547a78]{stroke:var(--scm-icon-color-1, #333)}.icon-partial.colorScheme1 svg.useFill[data-v-4a547a78]{fill:var(--scm-icon-color-1, #333);stroke-width:0}.icon-partial.colorScheme2 svg[data-v-4a547a78]{stroke:var(--scm-icon-color-2, #666)}.icon-partial.colorScheme2 svg.useFill[data-v-4a547a78]{fill:var(--scm-icon-color-2, #666);stroke-width:0}.icon-partial.colorScheme4 svg[data-v-4a547a78]{stroke:var(--scm-icon-color-1, #333)}.icon-partial.colorScheme4 svg.useFill[data-v-4a547a78]{fill:var(--scm-icon-color-1, #333);stroke-width:0}.icon-partial.white svg[data-v-4a547a78]{stroke:#fff}.icon-partial.white svg.useFill[data-v-4a547a78]{fill:#fff;stroke-width:0}.icon-partial.grey svg[data-v-4a547a78]{stroke:var(--scm-color-muted, #999)}.icon-partial.grey svg.useFill[data-v-4a547a78]{fill:var(--scm-color-muted, #999);stroke-width:0}.icon-partial.aiColor svg[data-v-4a547a78]{stroke:var(--scm-icon-ai-color, #8b5cf6)}.icon-partial.aiColor svg.useFill[data-v-4a547a78]{fill:var(--scm-icon-ai-color, #8b5cf6);stroke-width:0}.icon-partial.red svg[data-v-4a547a78]{stroke:var(--scm-color-danger, #e53935)}.icon-partial.red svg.useFill[data-v-4a547a78]{fill:var(--scm-color-danger, #e53935);stroke-width:0}.icon-partial.green svg[data-v-4a547a78]{stroke:#43a047}.icon-partial.green svg.useFill[data-v-4a547a78]{fill:#43a047;stroke-width:0}";
18297
- const IconsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["styles", [_style_0$2]], ["__scopeId", "data-v-4a547a78"]]);
18298
- const _hoisted_1$1 = { class: "connection-status" };
18299
- const _hoisted_2$1 = { class: "status" };
18300
- const _sfc_main$1 = /* @__PURE__ */ defineComponent({
18692
+ const _style_0$3 = ".icon-partial[data-v-f8bafd55]{display:grid;align-self:center;justify-content:center}.icon-partial svg[data-v-f8bafd55]{align-self:center;justify-self:center;stroke-width:1.5}.icon-partial.rotate90 svg[data-v-f8bafd55]{transform:rotate(90deg)}.icon-partial.rotate180 svg[data-v-f8bafd55]{transform:rotate(180deg)}.icon-partial.animate svg[data-v-f8bafd55]{transition:transform .2s ease,fill .2s ease,stroke .2s ease}@keyframes upAndDown-f8bafd55{0%{transform:translateY(0)}to{transform:translateY(-20px)}}.icon-partial.animate .upload .arrow-up[data-v-f8bafd55]{animation:upAndDown-f8bafd55 1s infinite ease-in-out}.icon-partial.big[data-v-f8bafd55]{width:30px;height:30px}.icon-partial.big svg[data-v-f8bafd55]{width:30px;height:30px}.icon-partial.larger[data-v-f8bafd55]{width:26px;height:26px}.icon-partial.larger svg[data-v-f8bafd55]{width:26px;height:26px}.icon-partial.large[data-v-f8bafd55]{width:24px;height:24px}.icon-partial.large svg[data-v-f8bafd55]{width:24px;height:24px}.icon-partial.xl[data-v-f8bafd55]{width:60px;height:60px}.icon-partial.xl svg[data-v-f8bafd55]{width:60px;height:60px}.icon-partial.medium[data-v-f8bafd55]{width:20px;height:20px}.icon-partial.medium svg[data-v-f8bafd55]{width:20px;height:20px}.icon-partial.small[data-v-f8bafd55]{width:16px;height:16px}.icon-partial.small svg[data-v-f8bafd55]{width:16px;height:16px}.icon-partial.smaller[data-v-f8bafd55]{width:10px;height:10px}.icon-partial.smaller svg[data-v-f8bafd55]{width:10px;height:10px}.icon-partial.colorScheme1 svg[data-v-f8bafd55]{stroke:var(--scm-icon-color-1, #333)}.icon-partial.colorScheme1 svg.useFill[data-v-f8bafd55]{fill:var(--scm-icon-color-1, #333);stroke-width:0}.icon-partial.colorScheme2 svg[data-v-f8bafd55]{stroke:var(--scm-icon-color-2, #666)}.icon-partial.colorScheme2 svg.useFill[data-v-f8bafd55]{fill:var(--scm-icon-color-2, #666);stroke-width:0}.icon-partial.colorScheme4 svg[data-v-f8bafd55]{stroke:var(--scm-icon-color-1, #333)}.icon-partial.colorScheme4 svg.useFill[data-v-f8bafd55]{fill:var(--scm-icon-color-1, #333);stroke-width:0}.icon-partial.white svg[data-v-f8bafd55]{stroke:#fff}.icon-partial.white svg.useFill[data-v-f8bafd55]{fill:#fff;stroke-width:0}.icon-partial.grey svg[data-v-f8bafd55]{stroke:var(--scm-color-muted, #999)}.icon-partial.grey svg.useFill[data-v-f8bafd55]{fill:var(--scm-color-muted, #999);stroke-width:0}.icon-partial.aiColor svg[data-v-f8bafd55]{stroke:var(--scm-icon-ai-color, #8b5cf6)}.icon-partial.aiColor svg.useFill[data-v-f8bafd55]{fill:var(--scm-icon-ai-color, #8b5cf6);stroke-width:0}.icon-partial.red svg[data-v-f8bafd55]{stroke:var(--scm-color-danger, #e53935)}.icon-partial.red svg.useFill[data-v-f8bafd55]{fill:var(--scm-color-danger, #e53935);stroke-width:0}.icon-partial.green svg[data-v-f8bafd55]{stroke:#43a047}.icon-partial.green svg.useFill[data-v-f8bafd55]{fill:#43a047;stroke-width:0}";
18693
+ const IconsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["styles", [_style_0$3]], ["__scopeId", "data-v-f8bafd55"]]);
18694
+ const _hoisted_1$2 = { class: "connection-status" };
18695
+ const _hoisted_2$2 = { class: "status" };
18696
+ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
18301
18697
  __name: "connection-status.partial",
18302
18698
  setup(__props) {
18303
18699
  const mainStore = useMainStore();
18304
18700
  const connection = computed(() => mainStore.state);
18305
18701
  return (_ctx, _cache) => {
18306
- return openBlock(), createElementBlock("section", _hoisted_1$1, [
18307
- createBaseVNode("div", _hoisted_2$1, toDisplayString$1(_ctx.$t(`status.${connection.value}`)), 1)
18702
+ return openBlock(), createElementBlock("section", _hoisted_1$2, [
18703
+ createBaseVNode("div", _hoisted_2$2, toDisplayString$1(_ctx.$t(`status.${connection.value}`)), 1)
18308
18704
  ]);
18309
18705
  };
18310
18706
  }
18311
18707
  });
18312
- const _style_0$1 = ".connection-status[data-v-a483f86f]{position:absolute;top:10px;right:10px;pointer-events:none}";
18313
- const ConnectionStatusPartial = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["styles", [_style_0$1]], ["__scopeId", "data-v-a483f86f"]]);
18708
+ const _style_0$2 = ".connection-status[data-v-81fdb902]{background-color:var(--scm-color-danger);color:#fff;padding:0 10px;font-size:.7rem;align-content:center}";
18709
+ const ConnectionStatusPartial = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["styles", [_style_0$2]], ["__scopeId", "data-v-81fdb902"]]);
18710
+ const _hoisted_1$1 = {
18711
+ key: 0,
18712
+ class: "conversations-button-partial"
18713
+ };
18714
+ const _hoisted_2$1 = {
18715
+ key: 0,
18716
+ class: "unread"
18717
+ };
18718
+ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
18719
+ __name: "conversations-button.partial",
18720
+ setup(__props) {
18721
+ const mainStore = useMainStore();
18722
+ const conversationsStore = useConversationsStore();
18723
+ const showButton = computed(() => mainStore.hasChat || conversationsStore.conversations.length > 1);
18724
+ const unreadCount = computed(() => conversationsStore.totalUnreadCount);
18725
+ function showConversations() {
18726
+ mainStore.showConversations();
18727
+ }
18728
+ return (_ctx, _cache) => {
18729
+ const _component_button_partial = resolveComponent("button-partial");
18730
+ return showButton.value ? (openBlock(), createElementBlock("section", _hoisted_1$1, [
18731
+ createVNode(_component_button_partial, {
18732
+ icon: "chat",
18733
+ variant: "secondary",
18734
+ "aria-label": "Conversations",
18735
+ onClick: showConversations
18736
+ }),
18737
+ unreadCount.value ? (openBlock(), createElementBlock("span", _hoisted_2$1, toDisplayString$1(unreadCount.value), 1)) : createCommentVNode("", true)
18738
+ ])) : createCommentVNode("", true);
18739
+ };
18740
+ }
18741
+ });
18742
+ const _style_0$1 = ".conversations-button-partial[data-v-864b2cbd]{position:relative;display:inline-flex}.conversations-button-partial .unread[data-v-864b2cbd]{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;border-radius:8px;background-color:var(--scm-color-own);color:#fff;font-size:.625em;line-height:16px;text-align:center;pointer-events:none}";
18743
+ const ConversationsButtonPartial = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["styles", [_style_0$1]], ["__scopeId", "data-v-864b2cbd"]]);
18314
18744
  function resolveFeatures(config) {
18315
18745
  if (config.features?.length) return [...new Set(config.features)];
18316
18746
  switch (config.mode) {
@@ -18343,7 +18773,7 @@ function applyConfig(config, pinia) {
18343
18773
  const MODULES = [
18344
18774
  { name: "chat", component: ChatModule },
18345
18775
  { name: "conversations", component: ConversationsModule },
18346
- { name: "chats", component: _sfc_main$i },
18776
+ { name: "chats", component: _sfc_main$j },
18347
18777
  { name: "contacts", component: ContactsModule }
18348
18778
  ];
18349
18779
  const PARTIALS = [
@@ -18359,9 +18789,10 @@ const PARTIALS = [
18359
18789
  { name: "dialog", component: DialogPartial },
18360
18790
  { name: "chat-input", component: ChatInputPartial },
18361
18791
  { name: "user-avatar", component: UserAvatarPartial },
18362
- { name: "user-name", component: _sfc_main$4 },
18792
+ { name: "user-name", component: _sfc_main$g },
18363
18793
  { name: "icons", component: IconsPartial },
18364
- { name: "connection-status", component: ConnectionStatusPartial }
18794
+ { name: "connection-status", component: ConnectionStatusPartial },
18795
+ { name: "conversations-button", component: ConversationsButtonPartial }
18365
18796
  ];
18366
18797
  function registerComponents(app) {
18367
18798
  loadViaDeclarationSync(MODULES, void 0, "module", app);
@@ -18496,10 +18927,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18496
18927
  const mainStore = useMainStore();
18497
18928
  const userStore = useUserStore();
18498
18929
  const conversationsStore = useConversationsStore();
18499
- const {
18500
- connect,
18501
- authenticate
18502
- } = useWebSocket();
18930
+ useWebSocket();
18503
18931
  const realWidth = useResize().realWidth;
18504
18932
  const viewMode = computed(() => mainStore.viewMode);
18505
18933
  const leftTarget = computed(() => mainStore.leftTarget);
@@ -18542,8 +18970,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18542
18970
  if (props.config) applyConfig(props.config);
18543
18971
  const token = await resolveToken() ?? props.config?.token ?? mainStore.authToken;
18544
18972
  if (token) mainStore.authToken = token;
18545
- const urlUser = new URLSearchParams(window.location.search).get("user") || void 0;
18546
- const userId = decodeSub(token) ?? props.config?.userId ?? userStore.user ?? urlUser;
18973
+ const userId = decodeSub(token) ?? props.config?.userId ?? userStore.user;
18547
18974
  if (!userId) {
18548
18975
  if (props.config) console.warn("[studio-chat] config has no usable identity (getToken/token sub or userId)");
18549
18976
  return;
@@ -18551,13 +18978,19 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18551
18978
  started = true;
18552
18979
  userStore.user = userId;
18553
18980
  if (props.config?.user) useUsersCacheStore().setOne({ ...props.config.user, id: userId });
18554
- connect({
18981
+ chatSession.start({
18555
18982
  url: mainStore.wsUrl,
18556
- maxRetries: 50,
18557
- maxRetryDelay: 3e4,
18558
- // resolve a fresh token on every (re)connect so rotation is transparent
18559
- onOpen: async () => authenticate(await resolveToken() ?? mainStore.authToken ?? userId),
18560
- onMessage: (data) => handleMessages(JSON.parse(data))
18983
+ fallbackToken: () => mainStore.authToken ?? userId,
18984
+ ownUserId: () => userStore.user,
18985
+ onMessage: handleMessages,
18986
+ onStateChange: (state) => {
18987
+ mainStore.state = state === "ready" ? "connected" : "disconnected";
18988
+ },
18989
+ // the session came back (re-auth or reconnect) — pull everything missed meanwhile
18990
+ onSessionRestored: () => {
18991
+ conversationsStore.handleReconnect().catch(() => {
18992
+ });
18993
+ }
18561
18994
  });
18562
18995
  if (mainStore.isSupportOnly) bootstrapSupport();
18563
18996
  } finally {
@@ -18567,25 +19000,27 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18567
19000
  watch(() => props.config, start, { immediate: true });
18568
19001
  return (_ctx, _cache) => {
18569
19002
  const _component_actions_partial = resolveComponent("actions-partial");
19003
+ const _component_connection_status_partial = resolveComponent("connection-status-partial");
18570
19004
  const _component_contacts_module = resolveComponent("contacts-module");
18571
19005
  return openBlock(), createElementBlock("div", {
18572
- class: normalizeClass(["studio-chat-root", [viewMode.value, leftTarget.value]])
19006
+ class: normalizeClass(["studio-chat-root", [viewMode.value, leftTarget.value, unref(mainStore).state]])
18573
19007
  }, [
18574
19008
  createBaseVNode("section", _hoisted_1, [
18575
- createVNode(_component_actions_partial)
19009
+ createVNode(_component_actions_partial),
19010
+ unref(mainStore).state === "disconnected" ? (openBlock(), createBlock(_component_connection_status_partial, { key: 0 })) : createCommentVNode("", true)
18576
19011
  ]),
18577
19012
  unref(mainStore).leftTarget ? (openBlock(), createElementBlock("section", _hoisted_2, [
18578
19013
  unref(mainStore).leftTarget === "conversations" ? (openBlock(), createBlock(ConversationsModule, { key: 0 })) : createCommentVNode("", true),
18579
19014
  unref(mainStore).leftTarget === "contacts-new" || unref(mainStore).leftTarget === "contacts-edit" ? (openBlock(), createBlock(_component_contacts_module, { key: 1 })) : createCommentVNode("", true)
18580
19015
  ])) : createCommentVNode("", true),
18581
- unref(mainStore).mainTarget === "chat" ? (openBlock(), createBlock(_sfc_main$i, { key: 1 })) : createCommentVNode("", true),
19016
+ unref(mainStore).mainTarget === "chat" ? (openBlock(), createBlock(_sfc_main$j, { key: 1 })) : createCommentVNode("", true),
18582
19017
  _cache[0] || (_cache[0] = createBaseVNode("div", { class: "util" }, null, -1))
18583
19018
  ], 2);
18584
19019
  };
18585
19020
  }
18586
19021
  });
18587
- const _style_0 = "[data-v-f291279e]:host{display:block;height:100%}[data-v-f291279e]:host,.studio-chat-root[data-v-f291279e]{--scm-font-size: 16px;--scm-color-primary: #008cff;--scm-color-own: #ff009d;--scm-color-surface: #f6f6f6;--scm-color-muted: #757474;--scm-color-danger: #e53935;font-size:var(--scm-font-size)}#app[data-v-f291279e]{height:100vh}.studio-chat-root[data-v-f291279e]{display:grid;grid-template-rows:40px 1fr;grid-template-columns:minmax(200px,400px) 1fr;height:100%}.studio-chat-root .header[data-v-f291279e]{display:grid;background-color:var(--scm-color-surface);grid-template-columns:max-content 1fr;padding:0 20px;align-content:center}.studio-chat-root .conversations-module[data-v-f291279e]{grid-column:1}.studio-chat-root .chats-module[data-v-f291279e]{grid-column:2}.studio-chat-root.support-no-sidebar[data-v-f291279e]{grid-template-columns:1fr}.studio-chat-root.support-no-sidebar .chats-module[data-v-f291279e]{grid-column:1}.studio-chat-root.compact[data-v-f291279e]{grid-template-columns:1fr}.studio-chat-root.compact .chats-module[data-v-f291279e]{grid-column:1}.studio-chat-root.compact .main-left-target[data-v-f291279e]{width:100%;position:absolute;top:40px;left:0;transform:translate(-100%);transition:transform .3s ease-in-out;background-color:#fff;z-index:100;height:calc(100% - 40px)}.studio-chat-root.compact.conversations .main-left-target[data-v-f291279e],.studio-chat-root.compact.contacts-new .main-left-target[data-v-f291279e],.studio-chat-root.compact.contacts-edit .main-left-target[data-v-f291279e]{transform:translate(0)}[data-v-f291279e]{-moz-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;background-repeat:no-repeat;list-style-type:none;text-decoration:none}";
18588
- const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["styles", [_style_0]], ["__scopeId", "data-v-f291279e"]]);
19022
+ const _style_0 = "[data-v-4f474ef7]:host{display:block;height:100%}[data-v-4f474ef7]:host,.studio-chat-root[data-v-4f474ef7]{--scm-font-size: 16px;--scm-color-primary: #008cff;--scm-color-own: #ff009d;--scm-color-surface: #f6f6f6;--scm-color-muted: #757474;--scm-color-danger: #e53935;font-family:Open Sans,sans-serif;font-size:var(--scm-font-size)}#app[data-v-4f474ef7]{height:100vh}.studio-chat-root[data-v-4f474ef7]{display:grid;grid-template-rows:40px 1fr;grid-template-columns:minmax(200px,400px) 1fr;height:100%}.studio-chat-root .header[data-v-4f474ef7]{display:grid;background-color:var(--scm-color-surface);grid-template-columns:max-content 1fr;align-content:center}.studio-chat-root.disconnected[data-v-4f474ef7]{grid-template-rows:60px 1fr}.studio-chat-root.disconnected .header[data-v-4f474ef7]{grid-template-rows:40px 20px}.studio-chat-root.disconnected .header .connection-status[data-v-4f474ef7]{grid-column:1/3}.studio-chat-root .conversations-module[data-v-4f474ef7]{grid-column:1}.studio-chat-root .chats-module[data-v-4f474ef7]{grid-column:2}.studio-chat-root.support-no-sidebar[data-v-4f474ef7]{grid-template-columns:1fr}.studio-chat-root.support-no-sidebar .chats-module[data-v-4f474ef7]{grid-column:1}.studio-chat-root.compact[data-v-4f474ef7]{grid-template-columns:1fr}.studio-chat-root.compact .chats-module[data-v-4f474ef7]{grid-column:1}.studio-chat-root.compact .main-left-target[data-v-4f474ef7]{width:100%;position:absolute;top:40px;left:0;transform:translate(-100%);transition:transform .3s ease-in-out;background-color:#fff;z-index:100;height:calc(100% - 40px)}.studio-chat-root.compact.conversations .main-left-target[data-v-4f474ef7],.studio-chat-root.compact.contacts-new .main-left-target[data-v-4f474ef7],.studio-chat-root.compact.contacts-edit .main-left-target[data-v-4f474ef7]{transform:translate(0)}.studio-chat-root.compact.disconnected .main-left-target[data-v-4f474ef7]{top:60px;height:calc(100% - 60px)}[data-v-4f474ef7],[data-v-4f474ef7]:before,[data-v-4f474ef7]:after{box-sizing:border-box;margin:0;padding:0;background-repeat:no-repeat}[data-v-4f474ef7]{min-width:0}img[data-v-4f474ef7],picture[data-v-4f474ef7],video[data-v-4f474ef7],canvas[data-v-4f474ef7],svg[data-v-4f474ef7]{display:block;max-width:100%}input[data-v-4f474ef7],button[data-v-4f474ef7],textarea[data-v-4f474ef7],select[data-v-4f474ef7]{font:inherit;color:inherit}a[data-v-4f474ef7]{text-decoration:none;color:inherit}ul[data-v-4f474ef7],ol[data-v-4f474ef7]{list-style:none}p[data-v-4f474ef7],h1[data-v-4f474ef7],h2[data-v-4f474ef7],h3[data-v-4f474ef7],h4[data-v-4f474ef7],h5[data-v-4f474ef7],h6[data-v-4f474ef7]{overflow-wrap:break-word}h1[data-v-4f474ef7],h2[data-v-4f474ef7],h3[data-v-4f474ef7],h4[data-v-4f474ef7],h5[data-v-4f474ef7],h6[data-v-4f474ef7]{font-size:inherit;font-weight:inherit}";
19023
+ const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["styles", [_style_0]], ["__scopeId", "data-v-4f474ef7"]]);
18589
19024
  dayjs.extend(relativeTime);
18590
19025
  const LilaquadratStudioChat = /* @__PURE__ */ defineCustomElement(App, {
18591
19026
  shadowRoot: true,