feat: add fixed questions and improve authentication

This commit is contained in:
吉鑫升 committed 2026-08-21 17:58:39 +08:00
1 parent 8af4fed89d
commit 4c9f75f31e
42 files changed
+4008 -200

No files matched your search

@@ -10,6 +10,7 @@ import {
FolderOpen,
Gauge,
ListTree,
MessageSquareText,
MousePointerClick,
BriefcaseBusiness,
Settings,
@@ -237,6 +238,14 @@ function SecondaryNavSidebar({ headerHidden }: { headerHidden: boolean }) {
按钮管理
</DropdownMenuItem>
)}
{isAdmin && (
<DropdownMenuItem
onClick={() => navigate("/page/strategy/admin/fixed-questions")}
>
<MessageSquareText className="size-4" />
问题配置
</DropdownMenuItem>
)}
{isAdmin && (
<DropdownMenuItem
onClick={() => navigate("/page/strategy/admin/position-roles")}
@@ -558,6 +567,12 @@ function LegacyAccordionSidebar({
按钮管理
</DropdownMenuItem>
)}
{isAdmin && (
<DropdownMenuItem onClick={() => navigate("/page/strategy/admin/fixed-questions")}>
<MessageSquareText className="size-4" />
问题配置
</DropdownMenuItem>
)}
{isAdmin && (
<DropdownMenuItem onClick={() => navigate("/page/strategy/admin/position-roles")}>
<BriefcaseBusiness className="size-4" />
@@ -76,6 +76,7 @@ function SelectionContent({
error,
source,
variant,
readOnly,
}: {
rows: LlmWikiKnowledgeBase[];
value: string[];
@@ -86,6 +87,7 @@ function SelectionContent({
error: unknown;
source: SelectorSource;
variant: SelectorVariant;
readOnly: boolean;
}) {
const selected = useMemo(() => new Set(value), [value]);
const filtered = useMemo(() => {
@@ -97,6 +99,7 @@ function SelectionContent({
}, [rows, search]);
const groups = useMemo(() => groupRows(filtered), [filtered]);
const toggle = (id: string) => {
if (readOnly) return;
onChange(selected.has(id) ? value.filter((item) => item !== id) : [...value, id]);
};
@@ -168,6 +171,7 @@ function SelectionContent({
<DropdownMenuCheckboxItem
key={item.id}
checked={selected.has(item.id)}
disabled={readOnly}
onCheckedChange={() => toggle(item.id)}
onSelect={(event) => event.preventDefault()}
className={cn(
@@ -183,6 +187,7 @@ function SelectionContent({
type="button"
role="checkbox"
aria-checked={selected.has(item.id)}
disabled={readOnly}
onClick={() => toggle(item.id)}
className={cn(
"my-0.5 flex min-h-11 w-full items-center gap-2 rounded-md px-2 py-1.5 text-left outline-none transition-colors hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-primary",
@@ -205,15 +210,21 @@ function SelectionContent({
</div>
<div className="flex items-center justify-between gap-2 border-t bg-muted/20 px-2.5 py-2">
<span className="text-[11px] text-muted-foreground">已选择 {value.length} 个</span>
<div className="flex items-center gap-1">
<Button type="button" variant="ghost" size="sm" className="h-7 px-2 text-xs" onClick={() => onChange(filtered.map((item) => item.id))}>
全选
</Button>
<Button type="button" variant="ghost" size="sm" className="h-7 px-2 text-xs" onClick={() => onChange([])}>
清空
</Button>
</div>
<span className="text-[11px] text-muted-foreground">
{readOnly ? `本对话已选 ${value.length} 个` : `已选择 ${value.length} 个`}
</span>
{readOnly ? (
<span className="text-[11px] text-muted-foreground">对话开始后不可更改</span>
) : (
<div className="flex items-center gap-1">
<Button type="button" variant="ghost" size="sm" className="h-7 px-2 text-xs" onClick={() => onChange(filtered.map((item) => item.id))}>
全选
</Button>
<Button type="button" variant="ghost" size="sm" className="h-7 px-2 text-xs" onClick={() => onChange([])}>
清空
</Button>
</div>
)}
</div>
</>
);
@@ -226,6 +237,7 @@ export function LlmWikiKnowledgeSelector({
agentId,
autoSelectAll = false,
disabled = false,
readOnly = false,
className,
label = "知识空间",
variant = "popover",
@@ -236,6 +248,8 @@ export function LlmWikiKnowledgeSelector({
agentId?: string | null;
autoSelectAll?: boolean;
disabled?: boolean;
/** Keep the selected knowledge spaces visible without allowing the thread scope to change. */
readOnly?: boolean;
className?: string;
label?: string;
variant?: SelectorVariant;
@@ -293,6 +307,7 @@ export function LlmWikiKnowledgeSelector({
error={error}
source={source}
variant={variant}
readOnly={readOnly}
/>
);
@@ -315,9 +330,14 @@ export function LlmWikiKnowledgeSelector({
);
}
const selectedName = value.length === 1
? rows.find((item) => item.id === value[0])?.name
: null;
const selectedNames = value
.map((id) => rows.find((item) => item.id === id)?.name)
.filter((name): name is string => !!name);
const selectedLabel = selectedNames.length > 0
? selectedNames.join("、")
: value.length > 0
? `已选 ${value.length} 个知识空间`
: label;
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
@@ -332,9 +352,10 @@ export function LlmWikiKnowledgeSelector({
className,
)}
disabled={disabled || loading}
title={value.length > 0 ? selectedLabel : undefined}
>
{loading ? <Loader2 className="size-3.5 animate-spin" /> : <Database className="size-3.5" />}
<span className="truncate text-xs font-normal">{selectedName || label}</span>
<span className="truncate text-xs font-normal">{selectedLabel}</span>
{value.length > 0 && (
<span className="flex size-4 items-center justify-center rounded-full bg-primary text-[9px] font-semibold text-primary-foreground">
{value.length}
@@ -11,6 +11,7 @@ import {
GaugeIcon,
HistoryIcon,
MessageCircleQuestionIcon,
MessageSquareTextIcon,
ScrollTextIcon,
Settings2Icon,
SettingsIcon,
@@ -268,6 +269,16 @@ export function WorkspaceNavMenu() {
推荐问题配置
</DropdownMenuItem>
)}
{isAdmin && (
<DropdownMenuItem
onClick={() =>
navigate("/page/strategy/admin/fixed-questions")
}
>
<MessageSquareTextIcon />
问题配置
</DropdownMenuItem>
)}
{isAdmin && logSystem?.enabled && logSystem.url && (
<DropdownMenuItem
onClick={() =>