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:
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user