feat: 知识库实体筛选、技能白名单收紧、嵌入主题与提问组件优化
- 知识库笔记列表支持按实体精确筛选(路由/服务/仓储层透传,关系图索引查询) - 自定义智能体技能白名单在 skill_view 与 skill_manage 的 list/view 别名上同样生效; config 未声明 skills 时视为无可见技能 - 嵌入圆桌主题解析默认深蓝,强制主题期间不再写 localStorage 覆盖主站选择 - 智能体提问建议拆出 agent-question-chips 组件,编辑器与详情面板联动优化 - 新增 qa-client 问答测试脚本 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
11bc442f10
commit
28098b3155
22 files changed
+500
-126
No files matched your search
@@ -8,6 +8,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AgentDetailPanel } from "@/components/workspace/agents/agent-detail-panel";
|
||||
import { AgentQuestionChips } from "@/components/workspace/agents/agent-question-chips";
|
||||
import { AgentWelcome } from "@/components/workspace/agent-welcome";
|
||||
import { ArtifactTrigger } from "@/components/workspace/artifacts";
|
||||
import { ChatBox, useThreadChat } from "@/components/workspace/chats";
|
||||
@@ -28,7 +29,6 @@ import { useThreadStream } from "@/core/threads/hooks";
|
||||
import { textOfMessage } from "@/core/threads/utils";
|
||||
import { env } from "@/env";
|
||||
import { desensitize } from "@/lib/desensitize";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function AgentChatPage() {
|
||||
const routeBase = useRouteBase();
|
||||
@@ -235,29 +235,12 @@ export default function AgentChatPage() {
|
||||
className="mb-7"
|
||||
/>
|
||||
<div className="w-full max-w-(--container-width-md)">
|
||||
{commonQuestions.length > 0 && (
|
||||
<div className="mb-2 flex max-h-20 flex-wrap items-start justify-start gap-2 overflow-y-auto px-1">
|
||||
{commonQuestions.map((question, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
disabled={isDemoMode}
|
||||
onClick={() =>
|
||||
handleSubmit({ text: question.prompt, files: [] })
|
||||
}
|
||||
title={desensitize(question.prompt)}
|
||||
className={cn(
|
||||
"border-border/70 bg-background/85 hover:bg-accent hover:border-border max-w-full rounded-full border px-3 py-1.5 text-left text-sm leading-5 shadow-sm transition-colors",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
)}
|
||||
>
|
||||
<span className="block max-w-[220px] truncate">
|
||||
{desensitize(question.title)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<AgentQuestionChips
|
||||
className="mb-3"
|
||||
questions={commonQuestions}
|
||||
disabled={isDemoMode}
|
||||
onAsk={(text) => handleSubmit({ text, files: [] })}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<InputBox
|
||||
className="w-full"
|
||||
|
||||
@@ -4,13 +4,9 @@ import { useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { loginByUsername } from "@/core/auth/api";
|
||||
import { setStoredAuth } from "@/core/auth";
|
||||
import {
|
||||
resolveEmbedChatParams,
|
||||
resolveEmbedTheme,
|
||||
} from "@/core/embed/params";
|
||||
import { resolveEmbedChatParams } from "@/core/embed/params";
|
||||
import { registerEmbedSession } from "@/core/embed/status-api";
|
||||
import { useThreadSettings } from "@/core/settings";
|
||||
import { useThreadStream } from "@/core/threads/hooks";
|
||||
@@ -29,7 +25,8 @@ const EMBED_ROUTE_BASE = "/embed/chats";
|
||||
export default function EmbedChatPage() {
|
||||
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
||||
const searchParams = useSearchParams();
|
||||
const { setTheme } = useTheme();
|
||||
// 主题不在这里处理 —— ?theme= 由 AppProviders 通过 forcedTheme 强制
|
||||
// (不落 localStorage),避免污染主站用户自己选的主题。
|
||||
const embedParams = resolveEmbedChatParams(searchParams.toString());
|
||||
const [authReady, setAuthReady] = useState(false);
|
||||
const [authError, setAuthError] = useState<string | null>(null);
|
||||
@@ -51,12 +48,6 @@ export default function EmbedChatPage() {
|
||||
const [settings] = useThreadSettings(threadId);
|
||||
const { tokenUsageEnabled } = useModels();
|
||||
|
||||
useEffect(() => {
|
||||
if (embedParams.theme) {
|
||||
setTheme(resolveEmbedTheme(embedParams.theme));
|
||||
}
|
||||
}, [embedParams.theme, setTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
async function runLogin() {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { loginByUsername } from "@/core/auth/api";
|
||||
import { setStoredAuth } from "@/core/auth";
|
||||
import { readWujieProps, isWujieEnv } from "@/core/embed/wujie";
|
||||
@@ -13,17 +12,6 @@ import "@/roundtable-planning/styles/roundtable-planning.css";
|
||||
|
||||
const DEFAULT_USERNAME = "guest";
|
||||
|
||||
/**
|
||||
* 把宿主传入的主题标识映射成 light/dark。**默认深蓝(dark)**:
|
||||
* 只有明确传 light/红白 才走浅色,其余(dark / dark-blue / 深蓝 / 空)一律深蓝。
|
||||
* 不复用 `resolveEmbedTheme`(它只认严格的 "dark-blue",传 "dark" 会被判成浅色)。
|
||||
*/
|
||||
function resolveEmbedRoundtableTheme(raw: string): "dark" | "light" {
|
||||
const v = raw.trim().toLowerCase();
|
||||
if (v === "light" || v === "red" || v === "红白") return "light";
|
||||
return "dark";
|
||||
}
|
||||
|
||||
/**
|
||||
* 圆桌规划嵌入页(独立入口,**与现有 wujie ticket 登录无关**)。
|
||||
*
|
||||
@@ -37,9 +25,10 @@ function resolveEmbedRoundtableTheme(raw: string): "dark" | "light" {
|
||||
*/
|
||||
export default function EmbedRoundtablePage() {
|
||||
const searchParams = useSearchParams();
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
// username / password / theme:wujie props 优先,URL query 兜底。
|
||||
// username / password:wujie props 优先,URL query 兜底。
|
||||
// 主题不在这里处理 —— 嵌入路由的主题由 AppProviders 通过 forcedTheme
|
||||
// 强制(不落 localStorage),避免污染主站用户自己选的主题。
|
||||
const wp = readWujieProps();
|
||||
const username =
|
||||
(typeof wp.username === "string" && wp.username.trim()) ||
|
||||
@@ -53,19 +42,10 @@ export default function EmbedRoundtablePage() {
|
||||
(typeof wp.password === "string" && wp.password) ||
|
||||
searchParams.get("password") ||
|
||||
undefined;
|
||||
// 主题色:宿主传入优先;默认「深蓝」= dark 主题(其主色为蓝,oklch hue 259)。
|
||||
const themeParam =
|
||||
(typeof wp.theme === "string" && wp.theme.trim()) ||
|
||||
searchParams.get("theme")?.trim() ||
|
||||
"dark";
|
||||
|
||||
const [authReady, setAuthReady] = useState(false);
|
||||
const [authError, setAuthError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setTheme(resolveEmbedRoundtableTheme(themeParam));
|
||||
}, [themeParam, setTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWujieEnv()) return;
|
||||
document.documentElement.classList.add("wujie-embed");
|
||||
|
||||
@@ -184,13 +184,11 @@ export default function NewAgentPage() {
|
||||
// Name is intentionally NOT required here so the user can start chatting
|
||||
// immediately. If they leave it blank, ensureAgent() generates a placeholder
|
||||
// and writes it back into the input so they can rename it later.
|
||||
if (enabledSkills.length > 0 && selectedSkills.length === 0) {
|
||||
setFormError("请至少选择一个技能");
|
||||
return null;
|
||||
}
|
||||
// Zero skills is a valid choice: the backend treats an empty/omitted skill
|
||||
// list as "no skills" for custom agents (never "all skills").
|
||||
setFormError("");
|
||||
return nameInput.trim();
|
||||
}, [enabledSkills.length, nameInput, selectedSkills.length]);
|
||||
}, [nameInput]);
|
||||
|
||||
const ensureAgent = useCallback(async () => {
|
||||
const provided = validateForm();
|
||||
|
||||
Reference in new issue
Block a user