@adminide-stack/yantra-mobile 12.0.28-alpha.66 → 12.0.28-alpha.68

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.
Files changed (35) hide show
  1. package/lib/api/stt.js +54 -0
  2. package/lib/api/stt.js.map +1 -0
  3. package/lib/components/GatewayConnector/GatewayConnector.js +18 -0
  4. package/lib/components/GatewayConnector/GatewayConnector.js.map +1 -0
  5. package/lib/components/NavigationHeader/NavigationHeader.js +214 -0
  6. package/lib/components/NavigationHeader/NavigationHeader.js.map +1 -0
  7. package/lib/components/ThinkingIndicator.js +55 -0
  8. package/lib/components/ThinkingIndicator.js.map +1 -0
  9. package/lib/compute.js +108 -31
  10. package/lib/compute.js.map +1 -1
  11. package/lib/contexts/CdecliConnectionContext.js +47 -0
  12. package/lib/contexts/CdecliConnectionContext.js.map +1 -0
  13. package/lib/contexts/GatewayContext.js +1 -1
  14. package/lib/features/audio-input/AudioRecorderPanel.js +228 -0
  15. package/lib/features/audio-input/AudioRecorderPanel.js.map +1 -0
  16. package/lib/hooks/useCdecliAutoConnect.js +41 -18
  17. package/lib/hooks/useCdecliAutoConnect.js.map +1 -1
  18. package/lib/hooks/useChatApi.js +158 -41
  19. package/lib/hooks/useChatApi.js.map +1 -1
  20. package/lib/hooks/useChatStream.js +59 -16
  21. package/lib/hooks/useChatStream.js.map +1 -1
  22. package/lib/hooks/usePrerequisiteIds.js +8 -12
  23. package/lib/hooks/usePrerequisiteIds.js.map +1 -1
  24. package/lib/index.js +1 -1
  25. package/lib/index.js.map +1 -1
  26. package/lib/routes.json +112 -0
  27. package/lib/screens/Chat/index.js +392 -0
  28. package/lib/screens/Chat/index.js.map +1 -0
  29. package/lib/screens/ChatHistory/index.js +56 -0
  30. package/lib/screens/ChatHistory/index.js.map +1 -0
  31. package/lib/screens/Home/HomeScreen.js +105 -427
  32. package/lib/screens/Home/HomeScreen.js.map +1 -1
  33. package/lib/screens/Home/components/ChatHistoryLanding.js +436 -214
  34. package/lib/screens/Home/components/ChatHistoryLanding.js.map +1 -1
  35. package/package.json +4 -4
