@lilaquadrat/chat 0.1.3 → 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
  );
@@ -15354,21 +15376,22 @@ const useConversationsStore = /* @__PURE__ */ defineStore("conversations", () =>
15354
15376
  };
15355
15377
  });
15356
15378
  const _hoisted_1$e = { class: "conversations-module" };
15357
- const _hoisted_2$a = ["onClick"];
15379
+ const _hoisted_2$b = ["onClick"];
15358
15380
  const _hoisted_3$6 = { class: "time" };
15359
- const _hoisted_4$3 = {
15360
- key: 3,
15381
+ const _hoisted_4$3 = { class: "message-unread-container" };
15382
+ const _hoisted_5$3 = {
15383
+ key: 0,
15361
15384
  class: "lastMessage"
15362
15385
  };
15363
- const _hoisted_5$3 = {
15364
- key: 4,
15386
+ const _hoisted_6$2 = {
15387
+ key: 1,
15365
15388
  class: "unread"
15366
15389
  };
15367
- const _hoisted_6$2 = {
15368
- key: 5,
15390
+ const _hoisted_7$2 = {
15391
+ key: 3,
15369
15392
  class: "draft"
15370
15393
  };
15371
- const _sfc_main$j = /* @__PURE__ */ defineComponent({
15394
+ const _sfc_main$k = /* @__PURE__ */ defineComponent({
15372
15395
  __name: "conversations.module",
15373
15396
  setup(__props) {
15374
15397
  const conversationsStore = useConversationsStore();
@@ -15376,14 +15399,13 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
15376
15399
  const userStore = useUserStore();
15377
15400
  watch(() => userStore.user, (user) => {
15378
15401
  if (!user) return;
15402
+ console.log("set user");
15379
15403
  conversationsStore.cleanConversations();
15380
15404
  conversationsStore.getAllConversations();
15381
15405
  }, { immediate: true });
15382
15406
  const conversations = computed(() => {
15383
15407
  const safeConversations = hardCopy(conversationsStore.conversations);
15384
- return safeConversations.sort((a, b) => {
15385
- return dayjs(a.lastMessage?.history.created).isBefore(dayjs(b.lastMessage?.history.created)) ? 1 : -1;
15386
- }).map((conversation) => ({
15408
+ return safeConversations.sort((a, b) => dayjs(a.lastMessage?.history.created).isBefore(dayjs(b.lastMessage?.history.created)) ? 1 : -1).map((conversation) => ({
15387
15409
  ...conversation,
15388
15410
  isActive: activeConversation.value?._id === conversation._id && conversation._id || activeConversation.value?.id === conversation.id && conversation.id
15389
15411
  }));
@@ -15422,16 +15444,18 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
15422
15444
  "remove-user": ""
15423
15445
  }, null, 8, ["participants"])),
15424
15446
  createBaseVNode("span", _hoisted_3$6, toDisplayString$1(unref(dayjs)(conversation.lastMessage?.history.created).fromNow()), 1),
15425
- conversation.lastMessage ? (openBlock(), createElementBlock("p", _hoisted_4$3, toDisplayString$1(conversation.lastMessage.message), 1)) : createCommentVNode("", true),
15426
- conversation.unread ? (openBlock(), createElementBlock("span", _hoisted_5$3, toDisplayString$1(conversation.unreadCount), 1)) : createCommentVNode("", true),
15427
- conversation.isNew ? (openBlock(), createElementBlock("span", _hoisted_6$2, "draft")) : createCommentVNode("", true)
15428
- ], 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);
15429
15453
  }), 128))
15430
15454
  ]);
15431
15455
  };
15432
15456
  }
15433
15457
  });
