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 the independent AgentDesk fork: localized knowledge workflows and safe display errors.
"use client";
import { useState } from "react";
@@ -145,7 +146,7 @@ export function DebugPanel({ knowledgeBaseId }: DebugPanelProps) {
{getKnowledgeAnswerStatusLabel(answerResult.answerStatus, answerResult.answerStatusName, t)}
</Badge>
<span className="text-xs text-muted-foreground">
{answerResult.latencyMs}ms · {answerResult.modelName || "fallback"}
{answerResult.latencyMs}ms · {answerResult.modelName || t("knowledge.answerFallback")}
</span>
</div>
<div className="rounded-md border bg-background p-3 whitespace-pre-wrap">
@@ -161,12 +162,12 @@ export function DebugPanel({ knowledgeBaseId }: DebugPanelProps) {
>
<div className="flex items-center justify-between gap-2">
<div className="truncate text-xs font-medium">
{getSearchResultLabel(citation)}
{getSearchResultLabel(citation, t)}
</div>
<Badge variant="outline">{citation.score.toFixed(4)}</Badge>
</div>
<div className="mt-1 text-xs text-muted-foreground">
{citation.sectionPath || citation.title || `Chunk #${citation.chunkNo}`}
{citation.sectionPath || citation.title || t("knowledge.chunkNumber", { number: citation.chunkNo })}
</div>
<div className="mt-2 text-xs leading-5 text-muted-foreground whitespace-pre-wrap">
{citation.snippet}
@@ -192,12 +193,12 @@ export function DebugPanel({ knowledgeBaseId }: DebugPanelProps) {
<div key={`${item.chunkId}-${item.documentId}`} className="rounded-md border bg-background p-3">
<div className="flex items-center justify-between gap-2">
<div className="truncate text-sm font-medium">
{getSearchResultLabel(item)}
{getSearchResultLabel(item, t)}
</div>
<Badge variant="outline">{item.score.toFixed(4)}</Badge>
</div>
<div className="mt-1 text-xs text-muted-foreground">
{item.sectionPath || item.title || `Chunk #${item.chunkNo}`}
{item.sectionPath || item.title || t("knowledge.chunkNumber", { number: item.chunkNo })}
</div>
<div className="mt-2 text-xs leading-5 text-muted-foreground whitespace-pre-wrap">
{item.content}
@@ -218,7 +219,7 @@ function getSearchResultLabel(item: {
faqId?: number
documentTitle?: string
documentId?: number
}) {
}, t: (key: string, values?: Record<string, string | number>) => string) {
if (item.faqQuestion) {
return item.faqQuestion
}
@@ -226,7 +227,7 @@ function getSearchResultLabel(item: {
return item.documentTitle
}
if (item.faqId && item.faqId > 0) {
return `FAQ ${item.faqId}`
return t("knowledge.faqNumber", { number: item.faqId })
}
return `Document ${item.documentId ?? 0}`
return t("knowledge.documentNumber", { number: item.documentId ?? 0 })
}

View File

@@ -1,3 +1,4 @@
// Modified in the independent AgentDesk fork: localized knowledge workflows and safe display errors.
"use client";
import {
@@ -60,6 +61,7 @@ import {
KnowledgeDocumentIndexStatus,
Status,
} from "@/lib/generated/enums";
import { getKnowledgeIndexErrorLabel } from "@/lib/knowledge-i18n";
import { cn, formatDateTime } from "@/lib/utils";
import { DocumentEditDialog } from "./document-edit";
import { KnowledgeContentDetailDialog } from "./knowledge-content-detail";
@@ -139,7 +141,7 @@ function renderIndexStatusBadge(item: KnowledgeDocumentListItem, t: TFunction) {
<span className="inline-flex">{badge}</span>
</TooltipTrigger>
<TooltipContent align="start" className="max-w-sm whitespace-normal">
{item.indexError}
{getKnowledgeIndexErrorLabel(item.indexError, t)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
@@ -637,7 +639,7 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL
<div className="mt-1 text-xs text-muted-foreground">
{item.indexStatus === KnowledgeDocumentIndexStatus.Indexed
? t("knowledge.indexTime", { time: formatDateTime(item.indexedAt) })
: item.indexError || getIndexStatusLabel(item.indexStatus, t)}
: getKnowledgeIndexErrorLabel(item.indexError, t) || getIndexStatusLabel(item.indexStatus, t)}
</div>
</div>
<div className="shrink-0 text-xs text-muted-foreground">

View File

@@ -1,3 +1,4 @@
// Modified in the independent AgentDesk fork: localized knowledge workflows and safe display errors.
"use client";
import {
@@ -56,6 +57,7 @@ import {
} from "@/lib/api/admin";
import { KnowledgeDocumentIndexStatus } from "@/lib/generated/enums";
import { useI18n } from "@/i18n/provider";
import { getKnowledgeIndexErrorLabel } from "@/lib/knowledge-i18n";
import { cn, formatDateTime } from "@/lib/utils";
import { FAQEditDialog } from "./faq-edit";
import { FAQImportDialog } from "./faq-import-dialog";
@@ -127,7 +129,7 @@ function renderIndexStatusBadge(item: KnowledgeFAQ, t: TFunction) {
<span className="inline-flex">{badge}</span>
</TooltipTrigger>
<TooltipContent align="start" className="max-w-sm whitespace-normal">
{item.indexError}
{getKnowledgeIndexErrorLabel(item.indexError, t)}
</TooltipContent>
</Tooltip>
</TooltipProvider>

View File

@@ -1,3 +1,4 @@
// Modified by the independent AgentDesk fork for support reliability and availability.
"use client";
import { useEffect, useMemo, useState } from "react";
@@ -42,26 +43,41 @@ export function KnowledgeBulkMoveDialog({
onSubmit,
}: KnowledgeBulkMoveDialogProps) {
const t = useI18n();
const [directories, setDirectories] = useState<KnowledgeDirectory[]>([]);
const [directoryResult, setDirectoryResult] = useState<{
knowledgeBaseId: number;
directories: KnowledgeDirectory[];
}>({ knowledgeBaseId, directories: [] });
const [targetDirectoryId, setTargetDirectoryId] = useState("0");
const [selectionScope, setSelectionScope] = useState({ knowledgeBaseId, open });
// A new base or dialog opening is a new selection session. Reset before the
// children render, rather than exposing the previous target for one frame.
if (selectionScope.knowledgeBaseId !== knowledgeBaseId || selectionScope.open !== open) {
setSelectionScope({ knowledgeBaseId, open });
setTargetDirectoryId("0");
}
useEffect(() => {
if (!open) {
return;
}
setTargetDirectoryId("0");
let active = true;
fetchKnowledgeDirectories(knowledgeBaseId)
.then(setDirectories)
.then((result) => {
if (active) setDirectoryResult({ knowledgeBaseId, directories: result });
})
.catch((error) => {
if (!active) return;
console.error(error);
setDirectories([]);
setDirectoryResult({ knowledgeBaseId, directories: [] });
});
return () => { active = false; };
}, [knowledgeBaseId, open]);
const directoryOptions = useMemo(() => [
{ value: "0", label: t("knowledge.rootContent") },
...flattenDirectoryOptions(directories),
], [directories, t]);
...flattenDirectoryOptions(directoryResult.knowledgeBaseId === knowledgeBaseId ? directoryResult.directories : []),
], [directoryResult, knowledgeBaseId, t]);
return (
<ProjectDialog

View File

@@ -1,3 +1,4 @@
// Modified in the independent AgentDesk fork: localized knowledge workflows and safe display errors.
"use client";
import { useEffect, useState } from "react";
@@ -15,6 +16,7 @@ import {
import { markdownToHtml } from "@/components/content-editor/convert";
import { KnowledgeDocumentContentType } from "@/lib/generated/enums";
import { useI18n } from "@/i18n/provider";
import { getKnowledgeStatusLabel, getKnowledgeIndexStatusLabel, getKnowledgeIndexErrorLabel } from "@/lib/knowledge-i18n";
import { formatDateTime } from "@/lib/utils";
type KnowledgeContentDetailDialogProps = {
@@ -128,11 +130,11 @@ export function KnowledgeContentDetailDialog({
/>
<div className="space-y-1">
<div className="text-xs text-muted-foreground">{t("knowledge.status")}</div>
<Badge variant="outline">{item.statusName || item.status}</Badge>
<Badge variant="outline">{getKnowledgeStatusLabel(item.status, t)}</Badge>
</div>
<div className="space-y-1">
<div className="text-xs text-muted-foreground">{t("knowledge.indexStatus")}</div>
<Badge variant="secondary">{item.indexStatusName || item.indexStatus}</Badge>
<Badge variant="secondary">{getKnowledgeIndexStatusLabel(item.indexStatus, t)}</Badge>
</div>
<DetailField
label={t("knowledge.createdAt")}
@@ -158,7 +160,7 @@ export function KnowledgeContentDetailDialog({
{item.indexError ? (
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive">
{item.indexError}
{getKnowledgeIndexErrorLabel(item.indexError, t)}
</div>
) : null}

View File

@@ -1,3 +1,4 @@
// Modified in the independent AgentDesk fork: localized knowledge workflows and safe display errors.
"use client"
import { useCallback, useEffect, useMemo, useState } from "react"
@@ -57,7 +58,7 @@ function CitationList({ hits, t }: { hits: KnowledgeRetrieveHit[]; t: TFunction
<div key={item.id} className="rounded-lg border p-3">
<div className="flex items-center gap-2">
<span className="font-medium">{getHitSourceLabel(item, t)}</span>
<Badge variant="outline">Chunk #{item.chunkNo}</Badge>
<Badge variant="outline">{t("knowledge.chunkNumber", { number: item.chunkNo })}</Badge>
</div>
<div className="mt-1 text-xs text-muted-foreground">
{item.sectionPath || item.title || t("knowledge.unrecordedSection")}
@@ -124,6 +125,7 @@ export function RetrieveLogDetailDrawer({
</div>
) : (
<div className="space-y-6 pb-6">
<p className="text-xs text-muted-foreground">{t("knowledge.historyRecordedNotice")}</p>
<section className="space-y-3">
<h3 className="text-sm font-semibold">{t("knowledge.requestInfo")}</h3>
<div className="grid gap-3 rounded-lg border p-4 md:grid-cols-2">
@@ -143,7 +145,7 @@ export function RetrieveLogDetailDrawer({
</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Request ID</div>
<div className="text-xs text-muted-foreground">{t("knowledge.requestId")}</div>
<div className="mt-1 break-all font-mono text-xs">{detail.log.requestId || "-"}</div>
</div>
<div className="md:col-span-2">
@@ -165,15 +167,15 @@ export function RetrieveLogDetailDrawer({
<h3 className="text-sm font-semibold">{t("knowledge.retrieveStrategy")}</h3>
<div className="grid gap-3 rounded-lg border p-4 md:grid-cols-3">
<Metric
label="Chunk Provider"
label={t("knowledge.chunkProvider")}
value={detail.log.chunkProvider ? getKnowledgeChunkProviderLabel(detail.log.chunkProvider, t) : "-"}
mono
/>
<Metric label="Target Tokens" value={detail.log.chunkTargetTokens} />
<Metric label="Max Tokens" value={detail.log.chunkMaxTokens} />
<Metric label="Overlap Tokens" value={detail.log.chunkOverlapTokens} />
<Metric label="Rerank" value={detail.log.rerankEnabled ? t("knowledge.rerankEnabled") : t("knowledge.rerankDisabled")} />
<Metric label="Rerank Limit" value={detail.log.rerankLimit} />
<Metric label={t("knowledge.targetToken")} value={detail.log.chunkTargetTokens} />
<Metric label={t("knowledge.maxToken")} value={detail.log.chunkMaxTokens} />
<Metric label={t("knowledge.overlapToken")} value={detail.log.chunkOverlapTokens} />
<Metric label={t("knowledge.rerank")} value={detail.log.rerankEnabled ? t("knowledge.rerankEnabled") : t("knowledge.rerankDisabled")} />
<Metric label={t("knowledge.rerankLimitLabel")} value={detail.log.rerankLimit} />
</div>
</section>
@@ -187,12 +189,12 @@ export function RetrieveLogDetailDrawer({
<Metric label={t("knowledge.hitCount")} value={detail.log.hitCount} />
<Metric label={t("knowledge.citations")} value={detail.log.citationCount} />
<Metric label={t("knowledge.contextChunks")} value={detail.log.usedChunkCount} />
<Metric label="Top Score" value={detail.log.topScore.toFixed(4)} mono />
<Metric label={t("knowledge.topScore")} value={detail.log.topScore.toFixed(4)} mono />
<Metric label={t("knowledge.retrieveMs")} value={`${detail.log.retrieveMs} ms`} />
<Metric label={t("knowledge.generateMs")} value={`${detail.log.generateMs} ms`} />
<Metric label={t("knowledge.totalMs")} value={`${detail.log.latencyMs} ms`} />
<Metric label="Prompt Tokens" value={detail.log.promptTokens} />
<Metric label="Completion Tokens" value={detail.log.completionTokens} />
<Metric label={t("knowledge.promptTokens")} value={detail.log.promptTokens} />
<Metric label={t("knowledge.completionTokens")} value={detail.log.completionTokens} />
<Metric label={t("knowledge.model")} value={detail.log.modelName || "-"} mono />
<Metric label={t("knowledge.sessionId")} value={detail.log.sessionId || "-"} mono />
</div>
@@ -221,10 +223,10 @@ export function RetrieveLogDetailDrawer({
</div>
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
<span>{t("knowledge.section", { section: item.sectionPath || item.title || "-" })}</span>
<span>Chunk #{item.chunkNo}</span>
<span>Provider:{item.provider || "-"}</span>
<span>Score:{item.score.toFixed(4)}</span>
<span>Rerank:{item.rerankScore ? item.rerankScore.toFixed(4) : "-"}</span>
<span>{t("knowledge.chunkNumber", { number: item.chunkNo })}</span>
<span>{t("knowledge.providerValue", { value: item.provider ? getKnowledgeChunkProviderLabel(item.provider, t) : "-" })}</span>
<span>{t("knowledge.scoreValue", { value: item.score.toFixed(4) })}</span>
<span>{t("knowledge.rerankValue", { value: item.rerankScore ? item.rerankScore.toFixed(4) : "-" })}</span>
</div>
<Separator className="my-3" />
<div className="text-sm leading-6 whitespace-pre-wrap text-foreground/90">
@@ -236,7 +238,7 @@ export function RetrieveLogDetailDrawer({
</section>
<section className="space-y-3">
<h3 className="text-sm font-semibold">TraceData</h3>
<h3 className="text-sm font-semibold">{t("knowledge.traceData")}</h3>
<div className="rounded-lg border bg-muted/20 p-4">
<pre className="overflow-x-auto text-xs leading-6 text-muted-foreground">
{traceData ? JSON.stringify(traceData, null, 2) : detail.log.traceData || "-"}
@@ -259,9 +261,9 @@ function getHitSourceLabel(item: KnowledgeRetrieveHit, t: TFunction) {
return item.documentTitle
}
if (item.faqId > 0) {
return `FAQ #${item.faqId}`
return t("knowledge.faqNumber", { number: item.faqId })
}
return `${t("knowledge.document")} #${item.documentId}`
return t("knowledge.documentNumber", { number: item.documentId })
}
function Metric({

View File

@@ -1,3 +1,4 @@
// Modified in the independent AgentDesk fork: localized knowledge workflows and safe display errors.
"use client"
import { useMemo, useState } from "react"
@@ -190,13 +191,13 @@ export function RetrieveLogList({
},
{
name: "rerankEnabled",
label: "Rerank",
label: t("knowledge.rerank"),
type: "select",
defaultValue: "all",
allValue: "all",
valueType: "number",
options: rerankOptions,
placeholder: "Rerank",
placeholder: t("knowledge.rerank"),
},
]}
fetchList={(query) => fetchKnowledgeRetrieveLogs({ knowledgeBaseId, ...query })}
@@ -243,20 +244,20 @@ export function RetrieveLogList({
},
{
key: "topScore",
label: "TopScore",
label: t("knowledge.topScore"),
className: "w-24 text-right font-mono text-xs",
render: (item) => item.topScore.toFixed(4),
},
{
key: "provider",
label: "Provider",
label: t("knowledge.chunkProvider"),
className: "w-28",
render: (item) =>
item.chunkProvider ? providerLabel(item.chunkProvider, t) : "-",
},
{
key: "rerank",
label: "Rerank",
label: t("knowledge.rerank"),
className: "w-24",
render: (item) =>
item.rerankEnabled

View File

@@ -1,3 +1,4 @@
// Modified in the independent AgentDesk fork: localized knowledge workflows and safe display errors.
"use client"
import { CircleHelpIcon, FileTextIcon, RefreshCwIcon } from "lucide-react"
@@ -19,6 +20,7 @@ import {
} from "@/lib/api/admin"
import { KnowledgeBaseType, Status } from "@/lib/generated/enums"
import { useI18n } from "@/i18n/provider"
import { getKnowledgeTypeLabel, getKnowledgeStatusLabel } from "@/lib/knowledge-i18n"
import { formatDateTime } from "@/lib/utils"
export default function DashboardKnowledgePage() {
@@ -83,7 +85,7 @@ export default function DashboardKnowledgePage() {
{
key: "type",
label: t("knowledge.columnType"),
render: (item) => <Badge variant="outline">{item.knowledgeTypeName}</Badge>,
render: (item) => <Badge variant="outline">{getKnowledgeTypeLabel(item.knowledgeType, t)}</Badge>,
},
{
key: "contentCount",
@@ -101,7 +103,7 @@ export default function DashboardKnowledgePage() {
label: t("knowledge.columnStatus"),
render: (item) => (
<Badge variant={item.status === Status.Ok ? "secondary" : "outline"}>
{item.statusName}
{getKnowledgeStatusLabel(item.status, t)}
</Badge>
),
},
@@ -131,7 +133,7 @@ export default function DashboardKnowledgePage() {
rowActions={[
{
key: "rebuild-index",
label: (item) => t("knowledge.rebuildIndex"),
label: () => t("knowledge.rebuildIndex"),
icon: <RefreshCwIcon />,
run: async ({ item }) => {
await rebuildKnowledgeBaseIndex(item.id)