@@ -1,265 +1,487 @@
1
- import {jsxs,jsx}from'react/jsx-runtime';import React from'react';import {useWindowDimensions,View,StyleSheet,ScrollView,Pressable}from'react-native';import {MaterialCommunityIcons}from'@expo/vector-icons';import {Text}from'@admin-layout/gluestack-ui-mobile';import {RoomType}from'common';import {useApolloClient}from'@apollo/client/index.js';import {AI_ASSISTANT_CHANNELS_QUERY_VARS}from'../../../hooks/useChatApi.js';import {useGetChannelsByUserWithLastMessageQuery,MessagesDocument}from'common/graphql';import {YantraBrandLoader}from'../../../components/YantraBrandLoader.js';import {mobileTokens}from'../../../theme/mobileTokens.js';const MESSAGES_PER_CHANNEL = 200;
2
- function truncatePreview(text, max = 64) {
1
+ import {jsx,jsxs}from'react/jsx-runtime';import React from'react';import {useWindowDimensions,useColorScheme,View,StyleSheet,Pressable,ActivityIndicator,SectionList,RefreshControl}from'react-native';import {Feather,MaterialCommunityIcons}from'@expo/vector-icons';import {Text}from'@admin-layout/gluestack-ui-mobile';import {useFocusEffect}from'@react-navigation/native';import {useChatHistorySessionsFromChannels,HISTORY_PAGE_SIZE,getHistoryChannelsQueryVariables,chatHistorySessionsFromChannels}from'../../../hooks/useChatApi.js';import {useApolloClient}from'@apollo/client/index.js';import {GetChannelsByUserWithLastMessageDocument}from'common/graphql';import {usePrerequisiteIds}from'../../../hooks/usePrerequisiteIds.js';import ThinkingIndicator from'../../../components/ThinkingIndicator.js';import {mobileTokens}from'../../../theme/mobileTokens.js';var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ function truncate(text, max = 96) {
3
21
  const cleaned = text.replace(/\s+/g, " ").trim();
4
22
  if (cleaned.length <= max) return cleaned;
5
23
  return `${cleaned.slice(0, max).trim()}...`;
6
24
  }
7
- function stripModelCostHeader(content) {
8
- const normalized = content.replace(/\r\n/g, "\n");
9
- return normalized.replace(/^\s*(?:[^\w\n]+\s*)?[a-z0-9][a-z0-9._-]*\s*\(\s*\$[\d.]+\s*\/\s*MTok\s+in\s*\)\s*\n+/i, "");
25
+ const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
26
+ function relativeTime(ts, now) {
27
+ var _a;
28
+ const deltaMs = now - ts;
29
+ if (deltaMs < 6e4) return "now";
30
+ const minutes = Math.floor(deltaMs / 6e4);
31
+ if (minutes < 60) return `${minutes}m`;
32
+ const hours = Math.floor(minutes / 60);
33
+ if (hours < 24) return `${hours}h`;
34
+ const days = Math.floor(hours / 24);
35
+ if (days < 7) {
36
+ const d2 = new Date(ts);
37
+ return (_a = WEEKDAYS[d2.getDay()]) != null ? _a : `${days}d`;
38
+ }
39
+ const d = new Date(ts);
40
+ const sameYear = d.getFullYear() === new Date(now).getFullYear();
41
+ const m = d.getMonth() + 1;
42
+ const day = d.getDate();
43
+ return sameYear ? `${m}/${day}` : `${m}/${day}/${String(d.getFullYear()).slice(2)}`;
10
44
  }
11
- function getPostRole(post) {
12
- var _a, _b, _c, _d, _e, _f, _g, _h;
13
- return (_h = (_g = (_e = (_b = (_a = post == null ? void 0 : post.propsConfiguration) == null ? void 0 : _a.content) == null ? void 0 : _b.role) != null ? _e : (_d = (_c = post == null ? void 0 : post.propsConfiguration) == null ? void 0 : _c.contents) == null ? void 0 : _d.role) != null ? _g : (_f = post == null ? void 0 : post.props) == null ? void 0 : _f.role) != null ? _h : void 0;
45
+ function bucketFor(ts, now) {
46
+ const a = new Date(ts);
47
+ const b = new Date(now);
48
+ const startOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
49
+ const today = startOf(b);
50
+ const yesterday = today - 24 * 60 * 60 * 1e3;
51
+ const weekAgo = today - 7 * 24 * 60 * 60 * 1e3;
52
+ const tsDay = startOf(a);
53
+ if (tsDay >= today) return "today";
54
+ if (tsDay >= yesterday) return "yesterday";
55
+ if (tsDay >= weekAgo) return "week";
56
+ return "earlier";
14
57
  }
15
- function sortPostsByTimeAsc(posts) {
16
- return [...posts].sort((a, b) => {
17
- var _a, _b, _c, _d;
18
- const ta = new Date((_b = (_a = a == null ? void 0 : a.createdAt) != null ? _a : a == null ? void 0 : a.updatedAt) != null ? _b : 0).getTime();
19
- const tb = new Date((_d = (_c = b == null ? void 0 : b.createdAt) != null ? _c : b == null ? void 0 : b.updatedAt) != null ? _d : 0).getTime();
20
- return ta - tb;
58
+ function buildSections(rows, now) {
59
+ const buckets = {
60
+ today: [],
61
+ yesterday: [],
62
+ week: [],
63
+ earlier: []
64
+ };
65
+ for (const row of rows) {
66
+ buckets[bucketFor(row.sortAt, now)].push(row);
67
+ }
68
+ const out = [];
69
+ if (buckets.today.length) out.push({
70
+ key: "today",
71
+ title: "Today",
72
+ data: buckets.today
73
+ });
74
+ if (buckets.yesterday.length) out.push({
75
+ key: "yesterday",
76
+ title: "Yesterday",
77
+ data: buckets.yesterday
78
+ });
79
+ if (buckets.week.length) out.push({
80
+ key: "week",
81
+ title: "This week",
82
+ data: buckets.week
21
83
  });
84
+ if (buckets.earlier.length) out.push({
85
+ key: "earlier",
86
+ title: "Earlier",
87
+ data: buckets.earlier
88
+ });
89
+ return out;
22
90
  }
23
- function buildSessionsFromMessages(posts) {
24
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
25
- const sortedAsc = sortPostsByTimeAsc(posts);
26
- const rows = [];
27
- const hasExplicitUserRole = sortedAsc.some((post) => String(getPostRole(post)).toUpperCase() === "USER");
28
- for (let i = 0; i < sortedAsc.length; i++) {
29
- const current = sortedAsc[i];
30
- const role = getPostRole(current);
31
- if (hasExplicitUserRole && String(role).toUpperCase() !== "USER") continue;
32
- const titleRaw = stripModelCostHeader(String((_a = current == null ? void 0 : current.message) != null ? _a : "").trim()) || "New Chat";
33
- let previewRaw = titleRaw;
34
- for (let j = i + 1; j < sortedAsc.length; j++) {
35
- const next = sortedAsc[j];
36
- const nextRole = getPostRole(next);
37
- if (String(nextRole).toUpperCase() === "ASSISTANT") {
38
- previewRaw = stripModelCostHeader(String((_b = next == null ? void 0 : next.message) != null ? _b : "").trim()) || titleRaw;
39
- break;
40
- }
41
- }
42
- const sessionChannelId = String((_e = (_d = (_c = current == null ? void 0 : current.channel) == null ? void 0 : _c.id) != null ? _d : current == null ? void 0 : current.channel) != null ? _e : "");
43
- if (!sessionChannelId) continue;
44
- rows.push({
45
- id: String((_f = current == null ? void 0 : current.id) != null ? _f : `${sessionChannelId}-${i}`),
46
- channelId: sessionChannelId,
47
- title: truncatePreview(titleRaw, 44),
48
- preview: truncatePreview(previewRaw, 72),
49
- sortAt: new Date((_h = (_g = current == null ? void 0 : current.updatedAt) != null ? _g : current == null ? void 0 : current.createdAt) != null ? _h : 0).getTime()
50
- });
51
- }
52
- if (rows.length > 0) {
53
- return rows.reverse();
54
- }
55
- const fallbackRows = [];
56
- const seenChannelIds = /* @__PURE__ */ new Set();
57
- for (let i = sortedAsc.length - 1; i >= 0; i--) {
58
- const post = sortedAsc[i];
59
- const sessionChannelId = String((_k = (_j = (_i = post == null ? void 0 : post.channel) == null ? void 0 : _i.id) != null ? _j : post == null ? void 0 : post.channel) != null ? _k : "");
60
- if (!sessionChannelId || seenChannelIds.has(sessionChannelId)) continue;
61
- seenChannelIds.add(sessionChannelId);
62
- const text = stripModelCostHeader(String((_l = post == null ? void 0 : post.message) != null ? _l : "").trim()) || "New Chat";
63
- fallbackRows.push({
64
- id: String((_m = post == null ? void 0 : post.id) != null ? _m : `${sessionChannelId}-${i}`),
65
- channelId: sessionChannelId,
66
- title: truncatePreview(text, 44),
67
- preview: truncatePreview(text, 72),
68
- sortAt: new Date((_o = (_n = post == null ? void 0 : post.updatedAt) != null ? _n : post == null ? void 0 : post.createdAt) != null ? _o : 0).getTime()
69
- });
91
+ function buildTheme(isDark) {
92
+ if (isDark) {
93
+ return {
94
+ bg: "#0b1220",
95
+ surface: mobileTokens.color.surface,
96
+ title: "#f1f5f9",
97
+ preview: "#94a3b8",
98
+ meta: "#9aa0b8",
99
+ muted: "#7a809a",
100
+ divider: "rgba(148, 163, 184, 0.12)",
101
+ sectionLabel: "#cbd5e1",
102
+ tileChatBg: "rgba(148, 163, 184, 0.16)",
103
+ tileChatIcon: "#e2e8f0",
104
+ tileSearchBg: "rgba(251, 191, 36, 0.16)",
105
+ tileSearchIcon: "#fde68a",
106
+ pressed: "rgba(148, 163, 184, 0.10)",
107
+ loadMoreText: "#e2e8f0",
108
+ loadMoreBorder: "rgba(148, 163, 184, 0.28)",
109
+ loadMoreBg: "rgba(148, 163, 184, 0.10)",
110
+ refreshTint: "#cbd5e1",
111
+ endDot: "#94a3b8",
112
+ emptyMarkBg: "rgba(148, 163, 184, 0.18)",
113
+ emptyMarkIcon: "#b4bccc"
114
+ };
70
115
  }
71
- return fallbackRows;
116
+ return {
117
+ bg: mobileTokens.color.bg,
118
+ surface: mobileTokens.color.surface,
119
+ title: "#0b1424",
120
+ preview: "#525a73",
121
+ meta: "#7b8197",
122
+ muted: "#94a3b8",
123
+ divider: "rgba(15, 23, 42, 0.06)",
124
+ sectionLabel: "#374151",
125
+ tileChatBg: "rgba(15, 23, 42, 0.06)",
126
+ tileChatIcon: "#1f2937",
127
+ tileSearchBg: "rgba(217, 119, 6, 0.10)",
128
+ tileSearchIcon: "#b45309",
129
+ pressed: "rgba(15, 23, 42, 0.05)",
130
+ loadMoreText: "#1f2937",
131
+ loadMoreBorder: "rgba(15, 23, 42, 0.15)",
132
+ loadMoreBg: "rgba(15, 23, 42, 0.04)",
133
+ refreshTint: "#374151",
134
+ endDot: "#9ca3af",
135
+ emptyMarkBg: "rgba(15, 23, 42, 0.07)",
136
+ emptyMarkIcon: "#6b7280"
137
+ };
138
+ }
139
+ function ModeTile({
140
+ mode,
141
+ theme
142
+ }) {
143
+ const isSearch = mode === "deep-search";
144
+ return /* @__PURE__ */ jsx(View, { style: [styles.tile, {
145
+ backgroundColor: isSearch ? theme.tileSearchBg : theme.tileChatBg
146
+ }], children: isSearch ? /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "lightning-bolt-outline", size: 18, color: theme.tileSearchIcon }) : /* @__PURE__ */ jsx(Feather, { name: "message-circle", size: 18, color: theme.tileChatIcon }) });
72
147
  }
73
148
  function ChatHistoryRow({
74
149
  session,
75
- onSelectSession
150
+ onSelectSession,
151
+ theme,
152
+ timeLabel
76
153
  }) {
77
- return /* @__PURE__ */ jsxs(Pressable, { onPress: () => onSelectSession(session.channelId), style: ({
154
+ const titleColor = session.isPlaceholder ? theme.preview : theme.title;
155
+ const titleStyle = [styles.rowTitle, {
156
+ color: titleColor
157
+ }, session.isPlaceholder ? styles.rowTitlePlaceholder : null];
158
+ return /* @__PURE__ */ jsxs(Pressable, { onPress: () => onSelectSession(session.channelId, session.mode), style: ({
78
159
  pressed
79
- }) => [styles.item, pressed && styles.itemPressed], children: [
80
- /* @__PURE__ */ jsxs(View, { style: styles.itemTop, children: [
81
- /* @__PURE__ */ jsxs(View, { style: styles.rowTitleWrap, children: [
82
- /* @__PURE__ */ jsx(View, { style: styles.rowIconWrap, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "message-text-outline", size: 15, color: mobileTokens.color.primary }) }),
83
- /* @__PURE__ */ jsx(Text, { style: styles.titleText, fontSize: "$lg", color: mobileTokens.color.text, fontWeight: "$semibold", numberOfLines: 1, children: session.title })
160
+ }) => [styles.row, pressed && {
161
+ backgroundColor: theme.pressed
162
+ }], accessibilityRole: "button", accessibilityLabel: session.previewForRender ? `Open conversation: ${session.titleForRender}. ${session.previewForRender}. ${timeLabel}` : `Open conversation: ${session.titleForRender}, ${timeLabel}`, children: [
163
+ /* @__PURE__ */ jsx(ModeTile, { mode: session.mode, theme }),
164
+ /* @__PURE__ */ jsxs(View, { style: styles.rowBody, children: [
165
+ /* @__PURE__ */ jsxs(View, { style: styles.rowHeader, children: [
166
+ /* @__PURE__ */ jsx(Text, { style: titleStyle, numberOfLines: 1, ellipsizeMode: "tail", children: session.titleForRender }),
167
+ /* @__PURE__ */ jsx(Text, { style: [styles.rowDate, {
168
+ color: theme.meta
169
+ }], numberOfLines: 1, children: timeLabel })
84
170
  ] }),
85
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "chevron-right", size: 20, color: mobileTokens.color.textMuted })
86
- ] }),
87
- /* @__PURE__ */ jsx(Text, { fontSize: "$sm", color: mobileTokens.color.textMuted, numberOfLines: 2, children: session.preview })
88
- ] }, session.id);
171
+ session.previewForRender ? /* @__PURE__ */ jsx(Text, { style: [styles.rowPreview, {
172
+ color: theme.preview
173
+ }], numberOfLines: 1, ellipsizeMode: "tail", children: session.previewForRender }) : null
174
+ ] })
175
+ ] });
89
176
  }
90
177
  function ChatHistoryLanding({
91
- onSelectSession,
92
- onCompose
178
+ onSelectSession
93
179
  }) {
94
180
  const {
95
181
  width: screenWidth
96
182
  } = useWindowDimensions();
97
- const client = useApolloClient();
183
+ const colorScheme = useColorScheme();
184
+ const isDark = colorScheme === "dark";
185
+ const theme = React.useMemo(() => buildTheme(isDark), [isDark]);
186
+ const apollo = useApolloClient();
98
187
  const {
99
- data: channelsData,
100
- loading: channelsLoading
101
- } = useGetChannelsByUserWithLastMessageQuery({
102
- variables: AI_ASSISTANT_CHANNELS_QUERY_VARS,
103
- fetchPolicy: "cache-and-network",
104
- errorPolicy: "all",
105
- notifyOnNetworkStatusChange: true,
106
- returnPartialData: true,
107
- context: {
108
- cacheKey: "channels-list"
109
- }
188
+ orgName,
189
+ loading: idsLoading
190
+ } = usePrerequisiteIds();
191
+ const {
192
+ sessions: liveSessions,
193
+ sessionsLoaded,
194
+ refetch,
195
+ sourceChannelCount
196
+ } = useChatHistorySessionsFromChannels(orgName, {
197
+ skip: !orgName
110
198
  });
111
- const [sessions, setSessions] = React.useState([]);
112
- const [threadsLoading, setThreadsLoading] = React.useState(false);
199
+ const [nowTs, setNowTs] = React.useState(() => Date.now());
200
+ const [refreshing, setRefreshing] = React.useState(false);
201
+ const [olderSessions, setOlderSessions] = React.useState([]);
202
+ const [hasMore, setHasMore] = React.useState(false);
203
+ const [loadingMore, setLoadingMore] = React.useState(false);
113
204
  React.useEffect(() => {
114
- let mounted = true;
115
- const load = async () => {
116
- var _a;
117
- const channels = ((_a = channelsData == null ? void 0 : channelsData.channelsByUser) != null ? _a : []).filter((c) => Boolean(c == null ? void 0 : c.id) && (!(c == null ? void 0 : c.type) || c.type === RoomType.Aiassistant));
118
- if (channels.length === 0) {
119
- if (mounted) setSessions([]);
120
- return;
121
- }
122
- setThreadsLoading(true);
123
- try {
124
- const rowsByChannel = await Promise.all(channels.map(async (channel) => {
125
- var _a2, _b, _c;
126
- const channelId = String(channel.id);
127
- const result = await client.query({
128
- query: MessagesDocument,
129
- variables: {
130
- channelId,
131
- parentId: null,
132
- limit: MESSAGES_PER_CHANNEL,
133
- skip: 0
134
- },
135
- fetchPolicy: "cache-first",
136
- context: {
137
- cacheKey: "messages-list"
138
- }
139
- });
140
- return buildSessionsFromMessages((_c = (_b = (_a2 = result == null ? void 0 : result.data) == null ? void 0 : _a2.messages) == null ? void 0 : _b.data) != null ? _c : []);
141
- }));
142
- if (!mounted) return;
143
- const merged = rowsByChannel.flat().sort((a, b) => b.sortAt - a.sortAt).slice(0, 200);
144
- setSessions(merged);
145
- } finally {
146
- if (mounted) setThreadsLoading(false);
205
+ if (olderSessions.length > 0) return;
206
+ setHasMore(sourceChannelCount >= HISTORY_PAGE_SIZE);
207
+ }, [sourceChannelCount, olderSessions.length]);
208
+ useFocusEffect(React.useCallback(() => {
209
+ if (!orgName) return;
210
+ void refetch(getHistoryChannelsQueryVariables(orgName)).catch(() => {
211
+ });
212
+ }, [orgName, refetch]));
213
+ const onRefresh = React.useCallback(async () => {
214
+ if (!orgName) return;
215
+ setRefreshing(true);
216
+ try {
217
+ await refetch(getHistoryChannelsQueryVariables(orgName));
218
+ setOlderSessions([]);
219
+ setNowTs(Date.now());
220
+ } catch (e) {
221
+ } finally {
222
+ setRefreshing(false);
223
+ }
224
+ }, [orgName, refetch]);
225
+ const onLoadMore = React.useCallback(async () => {
226
+ var _a, _b;
227
+ if (!orgName || loadingMore || !hasMore) return;
228
+ setLoadingMore(true);
229
+ try {
230
+ const nextSkip = liveSessions.length + olderSessions.length;
231
+ const result = await apollo.query({
232
+ query: GetChannelsByUserWithLastMessageDocument,
233
+ variables: getHistoryChannelsQueryVariables(orgName, {
234
+ skip: nextSkip,
235
+ limit: HISTORY_PAGE_SIZE
236
+ }),
237
+ fetchPolicy: "network-only"
238
+ });
239
+ const rawCount = ((_b = (_a = result.data) == null ? void 0 : _a.channelsByUser) != null ? _b : []).length;
240
+ const nextBatch = chatHistorySessionsFromChannels(result.data);
241
+ if (nextBatch.length === 0) {
242
+ setHasMore(false);
243
+ } else {
244
+ setOlderSessions((prev) => {
245
+ const map = /* @__PURE__ */ new Map();
246
+ for (const s of prev) map.set(s.channelId, s);
247
+ for (const s of nextBatch) map.set(s.channelId, s);
248
+ return Array.from(map.values());
249
+ });
250
+ setHasMore(rawCount >= HISTORY_PAGE_SIZE);
147
251
  }
148
- };
149
- load();
150
- return () => {
151
- mounted = false;
152
- };
153
- }, [channelsData, client]);
154
- const loading = (channelsLoading || threadsLoading) && sessions.length === 0;
155
- const contentMaxWidth = Math.min(760, Math.max(360, screenWidth - 24));
156
- return /* @__PURE__ */ jsxs(View, { style: styles.container, children: [
157
- /* @__PURE__ */ jsx(ScrollView, { style: styles.list, contentContainerStyle: [styles.listContent, {
158
- alignSelf: "center",
159
- width: contentMaxWidth
160
- }], contentInsetAdjustmentBehavior: "never", automaticallyAdjustContentInsets: false, contentInset: {
161
- top: 0,
162
- left: 0,
163
- bottom: 0,
164
- right: 0
165
- }, scrollIndicatorInsets: {
166
- top: 0,
167
- left: 0,
168
- bottom: 0,
169
- right: 0
170
- }, children: loading ? /* @__PURE__ */ jsx(View, { style: styles.emptyStateWrap, children: /* @__PURE__ */ jsx(YantraBrandLoader, {}) }) : sessions.length === 0 ? /* @__PURE__ */ jsxs(View, { style: styles.emptyStateWrap, children: [
171
- /* @__PURE__ */ jsx(Text, { fontSize: "$2xl", fontWeight: "$semibold", color: "$gray900", textAlign: "center", children: "What can I do for you?" }),
172
- /* @__PURE__ */ jsx(Text, { fontSize: "$sm", color: "$gray600", textAlign: "center", children: "Ask anything to search and build with Yantra." })
173
- ] }) : sessions.map((session) => /* @__PURE__ */ jsx(ChatHistoryRow, { session, onSelectSession }, session.id)) }),
174
- /* @__PURE__ */ jsx(Pressable, { onPress: onCompose, hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Start new chat", style: ({
175
- pressed
176
- }) => [styles.fab, {
177
- right: Math.max((screenWidth - contentMaxWidth) / 2 + 18, 18)
178
- }, pressed && styles.fabPressed], children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 28, color: mobileTokens.color.surface }) })
179
- ] });
252
+ } catch (e) {
253
+ } finally {
254
+ setLoadingMore(false);
255
+ }
256
+ }, [apollo, hasMore, liveSessions.length, loadingMore, olderSessions, orgName]);
257
+ const sessions = React.useMemo(() => {
258
+ const map = /* @__PURE__ */ new Map();
259
+ for (const s of olderSessions) map.set(s.channelId, s);
260
+ for (const s of liveSessions) map.set(s.channelId, s);
261
+ return Array.from(map.values()).sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()).map((s) => __spreadProps(__spreadValues({}, s), {
262
+ id: s.channelId,
263
+ titleForRender: truncate(s.title, 64),
264
+ previewForRender: s.preview ? truncate(s.preview, 96) : "",
265
+ sortAt: s.updatedAt.getTime()
266
+ }));
267
+ }, [liveSessions, olderSessions]);
268
+ const sections = React.useMemo(() => buildSections(sessions, nowTs), [sessions, nowTs]);
269
+ const initialLoading = !sessionsLoaded || idsLoading || !orgName;
270
+ const contentMaxWidth = Math.min(720, Math.max(360, screenWidth - 24));
271
+ const containerStyle = {
272
+ maxWidth: contentMaxWidth,
273
+ alignSelf: "center",
274
+ width: "100%"
275
+ };
276
+ const listFooter = hasMore ? /* @__PURE__ */ jsx(View, { style: [styles.footer, containerStyle], children: /* @__PURE__ */ jsx(Pressable, { onPress: () => void onLoadMore(), disabled: loadingMore, style: ({
277
+ pressed
278
+ }) => [styles.loadMore, {
279
+ backgroundColor: theme.loadMoreBg,
280
+ borderColor: theme.loadMoreBorder
281
+ }, loadingMore && {
282
+ opacity: 0.7
283
+ }, pressed && !loadingMore && {
284
+ opacity: 0.8
285
+ }], accessibilityRole: "button", accessibilityLabel: "Load older conversations", children: loadingMore ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: theme.loadMoreText }) : /* @__PURE__ */ jsx(Text, { style: [styles.loadMoreText, {
286
+ color: theme.loadMoreText
287
+ }], children: "Load older" }) }) }) : !hasMore && sessions.length > 0 ? /* @__PURE__ */ jsxs(View, { style: [styles.endHint, containerStyle], children: [
288
+ /* @__PURE__ */ jsx(View, { style: [styles.endHintDot, {
289
+ backgroundColor: theme.endDot,
290
+ opacity: 0.5
291
+ }] }),
292
+ /* @__PURE__ */ jsx(Text, { style: [styles.endHintText, {
293
+ color: theme.muted
294
+ }], children: "You're all caught up" })
295
+ ] }) : /* @__PURE__ */ jsx(View, { style: {
296
+ height: 24
297
+ } });
298
+ const renderItemSeparator = React.useCallback(() => /* @__PURE__ */ jsx(View, { style: [styles.divider, {
299
+ backgroundColor: theme.divider
300
+ }] }), [theme.divider]);
301
+ return /* @__PURE__ */ jsx(View, { style: [styles.container, {
302
+ backgroundColor: theme.bg
303
+ }], children: initialLoading ? (
304
+ /**
305
+ * Initial-fetch state mirrors the web composer's "Thinking…" pill
306
+ * (see `packages-modules/account/browser/src/pages/chat/ChatSessionPage.tsx`):
307
+ * a thin spinning arc + 12px muted caption, both tinted with
308
+ * `theme.muted` so the indicator reads as a quiet status row
309
+ * rather than a brand-stamped loading screen.
310
+ */
311
+ /* @__PURE__ */ jsx(View, { style: styles.emptyStateWrap, children: /* @__PURE__ */ jsx(ThinkingIndicator, { color: theme.muted }) })
312
+ ) : sessions.length === 0 ? /* @__PURE__ */ jsxs(View, { style: styles.emptyStateWrap, children: [
313
+ /* @__PURE__ */ jsx(View, { style: [styles.emptyMark, {
314
+ backgroundColor: theme.emptyMarkBg
315
+ }], children: /* @__PURE__ */ jsx(Feather, { name: "message-circle", size: 40, color: theme.emptyMarkIcon }) }),
316
+ /* @__PURE__ */ jsx(Text, { style: [styles.emptyTitle, {
317
+ color: theme.title
318
+ }], children: "No conversations yet" }),
319
+ /* @__PURE__ */ jsx(Text, { style: [styles.emptySubtitle, {
320
+ color: theme.muted
321
+ }], children: "Start your first chat to see your conversation history here." })
322
+ ] }) : /* @__PURE__ */ jsx(SectionList, { sections, keyExtractor: (item) => item.channelId, stickySectionHeadersEnabled: false, contentContainerStyle: [styles.listContent, containerStyle, {
323
+ paddingBottom: 32
324
+ }], ListFooterComponent: listFooter, ItemSeparatorComponent: renderItemSeparator, refreshControl: /* @__PURE__ */ jsx(RefreshControl, { refreshing, onRefresh: () => void onRefresh(), tintColor: theme.refreshTint, colors: [theme.refreshTint] }), renderSectionHeader: ({
325
+ section
326
+ }) => /* @__PURE__ */ jsx(View, { style: styles.sectionHeader, children: /* @__PURE__ */ jsx(Text, { style: [styles.sectionLabel, {
327
+ color: theme.sectionLabel
328
+ }], children: section.title }) }), renderItem: ({
329
+ item
330
+ }) => /* @__PURE__ */ jsx(ChatHistoryRow, { session: item, onSelectSession, theme, timeLabel: relativeTime(item.sortAt, nowTs) }), showsVerticalScrollIndicator: false }) });
180
331
  }
