mirror of
https://github.com/dataelement/bisheng.git
synced 2026-08-30 17:58:00 +08:00
Merge branch 'feat/2.6.0' into feat/2.6.0-cofco
This commit is contained in:
@@ -52,11 +52,15 @@ const KNOWN_TYPES = new Set([
|
||||
// they get the calm neutral ServiceBusyNotice (+ optional retry) rather than the red
|
||||
// failure card — a rate limit is the model vendor's availability blip, not a fault.
|
||||
// Mirrors the classifier's RETRYABLE bucket.
|
||||
//
|
||||
// `file_parse_busy` is deliberately NOT here despite being recoverable: the turn is
|
||||
// already lost (the attachment never made it into the prompt), so a Retry button
|
||||
// would re-run against the same throttled service. It keeps its own "busy" wording
|
||||
// but renders as the red card, and the user re-sends from the input box.
|
||||
const TRANSIENT_TYPES = new Set([
|
||||
'rate_limit',
|
||||
'network_timeout',
|
||||
'service_unavailable',
|
||||
'file_parse_busy',
|
||||
]);
|
||||
|
||||
/** Would this failure render as the calm busy notice rather than the red card?
|
||||
|
||||
@@ -11,6 +11,7 @@ import { QueryKeys, dataService } from "~/types/chat";
|
||||
import { addConversation, updateConvoFields } from "~/utils";
|
||||
import store from "~/store";
|
||||
import { useLocalize } from "~/hooks";
|
||||
import { useToastContext } from "~/Providers";
|
||||
import type { ChatMessage } from "~/api/chatApi";
|
||||
import { getAgentMessages } from "~/api/chatApi";
|
||||
import useAiChatSSE, { type SSESubmission } from "~/hooks/useAiChatSSE";
|
||||
@@ -21,8 +22,22 @@ import { SopStatus, taskModeSkillsState } from "~/store/linsight";
|
||||
|
||||
const NO_PARENT = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
/** The fields of an input-box attachment that decide whether it can be sent.
|
||||
Backends disagree on the path key (filepath / file_path / file_url), so all
|
||||
three count as "the upload landed somewhere we can point at". */
|
||||
interface OutgoingAttachment {
|
||||
filepath?: string;
|
||||
file_path?: string;
|
||||
file_url?: string;
|
||||
valid?: boolean;
|
||||
name?: string;
|
||||
filename?: string;
|
||||
file_name?: string;
|
||||
}
|
||||
|
||||
export default function useAiChat(initialConversationId: string = "new", isLingsi: boolean = false, shareToken: string = "") {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
// --- Local state ---
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [conversationId, setConversationId] = useState(initialConversationId);
|
||||
@@ -165,6 +180,24 @@ export default function useAiChat(initialConversationId: string = "new", isLings
|
||||
if (!text.trim() || isStreaming) return;
|
||||
const taskMode = !!opts?.taskMode;
|
||||
|
||||
// An attachment whose upload never landed carries no storage path. It
|
||||
// used to be sent anyway: the backend stored it as an attachment with
|
||||
// no object name, the model never saw it, and on render the bubble
|
||||
// showed "图片已失效" — so the user got an answer that quietly ignored
|
||||
// one of their files. Block the send and name the offender instead.
|
||||
const stranded = ((files ?? []) as OutgoingAttachment[]).filter(
|
||||
(f) => f && (f.valid === false || !(f.filepath || f.file_path || f.file_url)),
|
||||
);
|
||||
if (stranded.length) {
|
||||
showToast({
|
||||
message: localize("com_error_file_upload_incomplete", {
|
||||
0: stranded.map((f) => f.name || f.filename || f.file_name || "").join("、"),
|
||||
}),
|
||||
status: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop client-only fields (e.g. the local `previewUrl` blob string used
|
||||
// for input-box image previews) before they reach the message state or
|
||||
// the SSE payload — the backend only cares about the file ids/paths.
|
||||
@@ -620,7 +653,7 @@ export default function useAiChat(initialConversationId: string = "new", isLings
|
||||
setIsStreaming(true);
|
||||
setSseSubmission(submission);
|
||||
},
|
||||
[conversationId, isStreaming, chatModel, selectedOrgKbs, searchType, selectedAgentTools, dailyTaskSkills, isLingsi, createLinsight, updateLinsight, localize, queryClient]
|
||||
[conversationId, isStreaming, chatModel, selectedOrgKbs, searchType, selectedAgentTools, dailyTaskSkills, isLingsi, createLinsight, updateLinsight, localize, showToast, queryClient]
|
||||
);
|
||||
|
||||
// --- Stop generating ---
|
||||
|
||||
@@ -314,6 +314,7 @@
|
||||
"com_endpoint_top_p": "Top P",
|
||||
"com_endpoint_use_active_assistant": "Use Active Assistant",
|
||||
"com_error_expired_user_key": "Provided key for {{0}} expired at {{1}}. Please provide a new key and try again.",
|
||||
"com_error_file_upload_incomplete": "\"{{0}}\" hasn't finished uploading. Remove it and upload again before sending.",
|
||||
"com_error_files_dupe": "Duplicate file detected.",
|
||||
"com_error_files_empty": "Empty files are not allowed.",
|
||||
"com_error_files_process": "An error occurred while processing the file.",
|
||||
|
||||
@@ -300,6 +300,7 @@
|
||||
"com_endpoint_top_p": "Top P",
|
||||
"com_endpoint_use_active_assistant": "アクティブなアシスタントを使用",
|
||||
"com_error_expired_user_key": "提供された {{0}} キーは {{1}} に有効期限が切れました。新しいキーを提供して再試行してください。",
|
||||
"com_error_file_upload_incomplete": "「{{0}}」のアップロードが完了していません。削除して再アップロードしてから送信してください。",
|
||||
"com_error_files_dupe": "重複ファイルを検出しました",
|
||||
"com_error_files_empty": "空のファイルのアップロードは許可されていません",
|
||||
"com_error_files_process": "ファイル処理中にエラーが発生しました",
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
"12044": "使用的应用未上线",
|
||||
"12045": "部门同时在线会话数已达上限,请稍后再试",
|
||||
"12046": "当前使用人数较多,请稍后再试。",
|
||||
"12047": "文件解析失败,请稍后重试",
|
||||
"12047": "文件解析失败,并发过大,请稍后重试",
|
||||
"12060": "消息不存在或已删除,请刷新会话",
|
||||
"12061": "单次最多选中 200 条消息",
|
||||
"12062": "消息不存在或无访问权",
|
||||
|
||||
@@ -303,6 +303,7 @@
|
||||
"com_endpoint_top_p": "Top P",
|
||||
"com_endpoint_use_active_assistant": "使用激活的助手",
|
||||
"com_error_expired_user_key": "您提供的 {{0}} 密钥已于 {{1}} 过期。请提供新的密钥并重试。",
|
||||
"com_error_file_upload_incomplete": "「{{0}}」尚未上传完成,请移除后重新上传再发送",
|
||||
"com_error_files_dupe": "检测到重复文件",
|
||||
"com_error_files_empty": "不允许上传空文件",
|
||||
"com_error_files_process": "处理文件时发生错误",
|
||||
|
||||
@@ -160,7 +160,14 @@ export function AddSourceDropdown({
|
||||
const isInsideOtherDialog = (node: Node | null) => {
|
||||
if (!(node instanceof Element)) return false;
|
||||
const targetDialog = node.closest('[role="dialog"], [role="alertdialog"]');
|
||||
return targetDialog != null && targetDialog !== rootDialog;
|
||||
if (targetDialog) return targetDialog !== rootDialog;
|
||||
// A modal's full-screen mask is a SIBLING of its [role=alertdialog]
|
||||
// node inside the same portal layer, so `closest` can never see it.
|
||||
// Without this, clicking the mask of the stacked "link unrecognized"
|
||||
// confirm read as "clicked outside" and collapsed this panel — which
|
||||
// looked like the click had passed through the mask.
|
||||
const layerDialog = node.parentElement?.querySelector('[role="dialog"], [role="alertdialog"]');
|
||||
return layerDialog != null && layerDialog !== rootDialog;
|
||||
};
|
||||
|
||||
const closePanel = () => {
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
"12044": "使用的应用未上线",
|
||||
"12045": "部门同时在线会话数已达上限,请稍后再试",
|
||||
"12046": "当前使用人数较多,请稍后再试。",
|
||||
"12047": "文件解析失败,请稍后重试",
|
||||
"12047": "文件解析失败,并发过大,请稍后重试",
|
||||
"12060": "消息不存在或已删除,请刷新会话",
|
||||
"12061": "单次最多选中 200 条消息",
|
||||
"12062": "消息不存在或无访问权",
|
||||
|
||||
Reference in New Issue
Block a user