@yushaw/sanqian-chat 0.2.27 → 0.2.32

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.
@@ -125,7 +125,6 @@ function useChat(options) {
125
125
  const pendingInterruptStreamIdRef = (0, import_react.useRef)(null);
126
126
  const currentAgentIdRef = (0, import_react.useRef)(null);
127
127
  const pendingCancelRef = (0, import_react.useRef)(false);
128
- const pendingCancelTimeoutRef = (0, import_react.useRef)(null);
129
128
  const pendingCancelFnRef = (0, import_react.useRef)(null);
130
129
  const suppressStreamRef = (0, import_react.useRef)(false);
131
130
  const currentBlocksRef = (0, import_react.useRef)([]);
@@ -152,7 +151,6 @@ function useChat(options) {
152
151
  isMountedRef.current = false;
153
152
  cancelRef.current?.();
154
153
  if (typewriterIntervalRef.current) clearTimeout(typewriterIntervalRef.current);
155
- if (pendingCancelTimeoutRef.current) clearTimeout(pendingCancelTimeoutRef.current);
156
154
  };
157
155
  }, []);
158
156
  (0, import_react.useEffect)(() => {
@@ -215,10 +213,6 @@ function useChat(options) {
215
213
  const clearPendingCancel = (0, import_react.useCallback)(() => {
216
214
  pendingCancelRef.current = false;
217
215
  pendingCancelFnRef.current = null;
218
- if (pendingCancelTimeoutRef.current) {
219
- clearTimeout(pendingCancelTimeoutRef.current);
220
- pendingCancelTimeoutRef.current = null;
221
- }
222
216
  }, []);
223
217
  const handleStreamEvent = (0, import_react.useCallback)((event, assistantMessageId) => {
224
218
  if (!isMountedRef.current) return;
@@ -723,9 +717,12 @@ function useChat(options) {
723
717
  );
724
718
  cancelRef.current = cancel;
725
719
  if (pendingCancelRef.current) {
726
- clearPendingCancel();
720
+ pendingCancelFnRef.current = cancel;
727
721
  cancel();
728
- cancelRef.current = null;
722
+ if (currentRunIdRef.current) {
723
+ clearPendingCancel();
724
+ cancelRef.current = null;
725
+ }
729
726
  }
730
727
  return true;
731
728
  } catch (err) {
@@ -751,23 +748,15 @@ function useChat(options) {
751
748
  await trySendMessage(content, sendOptions);
752
749
  }, [trySendMessage]);
753
750
  const stopStreaming = (0, import_react.useCallback)(() => {
754
- const shouldDelayCancel = !currentRunIdRef.current;
755
- if (shouldDelayCancel) {
756
- pendingCancelRef.current = true;
757
- pendingCancelFnRef.current = cancelRef.current;
758
- suppressStreamRef.current = true;
759
- if (pendingCancelTimeoutRef.current) {
760
- clearTimeout(pendingCancelTimeoutRef.current);
751
+ pendingCancelRef.current = true;
752
+ pendingCancelFnRef.current = cancelRef.current;
753
+ suppressStreamRef.current = true;
754
+ if (cancelRef.current) {
755
+ cancelRef.current();
756
+ if (currentRunIdRef.current) {
757
+ clearPendingCancel();
758
+ cancelRef.current = null;
761
759
  }
762
- pendingCancelTimeoutRef.current = setTimeout(() => {
763
- pendingCancelRef.current = false;
764
- pendingCancelFnRef.current = null;
765
- pendingCancelTimeoutRef.current = null;
766
- suppressStreamRef.current = false;
767
- }, 1500);
768
- } else {
769
- cancelRef.current?.();
770
- cancelRef.current = null;
771
760
  }
772
761
  flushTypewriter();
773
762
  setMessages((prev) => {
@@ -795,10 +784,6 @@ function useChat(options) {
795
784
  resetStreamBuffers();
796
785
  currentRunIdRef.current = null;
797
786
  pendingInterruptStreamIdRef.current = null;
798
- if (!shouldDelayCancel) {
799
- clearPendingCancel();
800
- suppressStreamRef.current = false;
801
- }
802
787
  setIsStreaming(false);
803
788
  setIsLoading(false);
804
789
  }, [clearPendingCancel, flushTypewriter, resetStreamBuffers]);
@@ -1043,7 +1028,7 @@ var CHAT_UI_STRINGS = {
1043
1028
  hitlSubmit: "Submit",
1044
1029
  hitlCancel: "Cancel",
1045
1030
  hitlRememberChoice: "Remember this choice",
1046
- hitlRequiredField: "This field is required",
1031
+ hitlRequiredField: "Please enter a response before submitting",
1047
1032
  hitlTimeoutIn: "Timeout in",
1048
1033
  hitlSeconds: "s",
1049
1034
  hitlExecuteTool: "Execute",
@@ -1102,7 +1087,7 @@ var CHAT_UI_STRINGS = {
1102
1087
  hitlSubmit: "\u63D0\u4EA4",
1103
1088
  hitlCancel: "\u53D6\u6D88",
1104
1089
  hitlRememberChoice: "\u8BB0\u4F4F\u672C\u6B21\u9009\u62E9",
1105
- hitlRequiredField: "\u6B64\u9879\u5FC5\u586B",
1090
+ hitlRequiredField: "\u8BF7\u8F93\u5165\u56DE\u590D\u540E\u518D\u63D0\u4EA4",
1106
1091
  hitlTimeoutIn: "\u8D85\u65F6\u5269\u4F59",
1107
1092
  hitlSeconds: "\u79D2",
1108
1093
  hitlExecuteTool: "\u6267\u884C",
@@ -1125,9 +1110,15 @@ var CHAT_UI_STRINGS = {
1125
1110
  remove: "\u79FB\u9664"
1126
1111
  }
1127
1112
  };
1113
+ function normalizeLocale(locale) {
1114
+ const normalized = (locale || "en").toLowerCase();
1115
+ if (normalized.startsWith("zh")) return "zh";
1116
+ return "en";
1117
+ }
1128
1118
  function resolveChatStrings(locale = "en", overrides) {
1119
+ const normalizedLocale = normalizeLocale(locale);
1129
1120
  return {
1130
- ...CHAT_UI_STRINGS[locale],
1121
+ ...CHAT_UI_STRINGS[normalizedLocale],
1131
1122
  ...overrides
1132
1123
  };
1133
1124
  }
@@ -6000,7 +5991,7 @@ function createIpcAdapter() {
6000
5991
  onEvent({ ...event, stream_id: streamId });
6001
5992
  });
6002
5993
  try {
6003
- await api.stream({
5994
+ const streamPromise = api.stream({
6004
5995
  streamId,
6005
5996
  messages,
6006
5997
  conversationId,
@@ -6008,6 +5999,11 @@ function createIpcAdapter() {
6008
5999
  attachedResources: options?.attachedResources,
6009
6000
  sessionResources: options?.sessionResources
6010
6001
  });
6002
+ void streamPromise.catch((e) => {
6003
+ streamCallbacks.delete(streamId);
6004
+ const errorMessage = e instanceof Error ? e.message : "Stream error";
6005
+ onEvent({ type: "error", error: errorMessage, stream_id: streamId });
6006
+ });
6011
6007
  return {
6012
6008
  cancel: async () => {
6013
6009
  await api.cancelStream({ streamId });
@@ -8041,7 +8037,7 @@ var defaultStrings = {
8041
8037
  submit: "Submit",
8042
8038
  cancel: "Cancel",
8043
8039
  rememberChoice: "Remember this choice",
8044
- requiredField: "This field is required",
8040
+ requiredField: "Please enter a response before submitting",
8045
8041
  timeoutIn: "Timeout in",
8046
8042
  seconds: "s",
8047
8043
  executeTool: "Execute",
@@ -8052,23 +8048,6 @@ var defaultStrings = {
8052
8048
  approvalRequest: "Approval Request",
8053
8049
  inputRequest: "Input Request"
8054
8050
  };
8055
- var defaultZhStrings = {
8056
- approve: "\u6279\u51C6",
8057
- reject: "\u62D2\u7EDD",
8058
- submit: "\u63D0\u4EA4",
8059
- cancel: "\u53D6\u6D88",
8060
- rememberChoice: "\u8BB0\u4F4F\u672C\u6B21\u9009\u62E9",
8061
- requiredField: "\u6B64\u9879\u5FC5\u586B",
8062
- timeoutIn: "\u8D85\u65F6\u5269\u4F59",
8063
- seconds: "\u79D2",
8064
- executeTool: "\u6267\u884C",
8065
- toolLabel: "\u5DE5\u5177",
8066
- argsLabel: "\u53C2\u6570",
8067
- defaultPrefix: "\u9ED8\u8BA4",
8068
- enterResponse: "\u8BF7\u8F93\u5165\u4F60\u7684\u56DE\u590D...",
8069
- approvalRequest: "\u9700\u8981\u5BA1\u6279",
8070
- inputRequest: "\u9700\u8981\u8F93\u5165"
8071
- };
8072
8051
  var riskColors = {
8073
8052
  low: {
8074
8053
  bg: "bg-blue-500/10",
@@ -8098,42 +8077,15 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8098
8077
  isDarkMode = false,
8099
8078
  strings = {}
8100
8079
  }) {
8101
- const mergedStrings = { ...defaultStrings, ...strings };
8080
+ const t = { ...defaultStrings, ...strings };
8102
8081
  const isApproval = interrupt.type === "approval_request";
8103
8082
  const isUserInput = interrupt.type === "user_input_request";
8104
- const cjkRegex = /[\u3400-\u9fff]/;
8105
- const hasCjkContent = cjkRegex.test(
8106
- [
8107
- interrupt.question || "",
8108
- interrupt.context || "",
8109
- interrupt.reason || "",
8110
- interrupt.tool || "",
8111
- ...interrupt.options || []
8112
- ].join(" ")
8113
- );
8114
- const t = hasCjkContent ? {
8115
- ...mergedStrings,
8116
- approve: mergedStrings.approve === defaultStrings.approve ? defaultZhStrings.approve : mergedStrings.approve,
8117
- reject: mergedStrings.reject === defaultStrings.reject ? defaultZhStrings.reject : mergedStrings.reject,
8118
- submit: mergedStrings.submit === defaultStrings.submit ? defaultZhStrings.submit : mergedStrings.submit,
8119
- cancel: mergedStrings.cancel === defaultStrings.cancel ? defaultZhStrings.cancel : mergedStrings.cancel,
8120
- rememberChoice: mergedStrings.rememberChoice === defaultStrings.rememberChoice ? defaultZhStrings.rememberChoice : mergedStrings.rememberChoice,
8121
- requiredField: mergedStrings.requiredField === defaultStrings.requiredField ? defaultZhStrings.requiredField : mergedStrings.requiredField,
8122
- timeoutIn: mergedStrings.timeoutIn === defaultStrings.timeoutIn ? defaultZhStrings.timeoutIn : mergedStrings.timeoutIn,
8123
- seconds: mergedStrings.seconds === defaultStrings.seconds ? defaultZhStrings.seconds : mergedStrings.seconds,
8124
- executeTool: mergedStrings.executeTool === defaultStrings.executeTool ? defaultZhStrings.executeTool : mergedStrings.executeTool,
8125
- toolLabel: mergedStrings.toolLabel === defaultStrings.toolLabel ? defaultZhStrings.toolLabel : mergedStrings.toolLabel,
8126
- argsLabel: mergedStrings.argsLabel === defaultStrings.argsLabel ? defaultZhStrings.argsLabel : mergedStrings.argsLabel,
8127
- defaultPrefix: mergedStrings.defaultPrefix === defaultStrings.defaultPrefix ? defaultZhStrings.defaultPrefix : mergedStrings.defaultPrefix,
8128
- enterResponse: mergedStrings.enterResponse === defaultStrings.enterResponse ? defaultZhStrings.enterResponse : mergedStrings.enterResponse,
8129
- approvalRequest: mergedStrings.approvalRequest === defaultStrings.approvalRequest ? defaultZhStrings.approvalRequest : mergedStrings.approvalRequest,
8130
- inputRequest: mergedStrings.inputRequest === defaultStrings.inputRequest ? defaultZhStrings.inputRequest : mergedStrings.inputRequest
8131
- } : mergedStrings;
8132
8083
  const [answer, setAnswer] = (0, import_react24.useState)(interrupt.default || "");
8133
8084
  const [selectedIndices, setSelectedIndices] = (0, import_react24.useState)([]);
8134
8085
  const [isComposing, setIsComposing] = (0, import_react24.useState)(false);
8135
8086
  const [timeLeft, setTimeLeft] = (0, import_react24.useState)(interrupt.timeout ?? null);
8136
8087
  const [rememberChoice, setRememberChoice] = (0, import_react24.useState)(false);
8088
+ const [hasAttemptedSubmit, setHasAttemptedSubmit] = (0, import_react24.useState)(false);
8137
8089
  const inputRef = (0, import_react24.useRef)(null);
8138
8090
  const textareaRef = (0, import_react24.useRef)(null);
8139
8091
  const riskLevel = interrupt.risk_level || "medium";
@@ -8164,18 +8116,37 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8164
8116
  }, 1e3);
8165
8117
  return () => clearInterval(timer);
8166
8118
  }, [timeLeft, isUserInput, onSubmit, onCancel]);
8119
+ const hasOptions = Boolean(interrupt.options && interrupt.options.length > 0);
8120
+ const isRequiredMissing = isUserInput && !!interrupt.required && (hasOptions ? selectedIndices.length === 0 : !answer.trim());
8121
+ const showRequiredError = hasAttemptedSubmit && isRequiredMissing;
8167
8122
  const handleOptionToggle = (index) => {
8123
+ if (hasAttemptedSubmit) {
8124
+ setHasAttemptedSubmit(false);
8125
+ }
8168
8126
  if (interrupt.multi_select) {
8169
8127
  setSelectedIndices((prev) => prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index]);
8170
8128
  } else {
8171
8129
  setSelectedIndices([index]);
8172
8130
  }
8173
8131
  };
8132
+ const handleAnswerChange = (value) => {
8133
+ if (hasAttemptedSubmit) {
8134
+ setHasAttemptedSubmit(false);
8135
+ }
8136
+ setAnswer(value);
8137
+ };
8174
8138
  const handleSubmit = () => {
8175
8139
  if (isApproval) {
8176
8140
  onApprove?.(rememberChoice);
8177
8141
  } else if (isUserInput) {
8178
- const hasOptions = interrupt.options && interrupt.options.length > 0;
8142
+ if (isRequiredMissing) {
8143
+ setHasAttemptedSubmit(true);
8144
+ if (!hasOptions) {
8145
+ inputRef.current?.focus();
8146
+ textareaRef.current?.focus();
8147
+ }
8148
+ return;
8149
+ }
8179
8150
  if (hasOptions) {
8180
8151
  const selectedAnswers = selectedIndices.map((i) => interrupt.options[i]).join(", ");
8181
8152
  onSubmit?.({
@@ -8183,11 +8154,6 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8183
8154
  selected_indices: selectedIndices
8184
8155
  });
8185
8156
  } else {
8186
- if (interrupt.required && !answer.trim()) {
8187
- inputRef.current?.focus();
8188
- textareaRef.current?.focus();
8189
- return;
8190
- }
8191
8157
  onSubmit?.({ answer: answer || interrupt.default || "" });
8192
8158
  }
8193
8159
  }
@@ -8231,13 +8197,10 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8231
8197
  lineHeight: 1.55,
8232
8198
  letterSpacing: "0.01em"
8233
8199
  };
8234
- const questionIconStyle = {
8235
- background: isDarkMode ? "rgba(59, 130, 246, 0.26)" : "rgba(59, 130, 246, 0.16)",
8236
- color: isDarkMode ? "#dbeafe" : "#1d4ed8"
8237
- };
8200
+ const showHeaderRow = isApproval || timeLeft !== null && timeLeft > 0;
8238
8201
  const cancelButtonClass = isDarkMode ? "flex-1 rounded-lg border border-zinc-500 bg-zinc-700/80 px-3 py-2 text-sm font-semibold text-zinc-100 transition-colors hover:bg-zinc-600" : "flex-1 rounded-lg border border-zinc-300 bg-zinc-100 px-3 py-2 text-sm font-semibold text-zinc-900 transition-colors hover:bg-zinc-200";
8239
- const isSubmitDisabled = isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0);
8240
- const submitButtonClass = `flex-1 rounded-lg border px-3 py-2 text-sm font-semibold transition-colors ${isSubmitDisabled ? "cursor-not-allowed" : "hover:brightness-105"}`;
8202
+ const isSubmitBlocked = isRequiredMissing;
8203
+ const submitButtonClass = `flex-1 rounded-lg border px-3 py-2 text-sm font-semibold transition-colors ${isSubmitBlocked ? "cursor-pointer" : "hover:brightness-105"}`;
8241
8204
  const cancelButtonStyle = isDarkMode ? {
8242
8205
  background: "rgba(63, 63, 70, 0.82)",
8243
8206
  color: "#f4f4f5",
@@ -8247,7 +8210,7 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8247
8210
  color: "#18181b",
8248
8211
  borderColor: "rgba(113, 113, 122, 0.45)"
8249
8212
  };
8250
- const submitButtonStyle = isSubmitDisabled ? isDarkMode ? {
8213
+ const submitButtonStyle = isSubmitBlocked ? isDarkMode ? {
8251
8214
  background: "rgba(82, 82, 91, 0.72)",
8252
8215
  color: "#d4d4d8",
8253
8216
  borderColor: "rgba(161, 161, 170, 0.35)",
@@ -8268,6 +8231,14 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8268
8231
  borderColor: "transparent",
8269
8232
  boxShadow: "0 2px 10px rgba(37, 99, 235, 0.3)"
8270
8233
  };
8234
+ const inputValidationStyle = showRequiredError ? isDarkMode ? {
8235
+ borderColor: "rgba(248, 113, 113, 0.62)",
8236
+ boxShadow: "0 0 0 2px rgba(248, 113, 113, 0.2)"
8237
+ } : {
8238
+ borderColor: "rgba(220, 38, 38, 0.45)",
8239
+ boxShadow: "0 0 0 2px rgba(220, 38, 38, 0.11)"
8240
+ } : void 0;
8241
+ const requiredHintStyle = isDarkMode ? { color: "rgba(252, 165, 165, 0.92)" } : { color: "rgba(185, 28, 28, 0.84)" };
8271
8242
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8272
8243
  "div",
8273
8244
  {
@@ -8277,8 +8248,8 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8277
8248
  "aria-modal": "true",
8278
8249
  "aria-label": isApproval ? t.approvalRequest : t.inputRequest,
8279
8250
  children: [
8280
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "mb-2 flex items-start justify-between gap-2", children: [
8281
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex min-w-0 items-center gap-2", children: isApproval ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8251
+ showHeaderRow && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `mb-2 flex items-start gap-2 ${isApproval ? "justify-between" : "justify-end"}`, children: [
8252
+ isApproval && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex min-w-0 items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8282
8253
  "span",
8283
8254
  {
8284
8255
  className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium ${riskStyle.bg} ${riskStyle.border} ${riskStyle.text} border`,
@@ -8287,7 +8258,7 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8287
8258
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "capitalize", children: riskLevel })
8288
8259
  ]
8289
8260
  }
8290
- ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "inline-flex h-7 w-7 items-center justify-center rounded-full text-base font-semibold", style: questionIconStyle, children: "?" }) }),
8261
+ ) }),
8291
8262
  timeLeft !== null && timeLeft > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: `text-xs ${textSecondary} whitespace-nowrap`, children: [
8292
8263
  t.timeoutIn,
8293
8264
  " ",
@@ -8316,31 +8287,45 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8316
8287
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { className: "text-[10px]", children: JSON.stringify(interrupt.args, null, 0) })
8317
8288
  ] })
8318
8289
  ] }),
8319
- isUserInput && interrupt.options && interrupt.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-1.5", children: interrupt.options.map((option, index) => {
8320
- const isSelected = selectedIndices.includes(index);
8321
- const optionStyle = isSelected ? isDarkMode ? { borderColor: "rgba(96, 165, 250, 0.75)", background: "rgba(59, 130, 246, 0.24)" } : { borderColor: "rgba(37, 99, 235, 0.36)", background: "rgba(239, 246, 255, 0.98)" } : isDarkMode ? { borderColor: "rgba(113, 113, 122, 0.6)", background: "rgba(9, 9, 11, 0.84)" } : { borderColor: "rgba(113, 113, 122, 0.34)", background: "rgba(250, 250, 250, 0.96)" };
8322
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8323
- "label",
8290
+ isUserInput && interrupt.options && interrupt.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-1.5", children: [
8291
+ interrupt.options.map((option, index) => {
8292
+ const isSelected = selectedIndices.includes(index);
8293
+ const optionStyle = isSelected ? isDarkMode ? { borderColor: "rgba(96, 165, 250, 0.75)", background: "rgba(59, 130, 246, 0.24)" } : { borderColor: "rgba(37, 99, 235, 0.36)", background: "rgba(239, 246, 255, 0.98)" } : isDarkMode ? { borderColor: "rgba(113, 113, 122, 0.6)", background: "rgba(9, 9, 11, 0.84)" } : { borderColor: "rgba(113, 113, 122, 0.34)", background: "rgba(250, 250, 250, 0.96)" };
8294
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8295
+ "label",
8296
+ {
8297
+ className: `flex cursor-pointer items-center gap-2 rounded p-2 transition-colors ${isSelected ? isDarkMode ? "border-blue-500/50 bg-blue-500/20" : "border-blue-200 bg-blue-50" : `${inputBg} border-transparent hover:border-zinc-300`} border`,
8298
+ style: optionStyle,
8299
+ children: [
8300
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
8301
+ "input",
8302
+ {
8303
+ type: interrupt.multi_select ? "checkbox" : "radio",
8304
+ name: "hitl-options",
8305
+ checked: isSelected,
8306
+ onChange: () => handleOptionToggle(index),
8307
+ style: optionControlStyle
8308
+ }
8309
+ ),
8310
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `text-sm ${textPrimary} break-words ${isSelected ? "font-semibold" : "font-medium"}`, children: option })
8311
+ ]
8312
+ },
8313
+ index
8314
+ );
8315
+ }),
8316
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "min-h-[18px]", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8317
+ "div",
8324
8318
  {
8325
- className: `flex cursor-pointer items-center gap-2 rounded p-2 transition-colors ${isSelected ? isDarkMode ? "border-blue-500/50 bg-blue-500/20" : "border-blue-200 bg-blue-50" : `${inputBg} border-transparent hover:border-zinc-300`} border`,
8326
- style: optionStyle,
8319
+ className: `flex items-center gap-1 text-xs transition-all duration-150 ${showRequiredError ? "translate-y-0 opacity-100" : "-translate-y-1 opacity-0"}`,
8320
+ style: requiredHintStyle,
8321
+ "aria-live": "polite",
8327
8322
  children: [
8328
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
8329
- "input",
8330
- {
8331
- type: interrupt.multi_select ? "checkbox" : "radio",
8332
- name: "hitl-options",
8333
- checked: isSelected,
8334
- onChange: () => handleOptionToggle(index),
8335
- style: optionControlStyle
8336
- }
8337
- ),
8338
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `text-sm ${textPrimary} break-words ${isSelected ? "font-semibold" : "font-medium"}`, children: option })
8323
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "inline-block h-1.5 w-1.5 rounded-full bg-current opacity-75" }),
8324
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: t.requiredField })
8339
8325
  ]
8340
- },
8341
- index
8342
- );
8343
- }) }),
8326
+ }
8327
+ ) })
8328
+ ] }),
8344
8329
  isUserInput && (!interrupt.options || interrupt.options.length === 0) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
8345
8330
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
8346
8331
  "input",
@@ -8348,15 +8333,27 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8348
8333
  ref: inputRef,
8349
8334
  type: "text",
8350
8335
  value: answer,
8351
- onChange: (e) => setAnswer(e.target.value),
8336
+ onChange: (e) => handleAnswerChange(e.target.value),
8352
8337
  onKeyDown: handleKeyDown,
8353
8338
  onCompositionStart: () => setIsComposing(true),
8354
8339
  onCompositionEnd: () => setIsComposing(false),
8355
8340
  placeholder: interrupt.default ? `${t.defaultPrefix}: ${interrupt.default}` : t.enterResponse,
8356
- className: `w-full rounded-lg border px-3 py-2 text-sm ${inputBorder} ${inputBg} ${textPrimary} placeholder:${textSecondary} focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/40`
8341
+ className: `w-full rounded-lg border px-3 py-2 text-sm ${inputBorder} ${inputBg} ${textPrimary} placeholder:${textSecondary} ${showRequiredError ? "focus:border-red-400 focus:ring-red-400/25" : "focus:border-blue-500 focus:ring-blue-500/40"} focus:outline-none focus:ring-2`,
8342
+ style: inputValidationStyle
8357
8343
  }
8358
8344
  ),
8359
- interrupt.required && !answer.trim() && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "mt-1 text-xs text-red-500", children: t.requiredField })
8345
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-1 min-h-[18px]", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8346
+ "div",
8347
+ {
8348
+ className: `flex items-center gap-1 text-xs transition-all duration-150 ${showRequiredError ? "translate-y-0 opacity-100" : "-translate-y-1 opacity-0"}`,
8349
+ style: requiredHintStyle,
8350
+ "aria-live": "polite",
8351
+ children: [
8352
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "inline-block h-1.5 w-1.5 rounded-full bg-current opacity-75" }),
8353
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: t.requiredField })
8354
+ ]
8355
+ }
8356
+ ) })
8360
8357
  ] }),
8361
8358
  isApproval && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8362
8359
  "label",
@@ -8392,7 +8389,7 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8392
8389
  "button",
8393
8390
  {
8394
8391
  onClick: handleSubmit,
8395
- disabled: isSubmitDisabled,
8392
+ "aria-disabled": isSubmitBlocked,
8396
8393
  className: submitButtonClass,
8397
8394
  style: submitButtonStyle,
8398
8395
  children: isApproval ? t.approve : t.submit
@@ -9223,9 +9220,10 @@ var SanqianChat = (0, import_react29.memo)(function SanqianChat2({
9223
9220
  const { themeClass, isDarkMode } = useResolvedTheme(resolvedConfig?.theme ?? "auto");
9224
9221
  const accentStyle = useAccentStyle(resolvedConfig?.accentColor);
9225
9222
  const resolvedLogo = logo ?? resolvedConfig?.logo;
9223
+ const resolvedLocale = (0, import_react29.useMemo)(() => normalizeLocale(resolvedConfig?.locale), [resolvedConfig?.locale]);
9226
9224
  const strings = (0, import_react29.useMemo)(
9227
- () => resolveChatStrings(resolvedConfig?.locale, resolvedConfig?.strings),
9228
- [resolvedConfig?.locale, resolvedConfig?.strings]
9225
+ () => resolveChatStrings(resolvedLocale, resolvedConfig?.strings),
9226
+ [resolvedLocale, resolvedConfig?.strings]
9229
9227
  );
9230
9228
  const headerConfig = (0, import_react29.useMemo)(
9231
9229
  () => {
@@ -9411,7 +9409,7 @@ var SanqianChat = (0, import_react29.memo)(function SanqianChat2({
9411
9409
  isResourceAttached: resourcePicker.isResourceAttached,
9412
9410
  pickerError: resourcePicker.error,
9413
9411
  disabled: !!chat.pendingInterrupt || chat.isLoading,
9414
- locale: resolvedConfig?.locale
9412
+ locale: resolvedLocale
9415
9413
  }
9416
9414
  ) : void 0
9417
9415
  }
@@ -9810,7 +9808,10 @@ var FloatingChat = (0, import_react35.memo)(function FloatingChat2({
9810
9808
  minWidth: "100vw"
9811
9809
  }), [accentStyle]);
9812
9810
  const resolvedLogo = logo ?? resolvedConfig?.logo;
9813
- const resolvedLocale = resolvedConfig?.locale ?? locale;
9811
+ const resolvedLocale = (0, import_react35.useMemo)(
9812
+ () => normalizeLocale(resolvedConfig?.locale ?? locale),
9813
+ [resolvedConfig?.locale, locale]
9814
+ );
9814
9815
  const resolvedStrings = (0, import_react35.useMemo)(
9815
9816
  () => resolveChatStrings(resolvedLocale, resolvedConfig?.strings),
9816
9817
  [resolvedLocale, resolvedConfig?.strings]
@@ -10454,9 +10455,10 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10454
10455
  const { themeClass, isDarkMode: resolvedIsDarkMode } = useResolvedTheme(themeMode);
10455
10456
  const accentStyle = useAccentStyle(resolvedConfig?.accentColor);
10456
10457
  const resolvedLogo = logo ?? resolvedConfig?.logo;
10458
+ const resolvedLocale = (0, import_react38.useMemo)(() => normalizeLocale(resolvedConfig?.locale), [resolvedConfig?.locale]);
10457
10459
  const baseStrings = (0, import_react38.useMemo)(
10458
- () => resolveChatStrings(resolvedConfig?.locale, resolvedConfig?.strings),
10459
- [resolvedConfig?.locale, resolvedConfig?.strings]
10460
+ () => resolveChatStrings(resolvedLocale, resolvedConfig?.strings),
10461
+ [resolvedLocale, resolvedConfig?.strings]
10460
10462
  );
10461
10463
  const mergedStrings = (0, import_react38.useMemo)(() => ({ ...baseStrings, ...strings }), [baseStrings, strings]);
10462
10464
  const headerConfig = (0, import_react38.useMemo)(
@@ -10786,7 +10788,7 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10786
10788
  ] }) }),
10787
10789
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "flex-1" }),
10788
10790
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex items-center gap-0.5", style: { WebkitAppRegion: "no-drag" }, children: [
10789
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(PanelHeaderButtons, { locale: resolvedConfig?.locale }),
10791
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(PanelHeaderButtons, { locale: resolvedLocale }),
10790
10792
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "chat-tooltip-wrapper", children: [
10791
10793
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
10792
10794
  "button",
@@ -10912,7 +10914,7 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10912
10914
  isResourceAttached: resourcePicker.isResourceAttached,
10913
10915
  pickerError: resourcePicker.error,
10914
10916
  disabled: disableInput,
10915
- locale: resolvedConfig?.locale === "zh" ? "zh" : "en",
10917
+ locale: resolvedLocale,
10916
10918
  themeClass
10917
10919
  }
10918
10920
  ) : null;