@paymanai/payman-ask-sdk 1.2.20 → 1.2.22

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.
@@ -116,7 +116,8 @@ function ChatInput({
116
116
  recordingDurationSeconds = 0,
117
117
  onConfirmRecording,
118
118
  onCancelRecording,
119
- transcribedText
119
+ transcribedText,
120
+ onInputFocus
120
121
  }) {
121
122
  const isInputDisabled = disabled || isWaitingForResponse;
122
123
  const showVoiceButton = enableVoice && onVoicePress != null;
@@ -223,6 +224,7 @@ function ChatInput({
223
224
  ref: inputRef,
224
225
  value,
225
226
  onChangeText: onChange,
227
+ onFocus: onInputFocus,
226
228
  onPress: onClick,
227
229
  editable: !isInputDisabled,
228
230
  placeholder: getPlaceholder(),
@@ -1296,16 +1298,57 @@ function MessageList({
1296
1298
  streamingStepsText,
1297
1299
  completedStepsText,
1298
1300
  onExecutionTraceClick,
1299
- className
1301
+ className,
1302
+ isWaitingForResponse = false,
1303
+ scrollToEndHandleRef
1300
1304
  }) {
1301
1305
  const flatListRef = React.useRef(null);
1306
+ const messagesRef = React.useRef(messages);
1307
+ messagesRef.current = messages;
1308
+ const prevWaitingRef = React.useRef(isWaitingForResponse);
1309
+ const prevLastStreamingRef = React.useRef(void 0);
1310
+ const scrollToEnd = React.useCallback(() => {
1311
+ flatListRef.current?.scrollToEnd({ animated: true });
1312
+ }, []);
1313
+ const scrollToEndOnAssistantLayout = React.useCallback(() => {
1314
+ const list = messagesRef.current;
1315
+ const last = list[list.length - 1];
1316
+ const shouldFollow = isWaitingForResponse || last?.role === "assistant" && last.isStreaming;
1317
+ if (!shouldFollow) return;
1318
+ const run = () => flatListRef.current?.scrollToEnd({ animated: false });
1319
+ run();
1320
+ requestAnimationFrame(run);
1321
+ setTimeout(run, 16);
1322
+ setTimeout(run, 48);
1323
+ }, [isWaitingForResponse]);
1324
+ React.useEffect(() => {
1325
+ if (!scrollToEndHandleRef) return;
1326
+ scrollToEndHandleRef.current = scrollToEnd;
1327
+ return () => {
1328
+ scrollToEndHandleRef.current = null;
1329
+ };
1330
+ }, [scrollToEndHandleRef, scrollToEnd]);
1302
1331
  React.useEffect(() => {
1303
1332
  if (messages.length > 0) {
1304
1333
  setTimeout(() => {
1305
- flatListRef.current?.scrollToEnd({ animated: true });
1334
+ scrollToEnd();
1306
1335
  }, 100);
1307
1336
  }
1308
- }, [messages.length]);
1337
+ }, [messages.length, scrollToEnd]);
1338
+ React.useEffect(() => {
1339
+ const last = messages[messages.length - 1];
1340
+ const streaming = last?.isStreaming;
1341
+ if (prevLastStreamingRef.current === true && streaming === false) {
1342
+ setTimeout(() => scrollToEnd(), 0);
1343
+ }
1344
+ prevLastStreamingRef.current = streaming;
1345
+ }, [messages, scrollToEnd]);
1346
+ React.useEffect(() => {
1347
+ if (prevWaitingRef.current === true && isWaitingForResponse === false) {
1348
+ setTimeout(() => scrollToEnd(), 50);
1349
+ }
1350
+ prevWaitingRef.current = isWaitingForResponse;
1351
+ }, [isWaitingForResponse, scrollToEnd]);
1309
1352
  if (isLoading) {
1310
1353
  return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s5.container, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s5.skeletonContent, children: Array.from({ length: 5 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
1311
1354
  MessageRowSkeleton,
@@ -1332,8 +1375,11 @@ function MessageList({
1332
1375
  reactNative.FlatList,
1333
1376
  {
1334
1377
  ref: flatListRef,
1378
+ style: s5.list,
1335
1379
  data: messages,
1336
1380
  keyExtractor: (item) => item.id,
1381
+ keyboardShouldPersistTaps: "handled",
1382
+ keyboardDismissMode: "interactive",
1337
1383
  renderItem: ({ item }) => /* @__PURE__ */ jsxRuntime.jsx(
1338
1384
  MessageRow,
1339
1385
  {
@@ -1356,11 +1402,15 @@ function MessageList({
1356
1402
  s5.listContent,
1357
1403
  layout === "centered" && s5.listContentCentered
1358
1404
  ],
1405
+ ListFooterComponent: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s5.listFooterSpacer }),
1406
+ onContentSizeChange: scrollToEndOnAssistantLayout,
1359
1407
  showsVerticalScrollIndicator: false
1360
1408
  }
1361
1409
  );
1362
1410
  }
1363
1411
  var s5 = reactNative.StyleSheet.create({
1412
+ /** Fills space above the input so messages stay in the scroll region, not behind it. */
1413
+ list: { flex: 1 },
1364
1414
  container: { flex: 1 },
1365
1415
  skeletonContent: { padding: 16, gap: 16 },
1366
1416
  emptyContainer: {
@@ -1393,8 +1443,17 @@ var s5 = reactNative.StyleSheet.create({
1393
1443
  textAlign: "center",
1394
1444
  lineHeight: 20
1395
1445
  },
1396
- listContent: { padding: 16, gap: 16 },
1397
- listContentCentered: { maxWidth: 672, alignSelf: "center", width: "100%" }
1446
+ listContent: {
1447
+ paddingTop: 16,
1448
+ paddingHorizontal: 16,
1449
+ paddingBottom: 16,
1450
+ gap: 16
1451
+ },
1452
+ listContentCentered: { maxWidth: 672, alignSelf: "center", width: "100%" },
1453
+ /** Extra space below last message — thinking/stream text can wrap to multiple lines before scroll runs. */
1454
+ listFooterSpacer: {
1455
+ height: reactNative.Platform.select({ ios: 52, android: 60, default: 52 })
1456
+ }
1398
1457
  });
1399
1458
 
1400
1459
  // src/assets/payman-mono-crop-blue.png
@@ -2057,6 +2116,7 @@ function PaymanChat({
2057
2116
  const recordingStartRef = React.useRef(null);
2058
2117
  const recordingIntervalRef = React.useRef(null);
2059
2118
  const prevInputValueRef = React.useRef(inputValue);
2119
+ const scrollMessagesToEndRef = React.useRef(null);
2060
2120
  const chat = paymanTypescriptAskSdk.useChat(config, callbacks);
2061
2121
  const {
2062
2122
  messages,
@@ -2201,6 +2261,13 @@ function PaymanChat({
2201
2261
  const handleCancelRecording = React.useCallback(() => {
2202
2262
  stopRecording();
2203
2263
  }, [stopRecording]);
2264
+ const handleInputFocus = React.useCallback(() => {
2265
+ const run = () => scrollMessagesToEndRef.current?.();
2266
+ run();
2267
+ requestAnimationFrame(run);
2268
+ setTimeout(run, 100);
2269
+ setTimeout(run, 280);
2270
+ }, []);
2204
2271
  if (isChatDisabled) {
2205
2272
  if (disabledComponent) {
2206
2273
  return /* @__PURE__ */ jsxRuntime.jsx(PaymanChatContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s8.container, style], children: [
@@ -2217,14 +2284,15 @@ function PaymanChat({
2217
2284
  reactNative.KeyboardAvoidingView,
2218
2285
  {
2219
2286
  style: [s8.container, style],
2220
- behavior: reactNative.Platform.OS === "ios" ? "padding" : "height",
2287
+ behavior: "padding",
2221
2288
  keyboardVerticalOffset: reactNative.Platform.OS === "ios" ? 90 : 0,
2222
2289
  children: [
2223
2290
  children,
2224
- /* @__PURE__ */ jsxRuntime.jsx(
2291
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s8.messageListWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
2225
2292
  MessageList,
2226
2293
  {
2227
2294
  messages,
2295
+ isWaitingForResponse,
2228
2296
  isLoading: false,
2229
2297
  emptyStateText,
2230
2298
  showEmptyStateIcon,
@@ -2241,10 +2309,11 @@ function PaymanChat({
2241
2309
  showStreamingDot,
2242
2310
  streamingStepsText,
2243
2311
  completedStepsText,
2244
- onExecutionTraceClick
2312
+ onExecutionTraceClick,
2313
+ scrollToEndHandleRef: scrollMessagesToEndRef
2245
2314
  }
2246
- ),
2247
- hasAskPermission && /* @__PURE__ */ jsxRuntime.jsx(
2315
+ ) }),
2316
+ hasAskPermission && /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s8.inputBarWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
2248
2317
  ChatInput,
2249
2318
  {
2250
2319
  value: inputValue,
@@ -2265,9 +2334,10 @@ function PaymanChat({
2265
2334
  recordingDurationSeconds: recordingElapsedSeconds,
2266
2335
  onConfirmRecording: enableVoice ? handleConfirmRecording : void 0,
2267
2336
  onCancelRecording: enableVoice ? handleCancelRecording : void 0,
2268
- transcribedText: enableVoice && isRecording ? transcribedText : void 0
2337
+ transcribedText: enableVoice && isRecording ? transcribedText : void 0,
2338
+ onInputFocus: handleInputFocus
2269
2339
  }
2270
- ),
2340
+ ) }),
2271
2341
  /* @__PURE__ */ jsxRuntime.jsx(
2272
2342
  UserActionModal,
2273
2343
  {
@@ -2284,6 +2354,20 @@ function PaymanChat({
2284
2354
  ) });
2285
2355
  }
2286
2356
  var s8 = reactNative.StyleSheet.create({
2357
+ /** Lets the message list shrink when the keyboard opens; pairs with FlatList flex:1. */
2358
+ messageListWrap: { flex: 1, minHeight: 0, zIndex: 0 },
2359
+ /**
2360
+ * Keeps the composer above the message list when scrolling so rows (e.g. stream step toggles)
2361
+ * do not paint over the input. Elevation applies on Android only.
2362
+ */
2363
+ inputBarWrap: {
2364
+ width: "100%",
2365
+ zIndex: 2,
2366
+ ...reactNative.Platform.select({
2367
+ android: { elevation: 8 },
2368
+ default: {}
2369
+ })
2370
+ },
2287
2371
  container: {
2288
2372
  flex: 1,
2289
2373
  backgroundColor: "#FFFFFF",