181
332
  const styles = StyleSheet.create({
182
333
  container: {
183
- flex: 1,
184
- backgroundColor: mobileTokens.color.bg
185
- },
186
- list: {
187
334
  flex: 1
188
335
  },
189
336
  listContent: {
190
- paddingHorizontal: 16,
191
- paddingTop: 8,
192
- paddingBottom: 100,
193
- gap: 10
337
+ paddingHorizontal: 20,
338
+ paddingTop: 4
194
339
  },
195
- emptyStateWrap: {
196
- flex: 1,
197
- minHeight: 420,
198
- alignItems: "center",
199
- justifyContent: "center",
200
- gap: 10
340
+ /**
341
+ * Section header: sentence case, indigo-tinted, modest size. 28px top padding
342
+ * establishes rhythm between groups without dead air below the label.
343
+ */
344
+ sectionHeader: {
345
+ paddingTop: 28,
346
+ paddingBottom: 10,
347
+ paddingHorizontal: 2
201
348
  },
202
- item: {
203
- borderRadius: 14,
204
- paddingHorizontal: 14,
205
- paddingVertical: 14,
206
- backgroundColor: mobileTokens.color.surface,
207
- borderWidth: 1,
208
- borderColor: mobileTokens.color.border
349
+ sectionLabel: {
350
+ fontSize: 12,
351
+ fontWeight: "600",
352
+ letterSpacing: 0.2,
353
+ textTransform: "none"
209
354
  },
210
- itemTop: {
355
+ /**
356
+ * Row: leading 40×40 mode tile, two-line body (title + preview), right-aligned
357
+ * date aligned to the title's baseline. 14px gap matches the tile-to-text
358
+ * rhythm in Manus / iOS Mail.
359
+ */
360
+ row: {
211
361
  flexDirection: "row",
212
362
  alignItems: "center",
213
- justifyContent: "space-between",
214
- marginBottom: 4,
215
- gap: 8
363
+ paddingVertical: 12,
364
+ paddingHorizontal: 2,
365
+ gap: 14
216
366
  },
217
- rowTitleWrap: {
218
- flexDirection: "row",
367
+ tile: {
368
+ width: 40,
369
+ height: 40,
370
+ borderRadius: 12,
219
371
  alignItems: "center",
372
+ justifyContent: "center"
373
+ },
374
+ rowBody: {
220
375
  flex: 1,
221
- minWidth: 0,
222
- gap: 8
376
+ minWidth: 0
223
377
  },
224
- rowIconWrap: {
225
- width: 24,
226
- height: 24,
227
- borderRadius: 12,
378
+ rowHeader: {
379
+ flexDirection: "row",
380
+ alignItems: "baseline",
381
+ gap: 10
382
+ },
383
+ rowTitle: {
384
+ flex: 1,
385
+ fontSize: 16,
386
+ fontWeight: "600",
387
+ letterSpacing: -0.1,
388
+ lineHeight: 22
389
+ },
390
+ rowTitlePlaceholder: {
391
+ fontWeight: "500",
392
+ fontStyle: "italic"
393
+ },
394
+ rowDate: {
395
+ fontSize: 12.5,
396
+ fontWeight: "500",
397
+ fontVariant: ["tabular-nums"]
398
+ },
399
+ rowPreview: {
400
+ marginTop: 2,
401
+ fontSize: 13.5,
402
+ lineHeight: 19,
403
+ fontWeight: "400",
404
+ letterSpacing: -0.05
405
+ },
406
+ /**
407
+ * Indented hairline: starts past the tile column (40 + 14 = 54) so the rule
408
+ * aligns with the title's leading edge. Mail-style.
409
+ */
410
+ divider: {
411
+ height: StyleSheet.hairlineWidth,
412
+ marginLeft: 56
413
+ },
414
+ footer: {
415
+ paddingTop: 28,
416
+ paddingBottom: 8,
417
+ alignItems: "center"
418
+ },
419
+ /** Bordered ink-neutral pill. Label is the affordance, no leading icon, no chevron. */
420
+ loadMore: {
421
+ flexDirection: "row",
228
422
  alignItems: "center",
229
423
  justifyContent: "center",
230
- backgroundColor: mobileTokens.color.primarySoft,
424
+ paddingVertical: 10,
425
+ paddingHorizontal: 18,
426
+ borderRadius: 999,
231
427
  borderWidth: 1,
232
- borderColor: mobileTokens.color.primaryBorder
428
+ minWidth: 132
233
429
  },
234
- titleText: {
235
- flex: 1,
236
- minWidth: 0
430
+ loadMoreText: {
431
+ fontSize: 13.5,
432
+ fontWeight: "600",
433
+ letterSpacing: -0.05
237
434
  },
238
- itemPressed: {
239
- opacity: 0.82
435
+ endHint: {
436
+ flexDirection: "row",
437
+ alignItems: "center",
438
+ justifyContent: "center",
439
+ gap: 8,
440
+ paddingTop: 32,
441
+ paddingBottom: 12
240
442
  },
241
- fab: {
242
- position: "absolute",
243
- right: 18,
244
- bottom: 28,
245
- width: 56,
246
- height: 56,
247
- borderRadius: 28,
248
- backgroundColor: mobileTokens.color.primary,
249
- borderWidth: 1,
250
- borderColor: mobileTokens.color.primaryBorder,
443
+ endHintDot: {
444
+ width: 4,
445
+ height: 4,
446
+ borderRadius: 2
447
+ },
448
+ endHintText: {
449
+ fontSize: 12.5,
450
+ fontWeight: "500",
451
+ letterSpacing: 0.1
452
+ },
453
+ /**
454
+ * Empty state: stack a 64×64 indigo-tinted mark over title + subtitle.
455
+ * Generous vertical gap (16) between mark and title; tighter gap (6)
456
+ * between title and subtitle so the pair reads as one unit hanging off
457
+ * the mark, not three independent items.
458
+ */
459
+ emptyStateWrap: {
460
+ flex: 1,
461
+ minHeight: 420,
251
462
  alignItems: "center",
252
463
  justifyContent: "center",
253
- shadowColor: "#312e81",
254
- shadowOpacity: 0.22,
255
- shadowRadius: 10,
256
- shadowOffset: {
257
- width: 0,
258
- height: 4
259
- },
260
- elevation: 6
464
+ paddingHorizontal: 32
465
+ },
466
+ emptyMark: {
467
+ width: 72,
468
+ height: 72,
469
+ borderRadius: 36,
470
+ alignItems: "center",
471
+ justifyContent: "center",
472
+ marginBottom: 18
473
+ },
474
+ emptyTitle: {
475
+ fontSize: 20,
476
+ fontWeight: "700",
477
+ letterSpacing: -0.3,
478
+ textAlign: "center",
479
+ marginBottom: 6
261
480
  },
262
- fabPressed: {
263
- opacity: 0.85
481
+ emptySubtitle: {
482
+ fontSize: 14,
483
+ lineHeight: 20,
484
+ textAlign: "center",
485
+ maxWidth: 340
264
486
  }
265
487
  });export{ChatHistoryLanding as default};//# sourceMappingURL=ChatHistoryLanding.js.map