@firstlovecenter/ai-chat 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui/index.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var React = require('react');
5
+ var navigation = require('next/navigation');
5
6
  var lucideReact = require('lucide-react');
6
7
  var radixUi = require('radix-ui');
7
8
  var jsxRuntime = require('react/jsx-runtime');
@@ -672,10 +673,14 @@ var PROVIDER_DESCRIPTIONS = {
672
673
  function AiChat({
673
674
  userFirstName,
674
675
  scopeLabel,
675
- initialProvider
676
+ initialProvider,
677
+ initialSessionId = null
676
678
  }) {
679
+ const router = navigation.useRouter();
677
680
  const [sessions, setSessions] = React.useState([]);
678
- const [activeSessionId, setActiveSessionId] = React.useState(null);
681
+ const [activeSessionId, setActiveSessionId] = React.useState(
682
+ initialSessionId
683
+ );
679
684
  const [answers, setAnswers] = React.useState([]);
680
685
  const [pending, setPending] = React.useState(false);
681
686
  const [question, setQuestion] = React.useState("");
@@ -689,7 +694,6 @@ function AiChat({
689
694
  const textareaRef = React.useRef(null);
690
695
  const lastAnswerRef = React.useRef(null);
691
696
  const prevAnswersLen = React.useRef(0);
692
- const autoOpenedRef = React.useRef(false);
693
697
  React.useLayoutEffect(() => {
694
698
  const el = textareaRef.current;
695
699
  if (!el) return;
@@ -705,33 +709,7 @@ function AiChat({
705
709
  if (!res.ok) return;
706
710
  const data = await res.json();
707
711
  if (cancelled) return;
708
- const list = data.sessions ?? [];
709
- setSessions(list);
710
- if (!autoOpenedRef.current && list.length > 0) {
711
- autoOpenedRef.current = true;
712
- const mostRecentId = list[0].id;
713
- setLoadingSession(true);
714
- setActiveSessionId(mostRecentId);
715
- try {
716
- const sres = await fetch(`/api/chat/sessions/${mostRecentId}`, {
717
- cache: "no-store"
718
- });
719
- if (cancelled) return;
720
- if (!sres.ok) {
721
- setAnswers([]);
722
- return;
723
- }
724
- const sdata = await sres.json();
725
- if (cancelled) return;
726
- setAnswers(messagesToAnswers(sdata.messages ?? []));
727
- } catch {
728
- if (!cancelled) setAnswers([]);
729
- } finally {
730
- if (!cancelled) setLoadingSession(false);
731
- }
732
- } else if (!autoOpenedRef.current) {
733
- autoOpenedRef.current = true;
734
- }
712
+ setSessions(data.sessions ?? []);
735
713
  } catch {
736
714
  }
737
715
  }
@@ -740,6 +718,35 @@ function AiChat({
740
718
  cancelled = true;
741
719
  };
742
720
  }, []);
721
+ React.useEffect(() => {
722
+ if (initialSessionId == null) {
723
+ setAnswers([]);
724
+ return;
725
+ }
726
+ let cancelled = false;
727
+ async function load(id) {
728
+ setLoadingSession(true);
729
+ try {
730
+ const res = await fetch(`/api/chat/sessions/${id}`, { cache: "no-store" });
731
+ if (cancelled) return;
732
+ if (!res.ok) {
733
+ setAnswers([]);
734
+ return;
735
+ }
736
+ const data = await res.json();
737
+ if (cancelled) return;
738
+ setAnswers(messagesToAnswers(data.messages ?? []));
739
+ } catch {
740
+ if (!cancelled) setAnswers([]);
741
+ } finally {
742
+ if (!cancelled) setLoadingSession(false);
743
+ }
744
+ }
745
+ void load(initialSessionId);
746
+ return () => {
747
+ cancelled = true;
748
+ };
749
+ }, [initialSessionId]);
743
750
  React.useEffect(() => {
744
751
  if (answers.length > prevAnswersLen.current && lastAnswerRef.current) {
745
752
  lastAnswerRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
@@ -755,11 +762,18 @@ function AiChat({
755
762
  } catch {
756
763
  }
757
764
  }, []);
765
+ const syncUrl = React.useCallback(
766
+ (id) => {
767
+ router.push(id == null ? "/chat" : `/chat/${id}`);
768
+ },
769
+ [router]
770
+ );
758
771
  const newChat = React.useCallback(() => {
759
772
  setActiveSessionId(null);
760
773
  setAnswers([]);
761
774
  setQuestion("");
762
- }, []);
775
+ syncUrl(null);
776
+ }, [syncUrl]);
763
777
  const changeProvider = React.useCallback(
764
778
  async (next) => {
765
779
  if (next === provider || providerSaving) return;
@@ -782,23 +796,27 @@ function AiChat({
782
796
  },
783
797
  [provider, providerSaving]
784
798
  );
785
- const openSession = React.useCallback(async (id) => {
786
- setLoadingSession(true);
787
- setActiveSessionId(id);
788
- try {
789
- const res = await fetch(`/api/chat/sessions/${id}`, { cache: "no-store" });
790
- if (!res.ok) {
799
+ const openSession = React.useCallback(
800
+ async (id) => {
801
+ setLoadingSession(true);
802
+ setActiveSessionId(id);
803
+ syncUrl(id);
804
+ try {
805
+ const res = await fetch(`/api/chat/sessions/${id}`, { cache: "no-store" });
806
+ if (!res.ok) {
807
+ setAnswers([]);
808
+ return;
809
+ }
810
+ const data = await res.json();
811
+ setAnswers(messagesToAnswers(data.messages ?? []));
812
+ } catch {
791
813
  setAnswers([]);
792
- return;
814
+ } finally {
815
+ setLoadingSession(false);
793
816
  }
794
- const data = await res.json();
795
- setAnswers(messagesToAnswers(data.messages ?? []));
796
- } catch {
797
- setAnswers([]);
798
- } finally {
799
- setLoadingSession(false);
800
- }
801
- }, []);
817
+ },
818
+ [syncUrl]
819
+ );
802
820
  const persistTitle = React.useCallback(
803
821
  async (id, title) => {
804
822
  const trimmed = title.trim();
@@ -828,9 +846,10 @@ function AiChat({
828
846
  if (activeSessionId === id) {
829
847
  setActiveSessionId(null);
830
848
  setAnswers([]);
849
+ syncUrl(null);
831
850
  }
832
851
  },
833
- [activeSessionId]
852
+ [activeSessionId, syncUrl]
834
853
  );
835
854
  const submit = React.useCallback(
836
855
  async (q) => {
@@ -850,6 +869,7 @@ function AiChat({
850
869
  const data = await create.json();
851
870
  sessionId = data.session.id;
852
871
  setActiveSessionId(sessionId);
872
+ syncUrl(sessionId);
853
873
  setSessions((prev) => [
854
874
  { id: data.session.id, title: data.session.title, updatedAt: null },
855
875
  ...prev
@@ -930,7 +950,7 @@ function AiChat({
930
950
  void refreshSessions();
931
951
  }
932
952
  },
933
- [activeSessionId, pending, refreshSessions]
953
+ [activeSessionId, pending, refreshSessions, syncUrl]
934
954
  );
935
955
  const heroVisible = answers.length === 0 && !loadingSession;
936
956
  const greeting = React.useMemo(
@@ -1491,10 +1511,14 @@ function asDataPart(v) {
1491
1511
  function VercelChat({
1492
1512
  userFirstName,
1493
1513
  scopeLabel,
1494
- initialProvider
1514
+ initialProvider,
1515
+ initialSessionId = null
1495
1516
  }) {
1517
+ const router = navigation.useRouter();
1496
1518
  const [sessions, setSessions] = React.useState([]);
1497
- const [activeSessionId, setActiveSessionId] = React.useState(null);
1519
+ const [activeSessionId, setActiveSessionId] = React.useState(
1520
+ initialSessionId
1521
+ );
1498
1522
  const [sidebarOpen, setSidebarOpen] = React.useState(false);
1499
1523
  const [loadingSession, setLoadingSession] = React.useState(false);
1500
1524
  const [provider, setProvider] = React.useState(initialProvider);
@@ -1508,7 +1532,6 @@ function VercelChat({
1508
1532
  const textareaRef = React.useRef(null);
1509
1533
  const lastAnswerRef = React.useRef(null);
1510
1534
  const prevAnswersLen = React.useRef(0);
1511
- const autoOpenedRef = React.useRef(false);
1512
1535
  const activeSessionIdRef = React.useRef(activeSessionId);
1513
1536
  const providerRef = React.useRef(provider);
1514
1537
  React.useEffect(() => {
@@ -1564,48 +1587,7 @@ function VercelChat({
1564
1587
  if (!res.ok) return;
1565
1588
  const json = await res.json();
1566
1589
  if (cancelled) return;
1567
- const list = json.sessions ?? [];
1568
- setSessions(list);
1569
- if (!autoOpenedRef.current && list.length > 0) {
1570
- autoOpenedRef.current = true;
1571
- const mostRecentId = list[0].id;
1572
- setLoadingSession(true);
1573
- setActiveSessionId(mostRecentId);
1574
- try {
1575
- const sres = await fetch(`/api/chat/sessions/${mostRecentId}`, {
1576
- cache: "no-store"
1577
- });
1578
- if (cancelled) return;
1579
- if (!sres.ok) {
1580
- setMessages([]);
1581
- setHydratedBlocks({});
1582
- setHydratedProse({});
1583
- setHydratedErrors({});
1584
- return;
1585
- }
1586
- const sjson = await sres.json();
1587
- if (cancelled) return;
1588
- const { uiMessages, blocksMap, proseMap, errorsMap } = storedToUseChat(
1589
- sjson.messages ?? []
1590
- );
1591
- setMessages(uiMessages);
1592
- setHydratedBlocks(blocksMap);
1593
- setHydratedProse(proseMap);
1594
- setHydratedErrors(errorsMap);
1595
- setStartedAt({});
1596
- } catch {
1597
- if (!cancelled) {
1598
- setMessages([]);
1599
- setHydratedBlocks({});
1600
- setHydratedProse({});
1601
- setHydratedErrors({});
1602
- }
1603
- } finally {
1604
- if (!cancelled) setLoadingSession(false);
1605
- }
1606
- } else if (!autoOpenedRef.current) {
1607
- autoOpenedRef.current = true;
1608
- }
1590
+ setSessions(json.sessions ?? []);
1609
1591
  } catch {
1610
1592
  }
1611
1593
  }
@@ -1613,7 +1595,54 @@ function VercelChat({
1613
1595
  return () => {
1614
1596
  cancelled = true;
1615
1597
  };
1616
- }, [setMessages]);
1598
+ }, []);
1599
+ React.useEffect(() => {
1600
+ if (initialSessionId == null) {
1601
+ setMessages([]);
1602
+ setHydratedBlocks({});
1603
+ setHydratedProse({});
1604
+ setHydratedErrors({});
1605
+ return;
1606
+ }
1607
+ let cancelled = false;
1608
+ async function load(id) {
1609
+ setLoadingSession(true);
1610
+ try {
1611
+ const res = await fetch(`/api/chat/sessions/${id}`, { cache: "no-store" });
1612
+ if (cancelled) return;
1613
+ if (!res.ok) {
1614
+ setMessages([]);
1615
+ setHydratedBlocks({});
1616
+ setHydratedProse({});
1617
+ setHydratedErrors({});
1618
+ return;
1619
+ }
1620
+ const json = await res.json();
1621
+ if (cancelled) return;
1622
+ const { uiMessages, blocksMap, proseMap, errorsMap } = storedToUseChat(
1623
+ json.messages ?? []
1624
+ );
1625
+ setMessages(uiMessages);
1626
+ setHydratedBlocks(blocksMap);
1627
+ setHydratedProse(proseMap);
1628
+ setHydratedErrors(errorsMap);
1629
+ setStartedAt({});
1630
+ } catch {
1631
+ if (!cancelled) {
1632
+ setMessages([]);
1633
+ setHydratedBlocks({});
1634
+ setHydratedProse({});
1635
+ setHydratedErrors({});
1636
+ }
1637
+ } finally {
1638
+ if (!cancelled) setLoadingSession(false);
1639
+ }
1640
+ }
1641
+ void load(initialSessionId);
1642
+ return () => {
1643
+ cancelled = true;
1644
+ };
1645
+ }, [initialSessionId, setMessages]);
1617
1646
  const answers = React.useMemo(() => {
1618
1647
  const liveBlocks = [];
1619
1648
  const liveErrors = [];
@@ -1712,6 +1741,12 @@ function VercelChat({
1712
1741
  }
1713
1742
  prevAnswersLen.current = answers.length;
1714
1743
  }, [answers.length]);
1744
+ const syncUrl = React.useCallback(
1745
+ (id) => {
1746
+ router.push(id == null ? "/chat" : `/chat/${id}`);
1747
+ },
1748
+ [router]
1749
+ );
1715
1750
  const newChat = React.useCallback(() => {
1716
1751
  setActiveSessionId(null);
1717
1752
  setMessages([]);
@@ -1720,7 +1755,8 @@ function VercelChat({
1720
1755
  setHydratedErrors({});
1721
1756
  setStartedAt({});
1722
1757
  setInput("");
1723
- }, [setMessages, setInput]);
1758
+ syncUrl(null);
1759
+ }, [setMessages, setInput, syncUrl]);
1724
1760
  const changeProvider = React.useCallback(
1725
1761
  async (next) => {
1726
1762
  if (next === provider || providerSaving) return;
@@ -1747,6 +1783,7 @@ function VercelChat({
1747
1783
  async (id) => {
1748
1784
  setLoadingSession(true);
1749
1785
  setActiveSessionId(id);
1786
+ syncUrl(id);
1750
1787
  try {
1751
1788
  const res = await fetch(`/api/chat/sessions/${id}`, { cache: "no-store" });
1752
1789
  if (!res.ok) {
@@ -1774,7 +1811,7 @@ function VercelChat({
1774
1811
  setLoadingSession(false);
1775
1812
  }
1776
1813
  },
1777
- [setMessages]
1814
+ [setMessages, syncUrl]
1778
1815
  );
1779
1816
  const persistTitle = React.useCallback(
1780
1817
  async (id, title) => {
@@ -1808,9 +1845,10 @@ function VercelChat({
1808
1845
  setHydratedBlocks({});
1809
1846
  setHydratedProse({});
1810
1847
  setHydratedErrors({});
1848
+ syncUrl(null);
1811
1849
  }
1812
1850
  },
1813
- [activeSessionId, setMessages]
1851
+ [activeSessionId, setMessages, syncUrl]
1814
1852
  );
1815
1853
  const submitForm = React.useCallback(
1816
1854
  async (e) => {
@@ -1827,6 +1865,7 @@ function VercelChat({
1827
1865
  const json = await create.json();
1828
1866
  activeSessionIdRef.current = json.session.id;
1829
1867
  setActiveSessionId(json.session.id);
1868
+ syncUrl(json.session.id);
1830
1869
  setSessions((prev) => [
1831
1870
  { id: json.session.id, title: json.session.title, updatedAt: null },
1832
1871
  ...prev
@@ -1837,7 +1876,7 @@ function VercelChat({
1837
1876
  }
1838
1877
  handleSubmit(e);
1839
1878
  },
1840
- [input, status, handleSubmit]
1879
+ [input, status, handleSubmit, syncUrl]
1841
1880
  );
1842
1881
  React.useEffect(() => {
1843
1882
  setStartedAt((prev) => {