15434
- 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)}";
15435
15459
  const _export_sfc = (sfc, props) => {
15436
15460
  const target = sfc.__vccOpts || sfc;
15437
15461
  for (const [key, val] of props) {
@@ -15439,8 +15463,8 @@ const _export_sfc = (sfc, props) => {
15439
15463
  }
15440
15464
  return target;
15441
15465
  };
15442
- const ConversationsModule = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["styles", [_style_0$f]], ["__scopeId", "data-v-31a1e26e"]]);
15443
- 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({
15444
15468
  __name: "chats.module",
15445
15469
  setup(__props) {
15446
15470
  const conversationsStore = useConversationsStore();
@@ -15458,11 +15482,13 @@ class WebSocketManager {
15458
15482
  retryCount = 0;
15459
15483
  retryDelay = 1e3;
15460
15484
  retryTimeoutId = null;
15485
+ connectTimeoutId = null;
15461
15486
  // Number of active consumers (components/composables) holding the socket
15462
15487
  refCount = 0;
15463
15488
  onConnectionChange;
15464
15489
  heartbeatIntervalId = null;
15465
15490
  lastActivity = 0;
15491
+ recoveryListenersInstalled = false;
15466
15492
  constructor() {
15467
15493
  }
15468
15494
  static getInstance() {
@@ -15493,12 +15519,9 @@ class WebSocketManager {
15493
15519
  url,
15494
15520
  maxRetries = 50,
15495
15521
  maxRetryDelay = 3e4,
15496
- onMessage,
15497
- onOpen,
15498
- onClose,
15499
- onError,
15500
15522
  heartbeatIntervalMs = 3e4,
15501
15523
  inactivityTimeoutMs = 6e4,
15524
+ connectTimeoutMs = 1e4,
15502
15525
  buildPingMessage
15503
15526
  } = options;
15504
15527
  this.currentOptions = {
@@ -15507,8 +15530,10 @@ class WebSocketManager {
15507
15530
  maxRetryDelay,
15508
15531
  heartbeatIntervalMs,
15509
15532
  inactivityTimeoutMs,
15533
+ connectTimeoutMs,
15510
15534
  buildPingMessage
15511
15535
  };
15536
+ this.installRecoveryListeners();
15512
15537
  if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {
15513
15538
  logger.websocket("WS already connecting/open; reusing existing socket");
15514
15539
  return;
@@ -15517,33 +15542,76 @@ class WebSocketManager {
15517
15542
  clearTimeout(this.retryTimeoutId);
15518
15543
  this.retryTimeoutId = null;
15519
15544
  }
15545
+ if (this.socket) this.detachHandlers(this.socket);
15520
15546
  const ws = new WebSocket(url);
15521
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);
15522
15558
  ws.onopen = () => {
15559
+ this.clearConnectTimeout();
15523
15560
  this.retryCount = 0;
15524
15561
  this.lastActivity = Date.now();
15525
15562
  logger.websocket(`connected (readyState ${ws.readyState})`);
15526
15563
  this.onConnectionChange?.(true);
15527
- onOpen?.();
15564
+ this.currentOptions?.onOpen?.();
15528
15565
  this.startHeartbeat();
15529
15566
  };
15530
15567
  ws.onmessage = (event) => {
15531
15568
  this.lastActivity = Date.now();
15532
- onMessage?.(event.data);
15569
+ this.currentOptions?.onMessage?.(event.data);
15533
15570
  };
15534
15571
  ws.onerror = (error) => {
15535
15572
  console.error("WebSocket error:", error);
15536
- onError?.(error);
15573
+ this.currentOptions?.onError?.(error);
15537
15574
  };
15538
15575
  ws.onclose = (ev) => {
15576
+ this.clearConnectTimeout();
15539
15577
  this.stopHeartbeat();
15540
15578
  const everOpened = ev.code !== 1006 || ws.readyState === WebSocket.CLOSED;
15541
15579
  logger.websocket(`disconnected (code ${ev.code}, clean ${ev.wasClean}, everOpened ${everOpened})`);
15542
15580
  this.onConnectionChange?.(false);
15543
- onClose?.(ev);
15581
+ this.currentOptions?.onClose?.(ev);
15544
15582
  if (this.refCount > 0) this.attemptReconnect();
15545
15583
  };
15546
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
+ }
15547
15615
  startHeartbeat() {
15548
15616
  this.stopHeartbeat();
15549
15617
  const heartbeatIntervalMs = this.currentOptions?.heartbeatIntervalMs ?? 3e4;
@@ -15623,15 +15691,18 @@ class WebSocketManager {
15623
15691
  }
15624
15692
  _teardownSocket(reason) {
15625
15693
  this.stopHeartbeat();
15694
+ this.clearConnectTimeout();
15626
15695
  if (this.retryTimeoutId) {
15627
15696
  clearTimeout(this.retryTimeoutId);
15628
15697
  this.retryTimeoutId = null;
15629
15698
  }
15630
15699
  const ws = this.socket;
15631
15700
  this.socket = null;
15632
- if (ws && ws.readyState === WebSocket.OPEN) {
15701
+ if (!ws) return;
15702
+ this.detachHandlers(ws);
15703
+ if (ws.readyState === WebSocket.OPEN) {
15633
15704
  ws.close(1e3, reason);
15634
- } else if (ws && ws.readyState === WebSocket.CONNECTING) {
15705
+ } else if (ws.readyState === WebSocket.CONNECTING) {
15635
15706
  try {
15636
15707
  ws.close();
15637
15708
  } catch {
@@ -15642,7 +15713,6 @@ class WebSocketManager {
15642
15713
  if (this.socket && this.isConnected) {
15643
15714
  try {
15644
15715
  this.socket.send(JSON.stringify(message));
15645
- this.lastActivity = Date.now();
15646
15716
  return true;
15647
15717
  } catch {
15648
15718
  return false;
@@ -15651,32 +15721,323 @@ class WebSocketManager {
15651
15721
  return false;
15652
15722
  }
15653
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());
15654
16027
  function useWebSocket() {
15655
16028
  const wsManager = WebSocketManager.getInstance();
15656
- const mainStore = useMainStore();
15657
16029
  wsManager.acquire();
15658
- wsManager.onConnectionChange = (connected) => {
15659
- mainStore.state = connected ? "connected" : "disconnected";
15660
- };
15661
- const connect = (options) => wsManager.connect(options);
15662
- const disconnect = () => wsManager.disconnect();
15663
- const reconnect = () => wsManager.reconnect();
15664
- const conversationStart = (message, participants, name, id, operationMode) => wsManager.send({ type: "conversation:start", message, participants, name, id, operationMode });
15665
- const conversationLastRead = (conversation, lastReadId) => wsManager.send({ type: "conversation:lastRead", conversation, lastReadId });
15666
- const messageAdd = (message, conversation) => wsManager.send({ type: "message:add", message, conversation });
15667
- const authenticate = (token) => wsManager.send({ type: "authenticate", token });
15668
- 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 });
15669
16034
  onUnmounted(() => {
15670
16035
  wsManager.release();
15671
16036
  });
15672
16037
  return {
15673
- connect,
15674
- disconnect,
15675
- reconnect,
15676
16038
  conversationStart,
15677
16039
  conversationLastRead,
15678
16040
  messageAdd,
15679
- authenticate,
15680
16041
  conversationTyping
15681
16042
  };
15682
16043
  }
@@ -15750,14 +16111,6 @@ function handleMessages(message) {
15750
16111
  }
15751
16112
  function setGetUsers(fn) {
15752
16113
  }
15753
- let getTokenImpl = null;
15754
- function setGetToken(fn) {
15755
- getTokenImpl = fn;
15756
- }
15757
- async function resolveToken() {
15758
- if (!getTokenImpl) return void 0;
15759
- return await getTokenImpl();
15760
- }
15761
16114
  function getName(name, type, namespace) {
15762
16115
  return `${name}-${type}`;
15763
16116
  }
@@ -16077,8 +16430,37 @@ function groupMessages(messages, user) {
16077
16430
  });
16078
16431
  return Object.values(dateGroups);
16079
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
+ }
16080
16462
  const _hoisted_1$d = { class: "chat-messages" };
16081
- const _hoisted_2$9 = { class: "date-separator" };
16463
+ const _hoisted_2$a = { class: "date-separator" };
16082
16464
  const _hoisted_3$5 = { class: "message-content" };
16083
16465
  const _hoisted_4$2 = ["id"];
16084
16466
  const _hoisted_5$2 = {
@@ -16087,7 +16469,7 @@ const _hoisted_5$2 = {
16087
16469
  };
16088
16470
  const SCROLL_DEBOUNCE_MS = 100;
16089
16471
  const SCROLL_EDGE_PERCENT = 10;
16090
- const _sfc_main$h = /* @__PURE__ */ defineComponent({
16472
+ const _sfc_main$i = /* @__PURE__ */ defineComponent({
16091
16473
  __name: "chat.module",
16092
16474
  props: {
16093
16475
  _id: { type: String },
@@ -16114,6 +16496,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16114
16496
  const newChatExists = /* @__PURE__ */ ref(false);
16115
16497
  const existingChat = /* @__PURE__ */ ref(null);
16116
16498
  const mainStore = useMainStore();
16499
+ const { chatVisible } = useChatVisibility(chatMessagesRef);
16117
16500
  let scrollDebounceTimer = null;
16118
16501
  let typingIdleTimer = null;
16119
16502
  let lastTypingSent = 0;
@@ -16184,12 +16567,16 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16184
16567
  }
16185
16568
  }
16186
16569
  function updateLastRead() {
16570
+ if (!chatVisible.value) return;
16187
16571
  if (conversation.value?.participant?.lastReadId < conversation.value?.lastMessage?.id) {
16188
16572
  conversationLastRead(props._id, conversation.value.lastMessage.id);
16189
16573
  conversationsStore.setLastReadId(props._id, conversation.value.lastMessage.id);
16190
16574
  conversationsStore.updateUnread(props._id);
16191
16575
  }
16192
16576
  }
16577
+ watch(chatVisible, (visible) => {
16578
+ if (visible) updateLastRead();
16579
+ });
16193
16580
  function scrollToBottom() {
16194
16581
  if (chatMessagesRef.value) {
16195
16582
  chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
@@ -16322,7 +16709,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16322
16709
  key: dateGroup.date,
16323
16710
  class: "messages-group"
16324
16711
  }, [
16325
- createBaseVNode("div", _hoisted_2$9, toDisplayString$1(dateGroup.date), 1),
16712
+ createBaseVNode("div", _hoisted_2$a, toDisplayString$1(dateGroup.date), 1),
16326
16713
  (openBlock(true), createElementBlock(Fragment, null, renderList(dateGroup.userTimeGroups, (group, index) => {
16327
16714
  return openBlock(), createElementBlock("div", {
16328
16715
  key: index,
@@ -16371,8 +16758,8 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16371
16758
  };
16372
16759
  }
16373
16760
  });
16374
- 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}";
16375
- 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"]]);
16376
16763
  function sdkOptions() {
16377
16764
  const mainStore = useMainStore();
16378
16765
  return {
@@ -16401,7 +16788,7 @@ const useContactsStore = /* @__PURE__ */ defineStore("contacts", () => {
16401
16788
  };
16402
16789
  });
16403
16790
  const _hoisted_1$c = { class: "contacts-module" };
16404
- const _hoisted_2$8 = {
16791
+ const _hoisted_2$9 = {
16405
16792
  key: 0,
16406
16793
  class: "controls"
16407
16794
  };
@@ -16417,7 +16804,7 @@ const _hoisted_8$1 = {
16417
16804
  key: 0,
16418
16805
  class: "selected-indicator"
16419
16806
  };
16420
- const _sfc_main$g = /* @__PURE__ */ defineComponent({
16807
+ const _sfc_main$h = /* @__PURE__ */ defineComponent({
16421
16808
  __name: "contacts.module",
16422
16809
  setup(__props) {
16423
16810
  const mainStore = useMainStore();
@@ -16520,7 +16907,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
16520
16907
  const _component_button_partial = resolveComponent("button-partial");
16521
16908
  const _component_user_avatar_partial = resolveComponent("user-avatar-partial");
16522
16909
  return openBlock(), createElementBlock("article", _hoisted_1$c, [
16523
- isEdit.value ? (openBlock(), createElementBlock("section", _hoisted_2$8, [
16910
+ isEdit.value ? (openBlock(), createElementBlock("section", _hoisted_2$9, [
16524
16911
  createVNode(_component_button_partial, {
16525
16912
  variant: "primary",
16526
16913
  size: "sm",
@@ -16608,15 +16995,31 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
16608
16995
  };
16609
16996
  }
16610
16997
  });
16611
- 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}";
16612
- const ContactsModule = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["styles", [_style_0$d]], ["__scopeId", "data-v-045be51e"]]);
16613
- 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
+ });
16614
17016
  const _sfc_main$f = /* @__PURE__ */ defineComponent({
16615
17017
  __name: "participants.partial",
16616
17018
  props: {
16617
17019
  participants: { type: Array },
16618
17020
  removeUser: { type: Boolean },
16619
- isNew: { type: Boolean }
17021
+ isNew: { type: Boolean },
17022
+ inline: { type: Boolean }
16620
17023
  },
16621
17024
  setup(__props) {
16622
17025
  const userStore = useUserStore();
@@ -16629,29 +17032,35 @@ const _sfc_main$f = /* @__PURE__ */ defineComponent({
16629
17032
  });
16630
17033
  return (_ctx, _cache) => {
16631
17034
  const _component_user_avatar_partial = resolveComponent("user-avatar-partial");
16632
- return openBlock(), createElementBlock("section", _hoisted_1$b, [
17035
+ return openBlock(), createElementBlock("section", {
17036
+ class: normalizeClass(["participants-partial", { "participants-partial--inline": __props.inline }])
17037
+ }, [
16633
17038
  __props.isNew && !filteredParticipants.value.length ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
16634
17039
  createTextVNode(toDisplayString$1(_ctx.$t("select contacts")), 1)
16635
17040
  ], 64)) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(filteredParticipants.value, (participant, index) => {
16636
17041
  return openBlock(), createElementBlock(Fragment, {
16637
17042
  key: unref(participantId)(participant)
16638
17043
  }, [
16639
- createVNode(_component_user_avatar_partial, {
17044
+ __props.inline ? (openBlock(), createBlock(_sfc_main$g, {
17045
+ key: 0,
17046
+ "user-id": unref(participantId)(participant)
17047
+ }, null, 8, ["user-id"])) : (openBlock(), createBlock(_component_user_avatar_partial, {
17048
+ key: 1,
16640
17049
  "user-id": unref(participantId)(participant)
16641
- }, null, 8, ["user-id"]),
16642
- index < filteredParticipants.value.length - 1 ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
17050
+ }, null, 8, ["user-id"])),
17051
+ index < filteredParticipants.value.length - 1 ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
16643
17052
  createTextVNode(toDisplayString$1(", "))
16644
17053
  ], 64)) : createCommentVNode("", true)
16645
17054
  ], 64);
16646
17055
  }), 128))
16647
- ]);
17056
+ ], 2);
16648
17057
  };
16649
17058
  }
16650
17059
  });
16651
- const _style_0$c = "";
16652
- const ParticipantsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["styles", [_style_0$c]], ["__scopeId", "data-v-f06a1cdf"]]);
16653
- const _hoisted_1$a = { class: "actions" };
16654
- 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" };
16655
17064
  const _hoisted_3$3 = { class: "right" };
16656
17065
  const _sfc_main$e = /* @__PURE__ */ defineComponent({
16657
17066
  __name: "actions.partial",
@@ -16660,17 +17069,11 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
16660
17069
  const conversationsStore = useConversationsStore();
16661
17070
  const userStore = useUserStore();
16662
17071
  const hasChat = computed(() => mainStore.hasChat);
16663
- const showConversationsButton = computed(
16664
- () => hasChat.value || conversationsStore.conversations.length > 1
16665
- );
16666
17072
  const createConversation = () => {
16667
17073
  const tempId = conversationsStore.createNew([userStore.user], userStore.user);
16668
17074
  conversationsStore.activateConversation(tempId);
16669
- mainStore.showNewChat();
17075
+ mainStore.hideLeft();
16670
17076
  };
16671
- function showConversations() {
16672
- mainStore.showConversations();
16673
- }
16674
17077
  function close() {
16675
17078
  if (mainStore.leftTarget === "contacts-edit" || mainStore.leftTarget === "contacts-new") {
16676
17079
  mainStore.showConversations();
@@ -16679,18 +17082,13 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
16679
17082
  mainStore.closeCallback?.();
16680
17083
  }
16681
17084
  return (_ctx, _cache) => {
17085
+ const _component_conversations_button_partial = resolveComponent("conversations-button-partial");
16682
17086
  const _component_button_partial = resolveComponent("button-partial");
16683
- return openBlock(), createElementBlock("section", _hoisted_1$a, [
16684
- createBaseVNode("section", _hoisted_2$7, [
16685
- showConversationsButton.value ? (openBlock(), createBlock(_component_button_partial, {
16686
- key: 0,
16687
- icon: "chat",
16688
- variant: "secondary",
16689
- "aria-label": "Conversations",
16690
- onClick: showConversations
16691
- })) : createCommentVNode("", true),
17087
+ return openBlock(), createElementBlock("section", _hoisted_1$b, [
17088
+ createBaseVNode("section", _hoisted_2$8, [
17089
+ createVNode(_component_conversations_button_partial),
16692
17090
  hasChat.value ? (openBlock(), createBlock(_component_button_partial, {
16693
- key: 1,
17091
+ key: 0,
16694
17092
  icon: "add",
16695
17093
  "aria-label": "New chat",
16696
17094
  onClick: createConversation
@@ -16707,10 +17105,10 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
16707
17105
  };
16708
17106
  }
16709
17107
  });
16710
- 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}";
16711
- const ActionsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["styles", [_style_0$b]], ["__scopeId", "data-v-b803cf6d"]]);
16712
- const _hoisted_1$9 = { class: "background-container" };
16713
- 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 };
16714
17112
  const _sfc_main$d = /* @__PURE__ */ defineComponent({
16715
17113
  __name: "chat-header.partial",
16716
17114
  props: {
@@ -16781,7 +17179,7 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
16781
17179
  return openBlock(), createElementBlock("header", {
16782
17180
  class: normalizeClass(["chat-header", { isSupport: isSupport.value }])
16783
17181
  }, [
16784
- createBaseVNode("section", _hoisted_1$9, [
17182
+ createBaseVNode("section", _hoisted_1$a, [
16785
17183
  createBaseVNode("h3", null, [
16786
17184
  !isSupport.value ? (openBlock(), createBlock(_component_name_edit_partial, {
16787
17185
  key: 0,
@@ -16796,9 +17194,10 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
16796
17194
  createTextVNode(toDisplayString$1(_ctx.$t("support")), 1)
16797
17195
  ], 64)) : createCommentVNode("", true)
16798
17196
  ]),
16799
- !isSupport.value && conversationData.value ? (openBlock(), createElementBlock("h4", _hoisted_2$6, [
17197
+ !isSupport.value && conversationData.value ? (openBlock(), createElementBlock("h4", _hoisted_2$7, [
16800
17198
  createVNode(_component_participants_partial, {
16801
17199
  participants: conversationData.value.participants,
17200
+ inline: "",
16802
17201
  "remove-user": "",
16803
17202
  "is-new": isNew.value
16804
17203
  }, null, 8, ["participants", "is-new"]),
@@ -16806,13 +17205,9 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
16806
17205
  key: 0,
16807
17206
  size: "sm",
16808
17207
  variant: "ghost",
17208
+ icon: "edit",
16809
17209
  "aria-label": "Toggle edit mode",
16810
17210
  onClick: _cache[0] || (_cache[0] = ($event) => toggleEdit())
16811
- }, {
16812
- default: withCtx(() => [..._cache[1] || (_cache[1] = [
16813
- createTextVNode(" edit ", -1)
16814
- ])]),
16815
- _: 1
16816
17211
  })) : createCommentVNode("", true)
16817
17212
  ])) : createCommentVNode("", true),
16818
17213
  !isSupport.value && !isNew.value ? (openBlock(), createBlock(_component_chat_actions_partial, {
@@ -16824,8 +17219,8 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
16824
17219
  };
16825
17220
  }
16826
17221
  });
16827
- 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}";
16828
- 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"]]);
16829
17224
  const _sfc_main$c = /* @__PURE__ */ defineComponent({
16830
17225
  __name: "name-edit.partial",
16831
17226
  props: {
@@ -16869,17 +17264,13 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
16869
17264
  return openBlock(), createElementBlock(Fragment, null, [
16870
17265
  !edit.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
16871
17266
  renderSlot(_ctx.$slots, "default"),
16872
- _cache[2] || (_cache[2] = createTextVNode()),
17267
+ _cache[1] || (_cache[1] = createTextVNode()),
16873
17268
  createVNode(_component_button_partial, {
16874
17269
  size: "sm",
16875
17270
  variant: "ghost",
17271
+ icon: "edit",
16876
17272
  "aria-label": "Edit conversation name",
16877
17273
  onClick: toggleEdit
16878
- }, {
16879
- default: withCtx(() => [..._cache[1] || (_cache[1] = [
16880
- createTextVNode(" Edit ", -1)
16881
- ])]),
16882
- _: 1
16883
17274
  })
16884
17275
  ], 64)) : createCommentVNode("", true),
16885
17276
  edit.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
@@ -16899,7 +17290,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
16899
17290
  disabled: !name.value?.length,
16900
17291
  onClick: save
16901
17292
  }, {
16902
- default: withCtx(() => [..._cache[3] || (_cache[3] = [
17293
+ default: withCtx(() => [..._cache[2] || (_cache[2] = [
16903
17294
  createTextVNode(" Save ", -1)
16904
17295
  ])]),
16905
17296
  _: 1
@@ -16910,7 +17301,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
16910
17301
  "aria-label": "Cancel editing",
16911
17302
  onClick: toggleEdit
16912
17303
  }, {
16913
- default: withCtx(() => [..._cache[4] || (_cache[4] = [
17304
+ default: withCtx(() => [..._cache[3] || (_cache[3] = [
16914
17305
  createTextVNode(" X ", -1)
16915
17306
  ])]),
16916
17307
  _: 1
@@ -16920,7 +17311,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
16920
17311
  };
16921
17312
  }
16922
17313
  });
16923
- const _hoisted_1$8 = { class: "notice-partial" };
17314
+ const _hoisted_1$9 = { class: "notice-partial" };
16924
17315
  const _sfc_main$b = /* @__PURE__ */ defineComponent({
16925
17316
  __name: "notice.partial",
16926
17317
  props: {
@@ -16928,14 +17319,14 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
16928
17319
  },
16929
17320
  setup(__props) {
16930
17321
  return (_ctx, _cache) => {
16931
- return openBlock(), createElementBlock("section", _hoisted_1$8, [
17322
+ return openBlock(), createElementBlock("section", _hoisted_1$9, [
16932
17323
  renderSlot(_ctx.$slots, "default", {}, void 0, true)
16933
17324
  ]);
16934
17325
  };
16935
17326
  }
16936
17327
  });
16937
- const _style_0$9 = ".notice-partial[data-v-2ad9f6c8]{display:grid}";
16938
- 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"]]);
16939
17330
  const _sfc_main$a = /* @__PURE__ */ defineComponent({
16940
17331
  ...{ inheritAttrs: false },
16941
17332
  __name: "system-message.partial",
@@ -16979,8 +17370,8 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
16979
17370
  };
16980
17371
  }
16981
17372
  });
16982
- const _hoisted_1$7 = { class: "chat-actions-partial" };
16983
- const _hoisted_2$5 = { class: "options-container" };
17373
+ const _hoisted_1$8 = { class: "chat-actions-partial" };
17374
+ const _hoisted_2$6 = { class: "options-container" };
16984
17375
  const _sfc_main$9 = /* @__PURE__ */ defineComponent({
16985
17376
  __name: "chat-actions.partial",
16986
17377
  props: {
@@ -17011,7 +17402,7 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
17011
17402
  const _component_button_partial = resolveComponent("button-partial");
17012
17403
  const _component_overlay_container_partial = resolveComponent("overlay-container-partial");
17013
17404
  const _component_dialog_partial = resolveComponent("dialog-partial");
17014
- return openBlock(), createElementBlock("section", _hoisted_1$7, [
17405
+ return openBlock(), createElementBlock("section", _hoisted_1$8, [
17015
17406
  createVNode(_component_button_partial, {
17016
17407
  ref_key: "attachTo",
17017
17408
  ref: attachTo,
@@ -17031,7 +17422,7 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
17031
17422
  onClose: toggle
17032
17423
  }, {
17033
17424
  default: withCtx(() => [
17034
- createBaseVNode("section", _hoisted_2$5, [
17425
+ createBaseVNode("section", _hoisted_2$6, [
17035
17426
  createBaseVNode("ul", null, [
17036
17427
  createBaseVNode("li", null, toDisplayString$1(_ctx.$t("mute")), 1),
17037
17428
  createBaseVNode("li", null, toDisplayString$1(_ctx.$t("delete")), 1),
@@ -17072,8 +17463,8 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
17072
17463
  };
17073
17464
  }
17074
17465
  });
17075
- 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)}";
17076
- 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"]]);
17077
17468
  const _sfc_main$8 = /* @__PURE__ */ defineComponent({
17078
17469
  __name: "overlay-container.partial",
17079
17470
  props: {
@@ -17208,10 +17599,10 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
17208
17599
  };
17209
17600
  }
17210
17601
  });
17211
- 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}";
17212
- const OverlayContainerPartial = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["styles", [_style_0$7]]]);
17213
- const _hoisted_1$6 = ["type", "disabled", "aria-busy", "aria-label"];
17214
- 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 = {
17215
17606
  key: 0,
17216
17607
  class: "scm-btn__spinner",
17217
17608
  "aria-hidden": "true"
@@ -17264,19 +17655,19 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
17264
17655
  class: normalizeClass(classes.value),
17265
17656
  onClick
17266
17657
  }, [
17267
- 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, {
17268
17659
  key: 1,
17269
17660
  type: props.icon
17270
17661
  }, null, 8, ["type"])) : (openBlock(), createElementBlock("span", _hoisted_3$2, [
17271
17662
  renderSlot(_ctx.$slots, "default", {}, void 0, true)
17272
17663
  ]))
17273
- ], 10, _hoisted_1$6);
17664
+ ], 10, _hoisted_1$7);
17274
17665
  };
17275
17666
  }
17276
17667
  });
17277
- 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)}}";
17278
- const ButtonPartial = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["styles", [_style_0$6]], ["__scopeId", "data-v-a79bdb66"]]);
17279
- 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" };
17280
17671
  const _sfc_main$6 = /* @__PURE__ */ defineComponent({
17281
17672
  __name: "dialog.partial",
17282
17673
  emits: ["accept", "decline"],
@@ -17290,7 +17681,7 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
17290
17681
  }
17291
17682
  return (_ctx, _cache) => {
17292
17683
  const _component_button_partial = resolveComponent("button-partial");
17293
- return openBlock(), createElementBlock("section", _hoisted_1$5, [
17684
+ return openBlock(), createElementBlock("section", _hoisted_1$6, [
17294
17685
  renderSlot(_ctx.$slots, "default", {}, void 0, true),
17295
17686
  createVNode(_component_button_partial, { onClick: onAccept }, {
17296
17687
  default: withCtx(() => [..._cache[0] || (_cache[0] = [
@@ -17308,10 +17699,10 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
17308
17699
  };
17309
17700
  }
17310
17701
  });
17311
- 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)}";
17312
- const DialogPartial = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["styles", [_style_0$5]], ["__scopeId", "data-v-ae4dfdf7"]]);
17313
- const _hoisted_1$4 = { class: "chat-input" };
17314
- 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" };
17315
17706
  const _hoisted_3$1 = ["value", "placeholder"];
17316
17707
  const _sfc_main$5 = /* @__PURE__ */ defineComponent({
17317
17708
  __name: "chat-input.partial",
@@ -17346,8 +17737,8 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
17346
17737
  watch(() => props.modelValue, () => nextTick(resize2));
17347
17738
  return (_ctx, _cache) => {
17348
17739
  const _component_button_partial = resolveComponent("button-partial");
17349
- return openBlock(), createElementBlock("div", _hoisted_1$4, [
17350
- createBaseVNode("section", _hoisted_2$3, [
17740
+ return openBlock(), createElementBlock("div", _hoisted_1$5, [
17741
+ createBaseVNode("section", _hoisted_2$4, [
17351
17742
  createBaseVNode("textarea", {
17352
17743
  ref_key: "textareaRef",
17353
17744
  ref: textareaRef,
@@ -17369,26 +17760,10 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
17369
17760
  };
17370
17761
  }
17371
17762
  });
17372
- 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}";
17373
- 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" };
17374
17766
  const _sfc_main$4 = /* @__PURE__ */ defineComponent({
17375
- __name: "user-name.partial",
17376
- props: {
17377
- userId: { type: String }
17378
- },
17379
- setup(__props) {
17380
- const props = __props;
17381
- const usersCache = useUsersCacheStore();
17382
- onMounted(() => {
17383
- usersCache.ensure([props.userId]);
17384
- });
17385
- return (_ctx, _cache) => {
17386
- return toDisplayString$1(unref(usersCache).name(props.userId));
17387
- };
17388
- }
17389
- });
17390
- const _hoisted_1$3 = { class: "user-avatar" };
17391
- const _sfc_main$3 = /* @__PURE__ */ defineComponent({
17392
17767
  __name: "user-avatar.partial",
17393
17768
  props: {
17394
17769
  userId: { type: String }
@@ -17413,19 +17788,19 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
17413
17788
  usersCache.ensure([props.userId]);
17414
17789
  });
17415
17790
  return (_ctx, _cache) => {
17416
- return openBlock(), createElementBlock("span", _hoisted_1$3, [
17791
+ return openBlock(), createElementBlock("span", _hoisted_1$4, [
17417
17792
  createBaseVNode("span", {
17418
17793
  class: "user-avatar__circle",
17419
17794
  style: normalizeStyle({ backgroundColor: `hsl(${hue.value}, 45%, 60%)` })
17420
17795
  }, toDisplayString$1(initials.value), 5),
17421
- 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"])
17422
17797
  ]);
17423
17798
  };
17424
17799
  }
17425
17800
  });
17426
- 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}";
17427
- const UserAvatarPartial = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["styles", [_style_0$3]], ["__scopeId", "data-v-1fc1d2df"]]);
17428
- 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 = {
17429
17804
  key: 0,
17430
17805
  xmlns: "http://www.w3.org/2000/svg",
17431
17806
  class: "h-6 w-6",
@@ -17434,7 +17809,7 @@ const _hoisted_1$2 = {
17434
17809
  stroke: "currentColor",
17435
17810
  "stroke-width": "2"
17436
17811
  };
17437
- const _hoisted_2$2 = {
17812
+ const _hoisted_2$3 = {
17438
17813
  key: 1,
17439
17814
  xmlns: "http://www.w3.org/2000/svg",
17440
17815
  class: "h-6 w-6",
@@ -17896,7 +18271,7 @@ const _hoisted_53 = {
17896
18271
  stroke: "currentColor",
17897
18272
  class: "size-6"
17898
18273
  };
17899
- const _sfc_main$2 = /* @__PURE__ */ defineComponent({
18274
+ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
17900
18275
  __name: "icons.partial",
17901
18276
  props: {
17902
18277
  type: { type: String },
@@ -17915,14 +18290,14 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
17915
18290
  return openBlock(), createElementBlock("span", {
17916
18291
  class: normalizeClass(["icon-partial", [__props.type, __props.colorScheme || "colorScheme1", __props.size || "medium", rotateClass.value, { animate: __props.animate }]])
17917
18292
  }, [
17918
- __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] = [
17919
18294
  createBaseVNode("path", {
17920
18295
  "stroke-linecap": "round",
17921
18296
  "stroke-linejoin": "round",
17922
18297
  d: "M9 5l7 7-7 7"
17923
18298
  }, null, -1)
17924
18299
  ])])) : createCommentVNode("", true),
17925
- __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] = [
17926
18301
  createBaseVNode("path", {
17927
18302
  "stroke-linecap": "round",
17928
18303
  "stroke-linejoin": "round",
@@ -18314,24 +18689,58 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
18314
18689
  };
18315
18690
  }
18316
18691
  });
18317
- 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}";
18318
- const IconsPartial = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["styles", [_style_0$2]], ["__scopeId", "data-v-4a547a78"]]);
18319
- const _hoisted_1$1 = { class: "connection-status" };
18320
- const _hoisted_2$1 = { class: "status" };
18321
- 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({
18322
18697
  __name: "connection-status.partial",
18323
18698
  setup(__props) {
18324
18699
  const mainStore = useMainStore();
18325
18700
  const connection = computed(() => mainStore.state);
18326
18701
  return (_ctx, _cache) => {
18327
- return openBlock(), createElementBlock("section", _hoisted_1$1, [
18328
- 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)
18329
18704
  ]);
18330
18705
  };
18331
18706
  }
18332
18707
  });
18333
- const _style_0$1 = ".connection-status[data-v-a483f86f]{position:absolute;top:10px;right:10px;pointer-events:none}";
18334
- 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"]]);
18335
18744
  function resolveFeatures(config) {
18336
18745
  if (config.features?.length) return [...new Set(config.features)];
18337
18746
  switch (config.mode) {
@@ -18364,7 +18773,7 @@ function applyConfig(config, pinia) {
18364
18773
  const MODULES = [
18365
18774
  { name: "chat", component: ChatModule },
18366
18775
  { name: "conversations", component: ConversationsModule },
18367
- { name: "chats", component: _sfc_main$i },
18776
+ { name: "chats", component: _sfc_main$j },
18368
18777
  { name: "contacts", component: ContactsModule }
18369
18778
  ];
18370
18779
  const PARTIALS = [
@@ -18380,9 +18789,10 @@ const PARTIALS = [
18380
18789
  { name: "dialog", component: DialogPartial },
18381
18790
  { name: "chat-input", component: ChatInputPartial },
18382
18791
  { name: "user-avatar", component: UserAvatarPartial },
18383
- { name: "user-name", component: _sfc_main$4 },
18792
+ { name: "user-name", component: _sfc_main$g },
18384
18793
  { name: "icons", component: IconsPartial },
18385
- { name: "connection-status", component: ConnectionStatusPartial }
18794
+ { name: "connection-status", component: ConnectionStatusPartial },
18795
+ { name: "conversations-button", component: ConversationsButtonPartial }
18386
18796
  ];
18387
18797
  function registerComponents(app) {
18388
18798
  loadViaDeclarationSync(MODULES, void 0, "module", app);
@@ -18517,10 +18927,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18517
18927
  const mainStore = useMainStore();
18518
18928
  const userStore = useUserStore();
18519
18929
  const conversationsStore = useConversationsStore();
18520
- const {
18521
- connect,
18522
- authenticate
18523
- } = useWebSocket();
18930
+ useWebSocket();
18524
18931
  const realWidth = useResize().realWidth;
18525
18932
  const viewMode = computed(() => mainStore.viewMode);
18526
18933
  const leftTarget = computed(() => mainStore.leftTarget);
@@ -18536,14 +18943,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18536
18943
  unreadConversations: conversationsStore.totalUnreadConversations
18537
18944
  });
18538
18945
  });
18539
- let wasDisconnected = false;
18540
- watch(() => mainStore.state, (state) => {
18541
- if (state === "connected" && wasDisconnected) {
18542
- conversationsStore.handleReconnect().catch((_error) => {
18543
- });
18544
- }
18545
- wasDisconnected = state === "disconnected";
18546
- });
18547
18946
  function decodeSub(token) {
18548
18947
  if (!token) return void 0;
18549
18948
  try {
@@ -18571,8 +18970,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18571
18970
  if (props.config) applyConfig(props.config);
18572
18971
  const token = await resolveToken() ?? props.config?.token ?? mainStore.authToken;
18573
18972
  if (token) mainStore.authToken = token;
18574
- const urlUser = new URLSearchParams(window.location.search).get("user") || void 0;
18575
- const userId = decodeSub(token) ?? props.config?.userId ?? userStore.user ?? urlUser;
18973
+ const userId = decodeSub(token) ?? props.config?.userId ?? userStore.user;
18576
18974
  if (!userId) {
18577
18975
  if (props.config) console.warn("[studio-chat] config has no usable identity (getToken/token sub or userId)");
18578
18976
  return;
@@ -18580,13 +18978,19 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18580
18978
  started = true;
18581
18979
  userStore.user = userId;
18582
18980
  if (props.config?.user) useUsersCacheStore().setOne({ ...props.config.user, id: userId });
18583
- connect({
18981
+ chatSession.start({
18584
18982
  url: mainStore.wsUrl,
18585
- maxRetries: 50,
18586
- maxRetryDelay: 3e4,
18587
- // resolve a fresh token on every (re)connect so rotation is transparent
18588
- onOpen: async () => authenticate(await resolveToken() ?? mainStore.authToken ?? userId),
18589
- 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
+ }
18590
18994
  });
18591
18995
  if (mainStore.isSupportOnly) bootstrapSupport();
18592
18996
  } finally {
@@ -18596,25 +19000,27 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18596
19000
  watch(() => props.config, start, { immediate: true });
18597
19001
  return (_ctx, _cache) => {
18598
19002
  const _component_actions_partial = resolveComponent("actions-partial");
19003
+ const _component_connection_status_partial = resolveComponent("connection-status-partial");
18599
19004
  const _component_contacts_module = resolveComponent("contacts-module");
18600
19005
  return openBlock(), createElementBlock("div", {
18601
- class: normalizeClass(["studio-chat-root", [viewMode.value, leftTarget.value]])
19006
+ class: normalizeClass(["studio-chat-root", [viewMode.value, leftTarget.value, unref(mainStore).state]])
18602
19007
  }, [
18603
19008
  createBaseVNode("section", _hoisted_1, [
18604
- createVNode(_component_actions_partial)
19009
+ createVNode(_component_actions_partial),
19010
+ unref(mainStore).state === "disconnected" ? (openBlock(), createBlock(_component_connection_status_partial, { key: 0 })) : createCommentVNode("", true)
18605
19011
  ]),
18606
19012
  unref(mainStore).leftTarget ? (openBlock(), createElementBlock("section", _hoisted_2, [
18607
19013
  unref(mainStore).leftTarget === "conversations" ? (openBlock(), createBlock(ConversationsModule, { key: 0 })) : createCommentVNode("", true),
18608
19014
  unref(mainStore).leftTarget === "contacts-new" || unref(mainStore).leftTarget === "contacts-edit" ? (openBlock(), createBlock(_component_contacts_module, { key: 1 })) : createCommentVNode("", true)
18609
19015
  ])) : createCommentVNode("", true),
18610
- 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),
18611
19017
  _cache[0] || (_cache[0] = createBaseVNode("div", { class: "util" }, null, -1))
18612
19018
  ], 2);
18613
19019
  };
18614
19020
  }
18615
19021
  });
18616
- const _style_0 = "[data-v-1bdc8ab0]:host{display:block;height:100%}[data-v-1bdc8ab0]:host,.studio-chat-root[data-v-1bdc8ab0]{--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-1bdc8ab0]{height:100vh}.studio-chat-root[data-v-1bdc8ab0]{display:grid;grid-template-rows:40px 1fr;grid-template-columns:minmax(200px,400px) 1fr;height:100%}.studio-chat-root .header[data-v-1bdc8ab0]{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-1bdc8ab0]{grid-column:1}.studio-chat-root .chats-module[data-v-1bdc8ab0]{grid-column:2}.studio-chat-root.support-no-sidebar[data-v-1bdc8ab0]{grid-template-columns:1fr}.studio-chat-root.support-no-sidebar .chats-module[data-v-1bdc8ab0]{grid-column:1}.studio-chat-root.compact[data-v-1bdc8ab0]{grid-template-columns:1fr}.studio-chat-root.compact .chats-module[data-v-1bdc8ab0]{grid-column:1}.studio-chat-root.compact .main-left-target[data-v-1bdc8ab0]{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-1bdc8ab0],.studio-chat-root.compact.contacts-new .main-left-target[data-v-1bdc8ab0],.studio-chat-root.compact.contacts-edit .main-left-target[data-v-1bdc8ab0]{transform:translate(0)}[data-v-1bdc8ab0]{-moz-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;background-repeat:no-repeat;list-style-type:none;text-decoration:none}";
18617
- const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["styles", [_style_0]], ["__scopeId", "data-v-1bdc8ab0"]]);
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"]]);
18618
19024
  dayjs.extend(relativeTime);
18619
19025
  const LilaquadratStudioChat = /* @__PURE__ */ defineCustomElement(App, {
18620
19026
  shadowRoot: true,