feat: harden support, localize knowledge and add operator availability [skip ci]

Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.

Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.

Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.

Expected source tree: 0a4a7528e9.
This commit is contained in:
2026-10-16 15:21:42 +00:00
parent 299ea50fb5
commit 2f7a491666
176 changed files with 11869 additions and 2716 deletions

View File

@@ -1,3 +1,4 @@
// Modified in this independent fork for support availability; see docs/hardening/README.md.
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
@@ -26,9 +27,8 @@ import { Textarea } from "@/components/ui/textarea"
import {
assignConversation,
transferConversation,
fetchAgentProfilesAll,
type AdminAgentProfile,
} from "@/lib/api/admin"
import { fetchAvailabilityAgents, type OperatorAvailability } from "@/lib/api/support-availability"
import { useI18n } from "@/i18n/provider"
type ConversationTransferDialogProps = {
@@ -87,10 +87,10 @@ function ConversationTransferDialogBody({
const t = useI18n()
const [saving, setSaving] = useState(false)
const [loadingAgents, setLoadingAgents] = useState(false)
const [agents, setAgents] = useState<AdminAgentProfile[]>([])
const [agents, setAgents] = useState<OperatorAvailability[]>([])
const userOptions = agents.map((agent) => ({
value: String(agent.userId),
label: agent.displayName || agent.nickname || agent.username || t("conversationAction.agentFallback", { id: agent.userId }),
label: agent.displayName || t("conversationAction.agentFallback", { id: agent.userId }),
}))
const transferSchema = useMemo(
@@ -123,18 +123,25 @@ function ConversationTransferDialogBody({
}, [conversationId, reset])
useEffect(() => {
if (!conversationId) {
setAgents([])
setLoadingAgents(false)
return
}
setLoadingAgents(true)
fetchAgentProfilesAll()
let active = true
fetchAvailabilityAgents(conversationId)
.then((data) => {
setAgents(data.filter((item) => item.serviceStatus === 0))
if (active) setAgents(data.filter((item) => item.eligible))
})
.catch((error) => {
toast.error(error instanceof Error ? error.message : t("conversationAction.loadAgentsFailed"))
if (active) toast.error(error instanceof Error ? error.message : t("conversationAction.loadAgentsFailed"))
})
.finally(() => {
setLoadingAgents(false)
if (active) setLoadingAgents(false)
})
}, [t])
return () => { active = false }
}, [t, conversationId])
async function onFormSubmit(values: TransferForm) {
if (!conversationId) {
@@ -194,13 +201,15 @@ function ConversationTransferDialogBody({
value={field.value}
options={userOptions}
placeholder={
loadingAgents
!conversationId
? t("conversationAction.conversationMissing")
: loadingAgents
? t("conversationAction.loading")
: t("conversationAction.selectTargetAgent")
}
searchPlaceholder={t("conversationAction.searchAgent")}
emptyText={t("conversationAction.emptyAgents")}
disabled={saving || loadingAgents}
disabled={saving || loadingAgents || !conversationId}
onChange={field.onChange}
/>
)}
@@ -237,7 +246,7 @@ function ConversationTransferDialogBody({
>
{t("conversationAction.cancel")}
</Button>
<Button type="submit" disabled={saving}>
<Button type="submit" disabled={saving || loadingAgents || !conversationId || agents.length === 0}>
<ArrowRightLeftIcon />
{saving
? isAssign