Merge branch 'master-nj' of http://192.168.0.200/knowledge-base-bj/deerflow-code into master-dev
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.a{fill:#fff;opacity:0;}.b{fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;}</style></defs><rect class="a" width="16" height="16"/><path class="b" d="M24.036,10l-.012,9.333" transform="translate(-16.016 -6.667)"/><path class="b" d="M10,24h9.333" transform="translate(-6.667 -16)"/></svg>
|
||||||
|
After Width: | Height: | Size: 401 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36" height="36" viewBox="0 0 36 36"><defs><style>.a,.d{fill:#fff;}.a{opacity:0;}.b{fill:#da2331;}.c{fill:#0a54cf;}.e{fill:#1677ff;}.f{fill:#1e2433;}.g{fill:#10d6ff;}.h{fill:#ff8181;}.i{fill:none;stroke:#10d6ff;stroke-linecap:round;stroke-linejoin:round;}.j{filter:url(#d);}.k{filter:url(#a);}</style><filter id="a"><feOffset dx="4" input="SourceAlpha"/><feGaussianBlur stdDeviation="1" result="b"/><feFlood flood-opacity="0.161" result="c"/><feComposite operator="out" in="SourceGraphic" in2="b"/><feComposite operator="in" in="c"/><feComposite operator="in" in2="SourceGraphic"/></filter><filter id="d"><feOffset dx="-4" input="SourceAlpha"/><feGaussianBlur stdDeviation="1" result="e"/><feFlood flood-opacity="0.161" result="f"/><feComposite operator="out" in="SourceGraphic" in2="e"/><feComposite operator="in" in="f"/><feComposite operator="in" in2="SourceGraphic"/></filter></defs><g transform="translate(0 -1)"><rect class="a" width="36" height="36" transform="translate(0 1)"/><path class="b" d="M23.5,14.75a1.5,1.5,0,0,1-1.5-1.5V9.5a1.5,1.5,0,1,1,3,0v3.75A1.5,1.5,0,0,1,23.5,14.75Z" transform="translate(-5.5 -1)"/><g data-type="innerShadowGroup"><path class="c" d="M-2-2H1C3.206-2,5-.532,5,1.273V3.727C5,5.532,3.206,7,1,7H-2Z" transform="translate(28.66 19.541)"/><g class="k" transform="matrix(1, 0, 0, 1, 0, 1)"><path class="d" d="M-2-2H1C3.206-2,5-.532,5,1.273V3.727C5,5.532,3.206,7,1,7H-2Z" transform="translate(28.66 18.54)"/></g></g><g data-type="innerShadowGroup"><path class="c" d="M5-2H2C-.206-2-2-.532-2,1.273V3.727C-2,5.532-.206,7,2,7H5Z" transform="translate(4.34 19.541)"/><g class="j" transform="matrix(1, 0, 0, 1, 0, 1)"><path class="d" d="M5-2H2C-.206-2-2-.532-2,1.273V3.727C-2,5.532-.206,7,2,7H5Z" transform="translate(4.34 18.54)"/></g></g><path class="e" d="M5.5-2H16a7.508,7.508,0,0,1,7.5,7.5V13A7.508,7.508,0,0,1,16,20.5H5.5A7.508,7.508,0,0,1-2,13V5.5A7.508,7.508,0,0,1,5.5-2Z" transform="translate(7.25 13.5)"/><path class="f" d="M5.25,0h9A5.25,5.25,0,0,1,19.5,5.25V7.5a5.25,5.25,0,0,1-5.25,5.25h-9A5.25,5.25,0,0,1,0,7.5V5.25A5.25,5.25,0,0,1,5.25,0Z" transform="translate(8.25 15.25)"/><circle class="g" cx="1.5" cy="1.5" r="1.5" transform="translate(21 19)"/><circle class="g" cx="1.5" cy="1.5" r="1.5" transform="translate(12 19)"/><path class="d" d="M24,10v3.75" transform="translate(-6 -1.5)"/><circle class="h" cx="3" cy="3" r="3" transform="translate(15 4)"/><path class="i" d="M44,28.667a3.367,3.367,0,0,0,1.922.412,2.841,2.841,0,0,0,1.828-.412" transform="translate(-27.875 -5.017)"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96"><defs><style>.a{fill:#fff;opacity:0;}.b,.c{fill:none;stroke:#006efe;stroke-width:6px;}.b{stroke-linecap:round;stroke-linejoin:round;}</style></defs><g transform="translate(0 1)"><rect class="a" width="96" height="96" transform="translate(0 -1)"/><path class="b" d="M47,67H38L18,77V67H4V7H84V29" transform="translate(4 8)"/><circle class="c" cx="12" cy="12" r="12" transform="translate(56 45)"/><path class="b" d="M39,32l10,8" transform="translate(39 33)"/><path class="b" d="M12,15H24" transform="translate(12 16)"/><path class="b" d="M12,21H36" transform="translate(12 22)"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 670 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="11" viewBox="0 0 13 11"><defs><style>.a{stroke:rgba(0,0,0,0);stroke-miterlimit:10;fill:url(#a);}</style><linearGradient id="a" x1="0.277" y1="0.03" x2="0.725" y2="1.083" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#92eeff"/><stop offset="1" stop-color="#00c7ff"/></linearGradient></defs><path class="a" d="M1,10A1,1,0,1,1,1,8H6a1,1,0,0,1,0,2Zm9.8-.482L7.848,7.361A.445.445,0,0,1,7.661,7a.46.46,0,0,1,.187-.366L10.8,4.481a.45.45,0,0,1,.716.365V9.152a.44.44,0,0,1-.131.317.445.445,0,0,1-.322.136A.454.454,0,0,1,10.8,9.518ZM1,6A1,1,0,1,1,1,4H6A1,1,0,0,1,6,6ZM1,2A1,1,0,1,1,1,0H11a1,1,0,1,1,0,2Z" transform="translate(0.5 0.5)"/></svg>
|
||||||
|
After Width: | Height: | Size: 753 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="11" viewBox="0 0 13 11"><defs><style>.a{fill:url(#a);}.b{fill:url(#b);}</style><linearGradient id="a" x1="0.023" x2="1" y2="1" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#92eeff" stop-opacity="0.302"/><stop offset="1" stop-color="#00c7ff" stop-opacity="0.302"/></linearGradient><linearGradient id="b" x1="0.023" x2="1" y2="1" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#92eeff"/><stop offset="1" stop-color="#00c7ff"/></linearGradient></defs><path class="a" d="M5.5,0,11,8H0Z" transform="translate(13) rotate(90)"/><path class="b" d="M5.5,0,11,8H0Z" transform="translate(8) rotate(90)"/></svg>
|
||||||
|
After Width: | Height: | Size: 735 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.a{fill:#fff;opacity:0;}.b{fill:rgba(255,255,255,0.8);}</style></defs><rect class="a" width="16" height="16"/><path class="b" d="M10,1.5A1.5,1.5,0,1,1,11.5,3,1.5,1.5,0,0,1,10,1.5Zm-5,0A1.5,1.5,0,1,1,6.5,3,1.5,1.5,0,0,1,5,1.5Zm-5,0A1.5,1.5,0,1,1,1.5,3,1.5,1.5,0,0,1,0,1.5Z" transform="translate(1.498 6.5)"/></svg>
|
||||||
|
After Width: | Height: | Size: 410 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.a{fill:#fff;opacity:0;}.b{fill:rgba(85,166,254,0.6);}</style></defs><rect class="a" width="16" height="16"/><path class="b" d="M12.715,13.421,9.588,10.294a5.841,5.841,0,1,1,.707-.707l3.128,3.128a.5.5,0,0,1-.708.706ZM1,5.833A4.833,4.833,0,1,0,5.834,1,4.838,4.838,0,0,0,1,5.833Z" transform="translate(1.334 1.216)"/></svg>
|
||||||
|
After Width: | Height: | Size: 418 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.a{fill:#fff;}</style></defs><path class="a" d="M14.488.053.824,4.256a1.167,1.167,0,0,0-.285,2.1L4.576,8.936a1.167,1.167,0,0,0,1.382-.092L11,4.593A.292.292,0,0,1,11.408,5L7.156,10.043a1.167,1.167,0,0,0-.091,1.382l2.58,4.036a1.168,1.168,0,0,0,2.1-.286l4.2-13.663A1.167,1.167,0,0,0,14.488.053Z" transform="translate(0)"/></svg>
|
||||||
|
After Width: | Height: | Size: 422 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16.131" height="16" viewBox="0 0 16.131 16"><defs><style>.a{fill:#fff;opacity:0;}.b{fill:none;stroke:#97edff;stroke-linecap:round;stroke-linejoin:round;}</style></defs><rect class="a" width="16" height="16"/><path class="b" d="M6,30V34.12H18.361V30" transform="translate(-2.73 -19.994)"/><path class="b" d="M21.181,9.09,18.09,6,15,9.09" transform="translate(-8.64 -4.235)"/><path class="b" d="M23.992,14.241V6" transform="translate(-14.544 -4.235)"/></svg>
|
||||||
|
After Width: | Height: | Size: 504 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.a{fill:#fff;opacity:0;}.b,.c{fill:none;stroke:#346fb1;stroke-linejoin:round;}.c{stroke-linecap:round;}</style></defs><rect class="a" width="16" height="16"/><path class="b" d="M10.667,17.333A6.667,6.667,0,1,0,4,10.667,6.667,6.667,0,0,0,10.667,17.333Z" transform="translate(-2.667 -2.667)"/><path class="c" d="M24.008,12v4l2.826,2.826" transform="translate(-16.005 -8)"/></svg>
|
||||||
|
After Width: | Height: | Size: 473 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="95.001" height="26" viewBox="0 0 95.001 26"><defs><style>.a{fill:rgba(212,120,0,0.5);}</style></defs><path class="a" d="M250.146,407.92h91l-11.4,18.235c-2.679,4.26-5.243,7.5-11.211,7.765H246.146v-22A4,4,0,0,1,250.146,407.92Z" transform="translate(-246.146 -407.92)"/></svg>
|
||||||
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="95.001" height="26" viewBox="0 0 95.001 26"><defs><style>.a{fill:rgba(207,19,34,0.5);}</style></defs><path class="a" d="M250.146,407.92h91l-11.4,18.235c-2.679,4.26-5.243,7.5-11.211,7.765H246.146v-22A4,4,0,0,1,250.146,407.92Z" transform="translate(-246.146 -407.92)"/></svg>
|
||||||
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="95.001" height="26" viewBox="0 0 95.001 26"><defs><style>.a{fill:rgba(29,171,123,0.5);}</style></defs><path class="a" d="M250.146,407.92h91l-11.4,18.235c-2.679,4.26-5.243,7.5-11.211,7.765H246.146v-22A4,4,0,0,1,250.146,407.92Z" transform="translate(-246.146 -407.92)"/></svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="95.001" height="26" viewBox="0 0 95.001 26"><defs><style>.a{fill:rgba(132,85,230,0.5);}</style></defs><path class="a" d="M250.146,407.92h91l-11.4,18.235c-2.679,4.26-5.243,7.5-11.211,7.765H246.146v-22A4,4,0,0,1,250.146,407.92Z" transform="translate(-246.146 -407.92)"/></svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="95.001" height="26" viewBox="0 0 95.001 26"><defs><style>.a{fill:rgba(201,181,0,0.5);}</style></defs><path class="a" d="M250.146,407.92h91l-11.4,18.235c-2.679,4.26-5.243,7.5-11.211,7.765H246.146v-22A4,4,0,0,1,250.146,407.92Z" transform="translate(-246.146 -407.92)"/></svg>
|
||||||
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="95.001" height="26" viewBox="0 0 95.001 26"><defs><style>.a{fill:rgba(69,195,243,0.5);}</style></defs><path class="a" d="M250.146,407.92h91l-11.4,18.235c-2.679,4.26-5.243,7.5-11.211,7.765H246.146v-22A4,4,0,0,1,250.146,407.92Z" transform="translate(-246.146 -407.92)"/></svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.a{fill:#fff;opacity:0;}.b{fill:none;stroke:#346fb1;stroke-linecap:round;stroke-linejoin:round;}</style></defs><g transform="translate(0 -2)"><rect class="a" width="16" height="16" transform="translate(0 2)"/><path class="b" d="M17.333,8H4V18H9l1.667,1.667L12.333,18h5Z" transform="translate(-2.667 -3.667)"/><circle class="b" cx="1.667" cy="1.667" r="1.667" transform="translate(6.333 6.333)"/><path class="b" d="M21,26.667A2.848,2.848,0,0,0,18,24a2.848,2.848,0,0,0-3,2.667" transform="translate(-10 -14.333)"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 618 B |
@@ -100,15 +100,17 @@ function Section({
|
|||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
extra,
|
extra,
|
||||||
|
className,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
extra?: ReactNode;
|
extra?: ReactNode;
|
||||||
|
className?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section className="bg-card/60 rounded-xl border p-4">
|
<section className={cn("bg-card/60 rounded-xl border p-4", className)}>
|
||||||
<div className="mb-3 flex items-center justify-between gap-2">
|
<div className="mb-3 flex items-center justify-between gap-2">
|
||||||
<h4 className="text-foreground flex items-center gap-1.5 text-sm font-semibold">
|
<h4 className="text-foreground flex items-center gap-1.5 text-sm font-semibold">
|
||||||
{icon}
|
{icon}
|
||||||
@@ -139,6 +141,15 @@ export function AgentDetailPanel({
|
|||||||
defaultCollapsed,
|
defaultCollapsed,
|
||||||
showRecentChats,
|
showRecentChats,
|
||||||
compactAppearanceActions,
|
compactAppearanceActions,
|
||||||
|
avatarSrc,
|
||||||
|
positionName,
|
||||||
|
skillsIcon,
|
||||||
|
skillsTitle,
|
||||||
|
showCommonQuestions = false,
|
||||||
|
panelTitle,
|
||||||
|
embedded,
|
||||||
|
hideStatusBadges = false,
|
||||||
|
editIcon,
|
||||||
}: {
|
}: {
|
||||||
agent: Agent | null | undefined;
|
agent: Agent | null | undefined;
|
||||||
agentName: string;
|
agentName: string;
|
||||||
@@ -150,6 +161,19 @@ export function AgentDetailPanel({
|
|||||||
showRecentChats?: boolean;
|
showRecentChats?: boolean;
|
||||||
/** 简洁模式下挂在右上角收起/展开按钮左侧的外观控件。 */
|
/** 简洁模式下挂在右上角收起/展开按钮左侧的外观控件。 */
|
||||||
compactAppearanceActions?: ReactNode;
|
compactAppearanceActions?: ReactNode;
|
||||||
|
avatarSrc?: string;
|
||||||
|
positionName?: string;
|
||||||
|
skillsIcon?: ReactNode;
|
||||||
|
skillsTitle?: string;
|
||||||
|
showCommonQuestions?: boolean;
|
||||||
|
/** Overrides the standard panel heading for an embedded consumer. */
|
||||||
|
panelTitle?: string;
|
||||||
|
/** Renders one fixed panel instead of the responsive collapsible rail. */
|
||||||
|
embedded?: boolean;
|
||||||
|
/** Keeps deployment/status badges out of an embedded product surface. */
|
||||||
|
hideStatusBadges?: boolean;
|
||||||
|
/** Optional icon used by the compact embedded edit action. */
|
||||||
|
editIcon?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [collapsed, setCollapsed] = useState(defaultCollapsed ?? false);
|
const [collapsed, setCollapsed] = useState(defaultCollapsed ?? false);
|
||||||
@@ -472,11 +496,25 @@ export function AgentDetailPanel({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Button size="sm" variant="outline" onClick={beginEdit}>
|
{embedded ? (
|
||||||
<SquarePenIcon className="size-3.5" />
|
<Tooltip content="编辑智能体">
|
||||||
编辑
|
<Button
|
||||||
</Button>
|
type="button"
|
||||||
{canTogglePublish && (
|
size="icon-sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={beginEdit}
|
||||||
|
aria-label="编辑智能体"
|
||||||
|
>
|
||||||
|
{editIcon ?? <SquarePenIcon className="size-3.5" />}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Button size="sm" variant="outline" onClick={beginEdit}>
|
||||||
|
<SquarePenIcon className="size-3.5" />
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!embedded && canTogglePublish && (
|
||||||
<Tooltip content={agent?.published ? "下架" : "发布"}>
|
<Tooltip content={agent?.published ? "下架" : "发布"}>
|
||||||
<Button
|
<Button
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
@@ -493,17 +531,19 @@ export function AgentDetailPanel({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip content="删除智能体">
|
{!embedded && (
|
||||||
<Button
|
<Tooltip content="删除智能体">
|
||||||
size="icon-sm"
|
<Button
|
||||||
variant="ghost"
|
size="icon-sm"
|
||||||
className="text-destructive hover:text-destructive"
|
variant="ghost"
|
||||||
onClick={() => setDeleteOpen(true)}
|
className="text-destructive hover:text-destructive"
|
||||||
aria-label="删除智能体"
|
onClick={() => setDeleteOpen(true)}
|
||||||
>
|
aria-label="删除智能体"
|
||||||
<Trash2Icon className="size-3.5" />
|
>
|
||||||
</Button>
|
<Trash2Icon className="size-3.5" />
|
||||||
</Tooltip>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
) : null;
|
) : null;
|
||||||
@@ -521,7 +561,11 @@ export function AgentDetailPanel({
|
|||||||
<section className="bg-card/60 rounded-xl border p-4">
|
<section className="bg-card/60 rounded-xl border p-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="agent-avatar bg-primary/10 ring-primary/15 flex size-12 shrink-0 items-center justify-center rounded-2xl ring-1">
|
<div className="agent-avatar bg-primary/10 ring-primary/15 flex size-12 shrink-0 items-center justify-center rounded-2xl ring-1">
|
||||||
<BotIcon className="text-primary size-6" />
|
{avatarSrc ? (
|
||||||
|
<img src={avatarSrc} alt="" aria-hidden="true" className="size-9" />
|
||||||
|
) : (
|
||||||
|
<BotIcon className="text-primary size-6" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1 space-y-1.5">
|
<div className="min-w-0 flex-1 space-y-1.5">
|
||||||
{editing && draft ? (
|
{editing && draft ? (
|
||||||
@@ -541,9 +585,16 @@ export function AgentDetailPanel({
|
|||||||
{displayName}
|
{displayName}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-wrap items-center gap-1">
|
{!editing && positionName && (
|
||||||
{statusBadges}
|
<div className="position-agent-role text-sm">
|
||||||
</div>
|
岗位:{desensitize(positionName)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!hideStatusBadges && (
|
||||||
|
<div className="flex flex-wrap items-center gap-1">
|
||||||
|
{statusBadges}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!editing && agent.owner_username && !agent.builtin && (
|
{!editing && agent.owner_username && !agent.builtin && (
|
||||||
<div className="text-muted-foreground flex items-center gap-1 text-xs">
|
<div className="text-muted-foreground flex items-center gap-1 text-xs">
|
||||||
<UserRoundIcon className="size-3 shrink-0" />
|
<UserRoundIcon className="size-3 shrink-0" />
|
||||||
@@ -646,7 +697,7 @@ export function AgentDetailPanel({
|
|||||||
|
|
||||||
|
|
||||||
{/* ── 常用问题 ───────────────────────────────────────────────────── */}
|
{/* ── 常用问题 ───────────────────────────────────────────────────── */}
|
||||||
{(editing || questions.length > 0 || canEdit) && (
|
{(showCommonQuestions || editing || questions.length > 0 || canEdit) && (
|
||||||
<Section
|
<Section
|
||||||
icon={<MessageCircleQuestionIcon className="size-3.5" />}
|
icon={<MessageCircleQuestionIcon className="size-3.5" />}
|
||||||
title="常用问题"
|
title="常用问题"
|
||||||
@@ -787,8 +838,9 @@ export function AgentDetailPanel({
|
|||||||
|
|
||||||
{/* ── 技能管理 ───────────────────────────────────────────────────── */}
|
{/* ── 技能管理 ───────────────────────────────────────────────────── */}
|
||||||
<Section
|
<Section
|
||||||
icon={<SparklesIcon className="size-3.5" />}
|
icon={skillsIcon ?? <SparklesIcon className="size-3.5" />}
|
||||||
title={`已启用技能 ${skillNamesShown.length} 个`}
|
title={skillsTitle ?? `已启用技能 ${skillNamesShown.length} 个`}
|
||||||
|
className="position-roundtable-skills-section"
|
||||||
extra={
|
extra={
|
||||||
editing ? (
|
editing ? (
|
||||||
<Button
|
<Button
|
||||||
@@ -830,7 +882,9 @@ export function AgentDetailPanel({
|
|||||||
const panelChrome = (collapseButton: ReactNode) => (
|
const panelChrome = (collapseButton: ReactNode) => (
|
||||||
<>
|
<>
|
||||||
<div className="flex h-12 shrink-0 items-center justify-between border-b px-4">
|
<div className="flex h-12 shrink-0 items-center justify-between border-b px-4">
|
||||||
<span className="shrink-0 whitespace-nowrap text-sm font-semibold">智能体信息</span>
|
<span className="shrink-0 whitespace-nowrap text-sm font-semibold">
|
||||||
|
{panelTitle ?? "智能体信息"}
|
||||||
|
</span>
|
||||||
<div className="flex min-w-0 items-center gap-1">
|
<div className="flex min-w-0 items-center gap-1">
|
||||||
{headerActions}
|
{headerActions}
|
||||||
{compactAppearanceActions}
|
{compactAppearanceActions}
|
||||||
@@ -844,7 +898,11 @@ export function AgentDetailPanel({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* ── Desktop (lg+): inline aside, collapsible to a rail ───────────── */}
|
{/* ── Desktop (lg+): inline aside, collapsible to a rail ───────────── */}
|
||||||
{collapsed ? (
|
{embedded ? (
|
||||||
|
<aside className="bg-card/40 flex h-full w-full min-w-0 shrink-0 flex-col border-l">
|
||||||
|
{panelChrome(null)}
|
||||||
|
</aside>
|
||||||
|
) : collapsed ? (
|
||||||
<div className="hidden shrink-0 items-start border-l px-2 pt-3 lg:flex">
|
<div className="hidden shrink-0 items-start border-l px-2 pt-3 lg:flex">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{compactAppearanceActions}
|
{compactAppearanceActions}
|
||||||
@@ -877,30 +935,34 @@ export function AgentDetailPanel({
|
|||||||
</aside>
|
</aside>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ── Narrow screens (<lg): rail trigger + drawer with the same body ── */}
|
{!embedded && (
|
||||||
<div className="flex shrink-0 flex-col items-center border-l pt-3 lg:hidden">
|
<>
|
||||||
<Tooltip content="智能体信息">
|
{/* ── Narrow screens (<lg): rail trigger + drawer with the same body ── */}
|
||||||
<Button
|
<div className="flex shrink-0 flex-col items-center border-l pt-3 lg:hidden">
|
||||||
variant="ghost"
|
<Tooltip content="智能体信息">
|
||||||
size="icon-sm"
|
<Button
|
||||||
onClick={() => setMobileOpen(true)}
|
variant="ghost"
|
||||||
aria-label="打开智能体信息"
|
size="icon-sm"
|
||||||
>
|
onClick={() => setMobileOpen(true)}
|
||||||
<InfoIcon className="size-4" />
|
aria-label="打开智能体信息"
|
||||||
</Button>
|
>
|
||||||
</Tooltip>
|
<InfoIcon className="size-4" />
|
||||||
</div>
|
</Button>
|
||||||
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
|
</Tooltip>
|
||||||
<SheetContent side="right" className="flex w-full flex-col gap-0 p-0 sm:max-w-md">
|
</div>
|
||||||
<SheetHeader className="border-b px-4 pb-3 pt-4">
|
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
|
||||||
<SheetTitle className="flex items-center justify-between text-sm">
|
<SheetContent side="right" className="flex w-full flex-col gap-0 p-0 sm:max-w-md">
|
||||||
智能体信息
|
<SheetHeader className="border-b px-4 pb-3 pt-4">
|
||||||
<span className="mr-6">{headerActions}</span>
|
<SheetTitle className="flex items-center justify-between text-sm">
|
||||||
</SheetTitle>
|
智能体信息
|
||||||
</SheetHeader>
|
<span className="mr-6">{headerActions}</span>
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto">{body}</div>
|
</SheetTitle>
|
||||||
</SheetContent>
|
</SheetHeader>
|
||||||
</Sheet>
|
<div className="min-h-0 flex-1 overflow-y-auto">{body}</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ── Secondary surfaces ───────────────────────────────────────────── */}
|
{/* ── Secondary surfaces ───────────────────────────────────────────── */}
|
||||||
<SkillPickerSheet
|
<SkillPickerSheet
|
||||||
|
|||||||
@@ -1,10 +1,30 @@
|
|||||||
import { useEffect, useRef, type ComponentProps } from "react";
|
import { useEffect, useMemo, useRef, type ComponentProps } from "react";
|
||||||
|
|
||||||
import { InputBox } from "@/components/workspace/input-box";
|
import {
|
||||||
import { usePromptInputController } from "@/components/ai-elements/prompt-input";
|
usePromptInputController,
|
||||||
import { cn } from "@/lib/utils";
|
type PromptInputMessage,
|
||||||
|
} from "@/components/ai-elements/prompt-input";
|
||||||
|
import type { InputBox } from "@/components/workspace/input-box";
|
||||||
|
import { useModels } from "@/core/models/hooks";
|
||||||
|
import type { AgentQuestion } from "@/core/agents";
|
||||||
|
import { PositionIntentInput } from "@/position-roundtable/components/PositionIntentInput";
|
||||||
|
|
||||||
type PositionAgentInputProps = ComponentProps<typeof InputBox> & {
|
type InputBoxProps = ComponentProps<typeof InputBox>;
|
||||||
|
type PositionAgentInputProps = Pick<
|
||||||
|
InputBoxProps,
|
||||||
|
| "className"
|
||||||
|
| "assistantId"
|
||||||
|
| "status"
|
||||||
|
| "disabled"
|
||||||
|
| "context"
|
||||||
|
| "onContextChange"
|
||||||
|
| "onSubmit"
|
||||||
|
| "onStop"
|
||||||
|
| "isNewThread"
|
||||||
|
| "threadId"
|
||||||
|
| "hideAdminRecommendedQuestions"
|
||||||
|
> & {
|
||||||
|
commonQuestions?: (string | AgentQuestion)[] | null;
|
||||||
/** Stable node/builtin key used to restore the matching SQL-backed draft. */
|
/** Stable node/builtin key used to restore the matching SQL-backed draft. */
|
||||||
draftKey?: string;
|
draftKey?: string;
|
||||||
draftValue?: string;
|
draftValue?: string;
|
||||||
@@ -18,18 +38,36 @@ type PositionAgentInputProps = ComponentProps<typeof InputBox> & {
|
|||||||
* 只改这个组件即可,不会影响普通对话、工作区或其他业务页面。
|
* 只改这个组件即可,不会影响普通对话、工作区或其他业务页面。
|
||||||
*/
|
*/
|
||||||
export function PositionAgentInput({
|
export function PositionAgentInput({
|
||||||
className,
|
context,
|
||||||
|
disabled = false,
|
||||||
|
status = "ready",
|
||||||
|
onContextChange,
|
||||||
|
onSubmit,
|
||||||
|
onStop,
|
||||||
|
commonQuestions,
|
||||||
draftKey = "",
|
draftKey = "",
|
||||||
draftValue = "",
|
draftValue = "",
|
||||||
onDraftChange,
|
onDraftChange,
|
||||||
...props
|
|
||||||
}: PositionAgentInputProps) {
|
}: PositionAgentInputProps) {
|
||||||
const { textInput } = usePromptInputController();
|
const { textInput, attachments } = usePromptInputController();
|
||||||
|
const { models } = useModels();
|
||||||
const activeDraftKeyRef = useRef("");
|
const activeDraftKeyRef = useRef("");
|
||||||
const restoringValueRef = useRef<string | null>(null);
|
const restoringValueRef = useRef<string | null>(null);
|
||||||
const textInputRef = useRef(textInput);
|
const textInputRef = useRef(textInput);
|
||||||
textInputRef.current = textInput;
|
textInputRef.current = textInput;
|
||||||
|
|
||||||
|
const modelOptions = useMemo(
|
||||||
|
() => models.map((model) => ({ label: model.display_name ?? model.name, value: model.name })),
|
||||||
|
[models],
|
||||||
|
);
|
||||||
|
const attachedFiles = useMemo(
|
||||||
|
() =>
|
||||||
|
attachments.files
|
||||||
|
.map((attachment) => attachment.file)
|
||||||
|
.filter((file): file is File => file instanceof File),
|
||||||
|
[attachments.files],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
activeDraftKeyRef.current = draftKey;
|
activeDraftKeyRef.current = draftKey;
|
||||||
if (textInputRef.current.value !== draftValue) {
|
if (textInputRef.current.value !== draftValue) {
|
||||||
@@ -51,13 +89,60 @@ export function PositionAgentInput({
|
|||||||
onDraftChange?.(textInput.value);
|
onDraftChange?.(textInput.value);
|
||||||
}, [draftKey, onDraftChange, textInput.value]);
|
}, [draftKey, onDraftChange, textInput.value]);
|
||||||
|
|
||||||
// Keep the page-specific class on the wrapper rather than adding a variant
|
const handleSend = () => {
|
||||||
// to InputBox. Node chat and built-in chat can evolve together while every
|
if (status === "streaming") {
|
||||||
// other workspace keeps the global composer untouched.
|
onStop?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = textInput.value.trim();
|
||||||
|
if (!text && attachedFiles.length === 0) return;
|
||||||
|
const message: PromptInputMessage = {
|
||||||
|
text: textInput.value,
|
||||||
|
files: attachments.files,
|
||||||
|
};
|
||||||
|
void onSubmit?.(message);
|
||||||
|
textInput.clear();
|
||||||
|
attachments.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModelChange = (modelName: string) => {
|
||||||
|
const model = models.find((candidate) => candidate.name === modelName);
|
||||||
|
if (!model) return;
|
||||||
|
onContextChange?.({ ...context, model_name: model.name });
|
||||||
|
};
|
||||||
|
const selectedModel =
|
||||||
|
typeof context.model_name === "string"
|
||||||
|
? context.model_name
|
||||||
|
: modelOptions[0]?.value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputBox
|
<PositionIntentInput
|
||||||
{...props}
|
value={textInput.value}
|
||||||
className={cn("position-roundtable-agent-input w-full", className)}
|
onValueChange={textInput.setInput}
|
||||||
|
attachedFiles={attachedFiles}
|
||||||
|
onAddFiles={attachments.add}
|
||||||
|
onRemoveFile={(index) => {
|
||||||
|
const attachment = attachments.files[index];
|
||||||
|
if (attachment) attachments.remove(attachment.id);
|
||||||
|
}}
|
||||||
|
charactersLeft={textInput.value.length}
|
||||||
|
isSending={status === "streaming"}
|
||||||
|
canSend={!disabled && (Boolean(textInput.value.trim()) || attachedFiles.length > 0)}
|
||||||
|
pendingClarification={false}
|
||||||
|
onSend={handleSend}
|
||||||
|
onStop={() => onStop?.()}
|
||||||
|
onFollowUp={() => undefined}
|
||||||
|
onSkip={() => undefined}
|
||||||
|
onEnterRoundtable={() => undefined}
|
||||||
|
enterButtonLabel=""
|
||||||
|
intentReady={null}
|
||||||
|
hideEnterRoundtable
|
||||||
|
onManualSave={() => undefined}
|
||||||
|
onTriggerToast={() => undefined}
|
||||||
|
modelOptions={modelOptions}
|
||||||
|
selectedModel={selectedModel}
|
||||||
|
onSelectedModelChange={handleModelChange}
|
||||||
|
commonQuestions={commonQuestions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -716,7 +716,7 @@ export function PositionBuiltinAgentChat({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="position-roundtable-agent-composer-bar shrink-0 border-t border-slate-100 px-4 py-3 dark:border-[#2b8ffe]/20">
|
<div className="position-roundtable-agent-composer-bar position-roundtable-node-composer shrink-0">
|
||||||
<PositionAgentInput
|
<PositionAgentInput
|
||||||
className="w-full"
|
className="w-full"
|
||||||
isNewThread={!boundThreadId}
|
isNewThread={!boundThreadId}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { useRef } from "react";
|
import { useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
FileText,
|
FileText,
|
||||||
Loader2,
|
|
||||||
Paperclip,
|
|
||||||
Pause,
|
Pause,
|
||||||
Repeat,
|
Repeat,
|
||||||
Save,
|
Save,
|
||||||
Send,
|
|
||||||
Smile,
|
Smile,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
X,
|
X,
|
||||||
@@ -14,7 +11,11 @@ import {
|
|||||||
import { Select as TSelect } from "tdesign-react";
|
import { Select as TSelect } from "tdesign-react";
|
||||||
|
|
||||||
import { AutoGrowTextarea } from "@/components/ui/auto-grow-textarea";
|
import { AutoGrowTextarea } from "@/components/ui/auto-grow-textarea";
|
||||||
|
import { normalizeAgentQuestions, type AgentQuestion } from "@/core/agents";
|
||||||
import type { Step1InputAreaProps } from "@/roundtable-planning/components/Step1Panel";
|
import type { Step1InputAreaProps } from "@/roundtable-planning/components/Step1Panel";
|
||||||
|
import moreTaskIcon from "@/assets/position-roundtable/more-task.svg";
|
||||||
|
import uploadFileIcon from "@/assets/position-roundtable/upload-file.svg";
|
||||||
|
import sendMessageIcon from "@/assets/position-roundtable/send-message.svg";
|
||||||
|
|
||||||
interface PositionIntentModelOption {
|
interface PositionIntentModelOption {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -25,6 +26,7 @@ interface PositionIntentInputProps extends Step1InputAreaProps {
|
|||||||
modelOptions?: PositionIntentModelOption[];
|
modelOptions?: PositionIntentModelOption[];
|
||||||
selectedModel?: string;
|
selectedModel?: string;
|
||||||
onSelectedModelChange?: (model: string) => void;
|
onSelectedModelChange?: (model: string) => void;
|
||||||
|
commonQuestions?: (string | AgentQuestion)[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Keep attachment metadata presentation local to this position-only composer. */
|
/** Keep attachment metadata presentation local to this position-only composer. */
|
||||||
@@ -64,40 +66,25 @@ export function PositionIntentInput({
|
|||||||
modelOptions = [],
|
modelOptions = [],
|
||||||
selectedModel,
|
selectedModel,
|
||||||
onSelectedModelChange,
|
onSelectedModelChange,
|
||||||
|
commonQuestions,
|
||||||
}: PositionIntentInputProps) {
|
}: PositionIntentInputProps) {
|
||||||
// Files are owned by Step1Panel. This ref only opens the native picker and
|
// Files are owned by Step1Panel. This ref only opens the native picker and
|
||||||
// forwards its selection through onAddFiles, so replacement layouts do not
|
// forwards its selection through onAddFiles, so replacement layouts do not
|
||||||
// accidentally create a second attachment state.
|
// accidentally create a second attachment state.
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [showMoreQuestions, setShowMoreQuestions] = useState(false);
|
||||||
|
const normalizedCommonQuestions = normalizeAgentQuestions(commonQuestions);
|
||||||
|
const visibleCommonQuestions = normalizedCommonQuestions.slice(0, 2);
|
||||||
|
const extraCommonQuestions = normalizedCommonQuestions.slice(2);
|
||||||
|
|
||||||
|
const pickCommonQuestion = (question: AgentQuestion) => {
|
||||||
|
onValueChange(question.prompt);
|
||||||
|
setShowMoreQuestions(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="position-roundtable-intent-input position-roundtable-intent-composer shrink-0 border-t border-slate-200 bg-[#f7faff] px-4 py-3 dark:border-[#2b8ffe]/35 dark:bg-[#071a38]">
|
<div className="position-roundtable-intent-input position-roundtable-intent-composer shrink-0 border-t border-slate-200 bg-[#f7faff] px-4 py-3 dark:border-[#2b8ffe]/35 dark:bg-[#071a38]">
|
||||||
<div className="position-roundtable-intent-composer-frame relative overflow-hidden rounded-[4px] border border-slate-300 bg-white shadow-[0_2px_10px_rgba(15,23,42,0.08)] transition-all focus-within:border-primary focus-within:ring-2 focus-within:ring-[var(--brand-200)]/45 dark:border-[#2b8ffe]/55 dark:bg-[#0d2855] dark:shadow-none dark:focus-within:border-[#56baff] dark:focus-within:ring-[#2b8ffe]/25">
|
<div className="position-roundtable-intent-composer-frame relative overflow-hidden rounded-[4px] border border-slate-300 bg-white shadow-[0_2px_10px_rgba(15,23,42,0.08)] transition-all focus-within:border-primary focus-within:ring-2 focus-within:ring-[var(--brand-200)]/45 dark:border-[#2b8ffe]/55 dark:bg-[#0d2855] dark:shadow-none dark:focus-within:border-[#56baff] dark:focus-within:ring-[#2b8ffe]/25">
|
||||||
{attachedFiles.length > 0 && (
|
|
||||||
<div className="position-roundtable-intent-attachments mx-3 mb-2 flex flex-wrap gap-1.5 border-b border-slate-100 pb-2 pt-2.5 dark:border-[#2b8ffe]/25">
|
|
||||||
{attachedFiles.map((file, index) => (
|
|
||||||
<span
|
|
||||||
key={`${file.name}-${file.size}-${index}`}
|
|
||||||
className="inline-flex max-w-[200px] items-center gap-1.5 rounded-[4px] border border-slate-200 bg-slate-50 py-1 pl-2 pr-1 text-[11px] text-slate-600 dark:border-[#2b8ffe]/30 dark:bg-[#102f63] dark:text-[#c4dcff]"
|
|
||||||
>
|
|
||||||
<FileText className="size-3 shrink-0 text-primary" />
|
|
||||||
<span className="truncate font-medium" title={file.name}>
|
|
||||||
{file.name}
|
|
||||||
</span>
|
|
||||||
<span className="shrink-0 text-slate-400">{formatFileSize(file.size)}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => onRemoveFile(index)}
|
|
||||||
className="shrink-0 rounded-[3px] p-0.5 text-slate-400 transition hover:bg-slate-200 hover:text-slate-600 dark:text-[#8aaee0] dark:hover:bg-[#21447c] dark:hover:text-white"
|
|
||||||
title="移除"
|
|
||||||
>
|
|
||||||
<X className="size-3" />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<AutoGrowTextarea
|
<AutoGrowTextarea
|
||||||
id="input-text-area"
|
id="input-text-area"
|
||||||
value={value}
|
value={value}
|
||||||
@@ -144,7 +131,8 @@ export function PositionIntentInput({
|
|||||||
title="上传参考文件(智能体将读取文件内容辅助理解任务)"
|
title="上传参考文件(智能体将读取文件内容辅助理解任务)"
|
||||||
className="rounded-[3px] p-1.5 transition hover:bg-white hover:text-primary dark:hover:bg-[#16386e] dark:hover:text-[#73c8ff]"
|
className="rounded-[3px] p-1.5 transition hover:bg-white hover:text-primary dark:hover:bg-[#16386e] dark:hover:text-[#73c8ff]"
|
||||||
>
|
>
|
||||||
<Paperclip className="size-4" />
|
<img src={uploadFileIcon} alt="" aria-hidden="true" />
|
||||||
|
<span>上传附件</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-mono text-[10px] text-slate-400">{charactersLeft}/500</span>
|
<span className="font-mono text-[10px] text-slate-400">{charactersLeft}/500</span>
|
||||||
@@ -154,8 +142,79 @@ export function PositionIntentInput({
|
|||||||
Parent pages can still hide optional actions by their stable ids
|
Parent pages can still hide optional actions by their stable ids
|
||||||
without changing this component's layout or global input styles.
|
without changing this component's layout or global input styles.
|
||||||
*/}
|
*/}
|
||||||
<div className="position-roundtable-intent-toolbar flex flex-wrap items-center gap-3 border-t border-slate-100 bg-white px-3 py-2.5 dark:border-[#2b8ffe]/25 dark:bg-[#0b234c]">
|
<div className="position-roundtable-intent-toolbar flex flex-wrap items-center gap-3 border-t border-slate-100 px-3 py-2.5 dark:border-[#2b8ffe]/25">
|
||||||
<div className="flex shrink-0 items-center">
|
<div className="position-roundtable-intent-common-slot">
|
||||||
|
{visibleCommonQuestions.length > 0 && (
|
||||||
|
<div className="position-roundtable-intent-common-questions">
|
||||||
|
{visibleCommonQuestions.map((question, index) => (
|
||||||
|
<button
|
||||||
|
key={`${question.prompt}-${index}`}
|
||||||
|
type="button"
|
||||||
|
className="position-roundtable-intent-common-question"
|
||||||
|
title={question.prompt}
|
||||||
|
onClick={() => pickCommonQuestion(question)}
|
||||||
|
>
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
<span>{question.title}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{extraCommonQuestions.length > 0 && (
|
||||||
|
<div className="position-roundtable-intent-common-more">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="position-roundtable-intent-common-more-button"
|
||||||
|
aria-label="更多常用问题"
|
||||||
|
aria-expanded={showMoreQuestions}
|
||||||
|
title="更多常用问题"
|
||||||
|
onClick={() => setShowMoreQuestions((visible) => !visible)}
|
||||||
|
>
|
||||||
|
<img src={moreTaskIcon} alt="" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
{showMoreQuestions && (
|
||||||
|
<div className="position-roundtable-intent-common-more-menu">
|
||||||
|
{extraCommonQuestions.map((question, index) => (
|
||||||
|
<button
|
||||||
|
key={`${question.prompt}-${index}`}
|
||||||
|
type="button"
|
||||||
|
title={question.prompt}
|
||||||
|
onClick={() => pickCommonQuestion(question)}
|
||||||
|
>
|
||||||
|
{question.title}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{attachedFiles.length > 0 && (
|
||||||
|
<div className="position-roundtable-intent-file-cards flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto">
|
||||||
|
{attachedFiles.map((file, index) => (
|
||||||
|
<span
|
||||||
|
key={`${file.name}-${file.size}-${index}`}
|
||||||
|
className="inline-flex max-w-[220px] shrink-0 items-center gap-1.5 rounded-[4px] border border-[#2b8ffe]/45 bg-[#0b3168] py-1 pl-2 pr-1 text-[11px] text-[#c4dcff]"
|
||||||
|
>
|
||||||
|
<FileText className="size-3 shrink-0 text-[#73c8ff]" />
|
||||||
|
<span className="truncate font-medium" title={file.name}>
|
||||||
|
{file.name}
|
||||||
|
</span>
|
||||||
|
<span className="shrink-0 text-[#8aaee0]">{formatFileSize(file.size)}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onRemoveFile(index)}
|
||||||
|
className="shrink-0 cursor-pointer rounded-[3px] p-0.5 text-[#8aaee0] transition hover:bg-[#21447c] hover:text-white"
|
||||||
|
title="移除"
|
||||||
|
>
|
||||||
|
<X className="size-3" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="position-roundtable-intent-actions ml-auto flex min-w-0 flex-wrap items-center justify-end gap-1.5">
|
||||||
|
<div className="position-roundtable-intent-upload-wrap flex shrink-0 items-center">
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
@@ -169,28 +228,28 @@ export function PositionIntentInput({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
title="上传参考文件"
|
title="上传附件"
|
||||||
aria-label="上传参考文件"
|
aria-label="上传附件"
|
||||||
className="position-roundtable-intent-upload-btn flex size-10 items-center justify-center rounded-[4px] border border-slate-200 bg-white text-slate-500 transition hover:border-primary/45 hover:text-primary dark:border-[#2b8ffe]/35 dark:bg-[#102f63] dark:text-[#b9d3f7] dark:hover:bg-[#1a417b] dark:hover:text-[#73c8ff]"
|
className="position-roundtable-intent-upload-btn flex items-center justify-center rounded-[24px] border-0"
|
||||||
>
|
>
|
||||||
<Paperclip className="size-4" />
|
<img src={uploadFileIcon} alt="" aria-hidden="true" />
|
||||||
|
<span>上传附件</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="position-roundtable-intent-actions ml-auto flex min-w-0 flex-wrap items-center justify-end gap-1.5">
|
|
||||||
{modelOptions.length > 0 && (
|
{modelOptions.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className="position-roundtable-intent-model-switch flex h-10 shrink-0 items-center gap-1.5 rounded-[4px] border border-slate-200 bg-white pl-2 text-xs font-bold text-slate-700 shadow-sm dark:border-[#2b8ffe]/35 dark:bg-[#102f63] dark:text-[#b9d3f7]"
|
className="position-roundtable-intent-model-switch flex h-8 shrink-0 items-center gap-1.5 rounded-[20px] border border-[#2b8ffe]/55 bg-[#0b3168] px-2.5 text-xs font-bold text-[#dceaff]"
|
||||||
title="切换意图理解模型"
|
title="鍒囨崲鎰忓浘鐞嗚В妯″瀷"
|
||||||
>
|
>
|
||||||
<Sparkles className="size-3.5 shrink-0 text-primary dark:text-[#73c8ff]" />
|
<Sparkles className="size-3.5 shrink-0 text-[#73c8ff]" />
|
||||||
<TSelect
|
<TSelect
|
||||||
size="small"
|
size="small"
|
||||||
aria-label="切换意图理解模型"
|
aria-label="鍒囨崲鎰忓浘鐞嗚В妯″瀷"
|
||||||
value={selectedModel}
|
value={selectedModel}
|
||||||
options={modelOptions}
|
options={modelOptions}
|
||||||
disabled={isSending}
|
disabled={isSending}
|
||||||
onChange={(value) => onSelectedModelChange?.(String(value))}
|
onChange={(value) => onSelectedModelChange?.(String(value))}
|
||||||
className="w-[150px] shrink-0 [&_.t-input]:!h-8 [&_.t-input]:!border-0 [&_.t-input]:!bg-transparent [&_.t-input]:!px-1 [&_.t-input]:!text-xs [&_.t-input]:!font-bold [&_.t-input]:!shadow-none dark:[&_.t-input]:!text-[#dceaff]"
|
className="w-[150px] shrink-0 [&_.t-input]:!h-7 [&_.t-input]:!rounded-[4px] [&_.t-input]:!border-0 [&_.t-input]:!bg-transparent [&_.t-input]:!px-1 [&_.t-input]:!text-xs [&_.t-input]:!font-bold [&_.t-input]:!text-[#dceaff] [&_.t-input]:!shadow-none"
|
||||||
popupProps={{ overlayClassName: "z-[80]" }}
|
popupProps={{ overlayClassName: "z-[80]" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,26 +257,20 @@ export function PositionIntentInput({
|
|||||||
<button
|
<button
|
||||||
id="send-message-btn"
|
id="send-message-btn"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onSend}
|
onClick={isSending ? onStop : onSend}
|
||||||
disabled={!canSend}
|
disabled={isSending ? false : !canSend}
|
||||||
className="position-roundtable-intent-send-btn flex h-10 shrink-0 items-center gap-1.5 rounded-[4px] bg-primary px-3 text-xs font-bold text-white shadow-[0_2px_8px_rgba(245,34,45,0.15)] transition-all active:scale-[0.98] hover:bg-[var(--brand-900)] disabled:cursor-not-allowed disabled:bg-slate-300 dark:bg-[#1478e8] dark:hover:bg-[#2995ff] dark:disabled:bg-[#28476f]"
|
aria-label={isSending ? "暂停生成" : "发送"}
|
||||||
|
title={isSending ? "暂停生成" : "发送"}
|
||||||
|
className={`position-roundtable-intent-send-btn flex h-10 shrink-0 items-center gap-1.5 rounded-[4px] bg-primary px-3 text-xs font-bold text-white shadow-[0_2px_8px_rgba(245,34,45,0.15)] transition-all active:scale-[0.98] hover:bg-[var(--brand-900)] disabled:cursor-not-allowed disabled:bg-slate-300 dark:bg-[#1478e8] dark:hover:bg-[#2995ff] dark:disabled:bg-[#28476f]${isSending ? " position-roundtable-intent-send-btn--stopping" : ""}`}
|
||||||
>
|
>
|
||||||
{isSending ? <Loader2 className="size-3 animate-spin" /> : <Send className="size-3" />}
|
{isSending ? (
|
||||||
{isSending ? "发送中" : "发送"}
|
<Pause className="size-4" aria-hidden="true" />
|
||||||
|
) : (
|
||||||
|
<img src={sendMessageIcon} alt="" aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
<span className="sr-only">{isSending ? "暂停生成" : "发送"}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{isSending && (
|
|
||||||
<button
|
|
||||||
id="pause-intent-btn"
|
|
||||||
type="button"
|
|
||||||
onClick={onStop}
|
|
||||||
className="flex h-10 shrink-0 items-center gap-1.5 rounded-[4px] border border-amber-200/40 bg-amber-100 px-3 text-xs font-bold text-amber-800 shadow-sm transition active:scale-[0.98] hover:bg-amber-200 dark:border-amber-300/30 dark:bg-amber-400/15 dark:text-amber-200 dark:hover:bg-amber-400/25"
|
|
||||||
>
|
|
||||||
<Pause className="size-3" />
|
|
||||||
暂停
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="suggest-followup-btn"
|
id="suggest-followup-btn"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -811,7 +811,7 @@ export function PositionNodeChat({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="position-roundtable-agent-composer-bar shrink-0 border-t border-slate-100 px-4 py-3 dark:border-[#2b8ffe]/20">
|
<div className="position-roundtable-agent-composer-bar position-roundtable-node-composer shrink-0">
|
||||||
<PositionAgentInput
|
<PositionAgentInput
|
||||||
className="w-full"
|
className="w-full"
|
||||||
isNewThread={!boundThreadId}
|
isNewThread={!boundThreadId}
|
||||||
@@ -825,6 +825,7 @@ export function PositionNodeChat({
|
|||||||
onDraftChange={setInputDraft}
|
onDraftChange={setInputDraft}
|
||||||
hideAdminRecommendedQuestions
|
hideAdminRecommendedQuestions
|
||||||
onContextChange={(context) => setSettings("context", context)}
|
onContextChange={(context) => setSettings("context", context)}
|
||||||
|
commonQuestions={agent?.common_questions}
|
||||||
onSubmit={(message) => void handleSubmit(message)}
|
onSubmit={(message) => void handleSubmit(message)}
|
||||||
onStop={() => void thread.stop()}
|
onStop={() => void thread.stop()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import { resolveAllowMultiple } from "@/roundtable-planning/lib/clarification";
|
|||||||
|
|
||||||
export type { ClarificationPayload };
|
export type { ClarificationPayload };
|
||||||
|
|
||||||
|
/** Stable built-in agent used by the multi-agent roundtable Step 1 flow. */
|
||||||
|
export const INTENT_AGENT_ID = "roundtable-intent";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task-intent agent (Step 1) API. Mirror of multi-agent.ts but with a single
|
* Task-intent agent (Step 1) API. Mirror of multi-agent.ts but with a single
|
||||||
* built-in agent and no per-call coordinator: one /init creates a thread
|
* built-in agent and no per-call coordinator: one /init creates a thread
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ export interface Step1PanelProps {
|
|||||||
*/
|
*/
|
||||||
headerLeftSlot?: React.ReactNode;
|
headerLeftSlot?: React.ReactNode;
|
||||||
headerRightSlot?: React.ReactNode;
|
headerRightSlot?: React.ReactNode;
|
||||||
|
/** Optional side content rendered below the shared header, beside the dialogue area. */
|
||||||
|
rightSidebar?: React.ReactNode;
|
||||||
|
/** Hide the shared question-count badge when a workspace supplies its own header actions. */
|
||||||
|
hideQuestionCount?: boolean;
|
||||||
|
/** Optional empty state rendered in the centre of an otherwise blank dialogue list. */
|
||||||
|
emptyState?: React.ReactNode;
|
||||||
interactiveMessages: InteractiveMessage[];
|
interactiveMessages: InteractiveMessage[];
|
||||||
intentThreadId: string | null;
|
intentThreadId: string | null;
|
||||||
intentReady: object | null;
|
intentReady: object | null;
|
||||||
@@ -146,6 +152,9 @@ export function Step1Panel(props: Step1PanelProps) {
|
|||||||
const {
|
const {
|
||||||
headerLeftSlot,
|
headerLeftSlot,
|
||||||
headerRightSlot,
|
headerRightSlot,
|
||||||
|
rightSidebar,
|
||||||
|
hideQuestionCount = false,
|
||||||
|
emptyState,
|
||||||
rootClassName,
|
rootClassName,
|
||||||
interactiveMessages,
|
interactiveMessages,
|
||||||
intentThreadId,
|
intentThreadId,
|
||||||
@@ -230,6 +239,7 @@ export function Step1Panel(props: Step1PanelProps) {
|
|||||||
|
|
||||||
// 顶部标题栏整片脱敏(消息列表正文在下方,不在此容器内,保持原文)。
|
// 顶部标题栏整片脱敏(消息列表正文在下方,不在此容器内,保持原文)。
|
||||||
const headerDzRef = useDesensitizeSubtree<HTMLDivElement>();
|
const headerDzRef = useDesensitizeSubtree<HTMLDivElement>();
|
||||||
|
const showEmptyState = interactiveMessages.length === 0 && Boolean(emptyState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex-1 flex flex-col min-h-0 ${rootClassName ?? ""}`}>
|
<div className={`flex-1 flex flex-col min-h-0 ${rootClassName ?? ""}`}>
|
||||||
@@ -244,34 +254,45 @@ export function Step1Panel(props: Step1PanelProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex shrink-0 items-center gap-2">
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
<span className="shrink-0 text-[11px] font-bold text-slate-400 bg-white border px-2 py-0.5 rounded-md">
|
{!hideQuestionCount && (
|
||||||
问答轮次:{interactiveMessages.filter((m) => m.sender === "user").length} 轮
|
<span className="shrink-0 text-[11px] font-bold text-slate-400 bg-white border px-2 py-0.5 rounded-md">
|
||||||
</span>
|
问答轮次:{interactiveMessages.filter((m) => m.sender === "user").length} 轮
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{headerRightSlot}
|
{headerRightSlot}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={rightSidebar ? "flex min-h-0 flex-1" : "contents"}>
|
||||||
|
<div className={rightSidebar ? "flex min-w-0 flex-1 flex-col" : "contents"}>
|
||||||
{/* Chat list */}
|
{/* Chat list */}
|
||||||
<div className="relative flex-1 min-h-0 flex flex-col font-normal">
|
<div className="relative flex-1 min-h-0 flex flex-col font-normal">
|
||||||
<div
|
<div
|
||||||
ref={step1ScrollRef}
|
ref={step1ScrollRef}
|
||||||
// overflow-x-hidden:消息列表只竖向滚(见 Step2Panel 同处注释),避免误触横向拖动抢滑块。
|
// overflow-x-hidden:消息列表只竖向滚(见 Step2Panel 同处注释),避免误触横向拖动抢滑块。
|
||||||
className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden bg-white"
|
className="flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-hidden bg-white"
|
||||||
>
|
>
|
||||||
<div ref={step1ContentRef} className="space-y-4 p-6">
|
<div
|
||||||
{interactiveMessages.map((msg) => (
|
ref={step1ContentRef}
|
||||||
<Step1Message
|
className={showEmptyState ? "flex min-h-full flex-1 p-6" : "space-y-4 p-6"}
|
||||||
key={msg.id}
|
>
|
||||||
msg={msg}
|
{showEmptyState ? (
|
||||||
metric={speedMeterEnabled ? messageMetrics.get(msg.id) : undefined}
|
<div className="flex min-h-full w-full flex-1 items-center justify-center">{emptyState}</div>
|
||||||
intentReady={intentReady}
|
) : (
|
||||||
pendingIntentClarificationMsgId={pendingIntentClarificationMsgId}
|
interactiveMessages.map((msg) => (
|
||||||
intentClarificationSelections={intentClarificationSelections}
|
<Step1Message
|
||||||
setIntentClarificationSelections={setIntentClarificationSelections}
|
key={msg.id}
|
||||||
isIntentSending={isIntentSending}
|
msg={msg}
|
||||||
submitIntentClarifications={submitIntentClarifications}
|
metric={speedMeterEnabled ? messageMetrics.get(msg.id) : undefined}
|
||||||
/>
|
intentReady={intentReady}
|
||||||
))}
|
pendingIntentClarificationMsgId={pendingIntentClarificationMsgId}
|
||||||
|
intentClarificationSelections={intentClarificationSelections}
|
||||||
|
setIntentClarificationSelections={setIntentClarificationSelections}
|
||||||
|
isIntentSending={isIntentSending}
|
||||||
|
submitIntentClarifications={submitIntentClarifications}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!step1AtBottom && <ScrollToBottomButton onClick={scrollStep1ToBottom} />}
|
{!step1AtBottom && <ScrollToBottomButton onClick={scrollStep1ToBottom} />}
|
||||||
@@ -453,6 +474,9 @@ export function Step1Panel(props: Step1PanelProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
{rightSidebar}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ export interface UseStep1IntentOptions {
|
|||||||
taskContext?: Record<string, unknown> | null;
|
taskContext?: Record<string, unknown> | null;
|
||||||
/** Optional task-aware welcome bubble text before the first user turn. */
|
/** Optional task-aware welcome bubble text before the first user turn. */
|
||||||
initialWelcomeText?: string;
|
initialWelcomeText?: string;
|
||||||
|
/** Keep the dialogue visually empty while its intent thread is initialising. */
|
||||||
|
showInitialWelcome?: boolean;
|
||||||
/** 主页面统一的 toast 入口。 */
|
/** 主页面统一的 toast 入口。 */
|
||||||
triggerToast: (msg: string) => void;
|
triggerToast: (msg: string) => void;
|
||||||
}
|
}
|
||||||
@@ -104,6 +106,7 @@ export interface UseStep1IntentReturn {
|
|||||||
intentReady: IntentSummary | null;
|
intentReady: IntentSummary | null;
|
||||||
intentInitError: string | null;
|
intentInitError: string | null;
|
||||||
intentInitErrorDetail: IntentErrorDetail | null;
|
intentInitErrorDetail: IntentErrorDetail | null;
|
||||||
|
isIntentInitializing: boolean;
|
||||||
isIntentSending: boolean;
|
isIntentSending: boolean;
|
||||||
pendingIntentClarificationMsgId: number | null;
|
pendingIntentClarificationMsgId: number | null;
|
||||||
/** Per-question selections, keyed by `clarificationKey(msgId, clarId, index)`. */
|
/** Per-question selections, keyed by `clarificationKey(msgId, clarId, index)`. */
|
||||||
@@ -172,13 +175,21 @@ function intentPhaseLabel(phase: IntentStreamPhase, toolName?: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentReturn {
|
export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentReturn {
|
||||||
const { currentStep, selectedModel, taskContext, initialWelcomeText, triggerToast } = options;
|
const {
|
||||||
|
currentStep,
|
||||||
|
selectedModel,
|
||||||
|
taskContext,
|
||||||
|
initialWelcomeText,
|
||||||
|
showInitialWelcome = true,
|
||||||
|
triggerToast,
|
||||||
|
} = options;
|
||||||
|
|
||||||
const [intentThreadId, setIntentThreadId] = useState<string | null>(null);
|
const [intentThreadId, setIntentThreadId] = useState<string | null>(null);
|
||||||
const [intentReady, setIntentReady] = useState<IntentSummary | null>(null);
|
const [intentReady, setIntentReady] = useState<IntentSummary | null>(null);
|
||||||
const [intentInitError, setIntentInitError] = useState<string | null>(null);
|
const [intentInitError, setIntentInitError] = useState<string | null>(null);
|
||||||
const [intentInitErrorDetail, setIntentInitErrorDetail] =
|
const [intentInitErrorDetail, setIntentInitErrorDetail] =
|
||||||
useState<IntentErrorDetail | null>(null);
|
useState<IntentErrorDetail | null>(null);
|
||||||
|
const [isIntentInitializing, setIsIntentInitializing] = useState<boolean>(false);
|
||||||
const [isIntentSending, setIsIntentSending] = useState<boolean>(false);
|
const [isIntentSending, setIsIntentSending] = useState<boolean>(false);
|
||||||
const [pendingIntentClarificationMsgId, setPendingIntentClarificationMsgId] =
|
const [pendingIntentClarificationMsgId, setPendingIntentClarificationMsgId] =
|
||||||
useState<number | null>(null);
|
useState<number | null>(null);
|
||||||
@@ -230,18 +241,23 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
|||||||
setIntentInitError(null);
|
setIntentInitError(null);
|
||||||
setIntentInitErrorDetail(null);
|
setIntentInitErrorDetail(null);
|
||||||
setIntentThreadId(null);
|
setIntentThreadId(null);
|
||||||
|
setIsIntentInitializing(true);
|
||||||
setPendingIntentClarificationMsgId(null);
|
setPendingIntentClarificationMsgId(null);
|
||||||
setIntentClarificationSelections(new Map());
|
setIntentClarificationSelections(new Map());
|
||||||
setTimelineEntries([]);
|
setTimelineEntries([]);
|
||||||
setInteractiveMessages([
|
setInteractiveMessages(
|
||||||
{
|
showInitialWelcome
|
||||||
id: 1,
|
? [
|
||||||
sender: "agent",
|
{
|
||||||
senderName: "任务理解智能体",
|
id: 1,
|
||||||
content: initialWelcomeTextRef.current || INTENT_WELCOME_TEXT,
|
sender: "agent",
|
||||||
time: nowTime(),
|
senderName: "任务理解智能体",
|
||||||
},
|
content: initialWelcomeTextRef.current || INTENT_WELCOME_TEXT,
|
||||||
]);
|
time: nowTime(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
);
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
// initIntent 当前不在 thread 上落盘 model,只为占位与兼容;空时由后端
|
// initIntent 当前不在 thread 上落盘 model,只为占位与兼容;空时由后端
|
||||||
@@ -252,6 +268,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
|||||||
);
|
);
|
||||||
if (step1InitGenRef.current !== myGen) return;
|
if (step1InitGenRef.current !== myGen) return;
|
||||||
setIntentThreadId(res.thread_id);
|
setIntentThreadId(res.thread_id);
|
||||||
|
setIsIntentInitializing(false);
|
||||||
setTimelineEntries([
|
setTimelineEntries([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -262,6 +279,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
|||||||
]);
|
]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (step1InitGenRef.current !== myGen) return;
|
if (step1InitGenRef.current !== myGen) return;
|
||||||
|
setIsIntentInitializing(false);
|
||||||
console.error("init intent failed:", err);
|
console.error("init intent failed:", err);
|
||||||
if (err instanceof IntentInitError) {
|
if (err instanceof IntentInitError) {
|
||||||
setIntentInitError(err.detail?.message || err.message);
|
setIntentInitError(err.detail?.message || err.message);
|
||||||
@@ -272,13 +290,14 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, [showInitialWelcome]);
|
||||||
|
|
||||||
const resetIntent = useCallback(() => {
|
const resetIntent = useCallback(() => {
|
||||||
stopIntentStream();
|
stopIntentStream();
|
||||||
step1InitRef.current = false;
|
step1InitRef.current = false;
|
||||||
step1InitGenRef.current += 1;
|
step1InitGenRef.current += 1;
|
||||||
setIntentThreadId(null);
|
setIntentThreadId(null);
|
||||||
|
setIsIntentInitializing(false);
|
||||||
setIntentReady(null);
|
setIntentReady(null);
|
||||||
setIntentInitError(null);
|
setIntentInitError(null);
|
||||||
setIntentInitErrorDetail(null);
|
setIntentInitErrorDetail(null);
|
||||||
@@ -654,6 +673,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
|||||||
// 或继续显示旧会话的澄清选择。
|
// 或继续显示旧会话的澄清选择。
|
||||||
stopIntentStream();
|
stopIntentStream();
|
||||||
setIsIntentSending(false);
|
setIsIntentSending(false);
|
||||||
|
setIsIntentInitializing(false);
|
||||||
setPendingIntentClarificationMsgId(null);
|
setPendingIntentClarificationMsgId(null);
|
||||||
setIntentClarificationSelections(new Map());
|
setIntentClarificationSelections(new Map());
|
||||||
setIntentThreadId(snapshot.intentThreadId);
|
setIntentThreadId(snapshot.intentThreadId);
|
||||||
@@ -690,6 +710,7 @@ export function useStep1Intent(options: UseStep1IntentOptions): UseStep1IntentRe
|
|||||||
intentReady,
|
intentReady,
|
||||||
intentInitError,
|
intentInitError,
|
||||||
intentInitErrorDetail,
|
intentInitErrorDetail,
|
||||||
|
isIntentInitializing,
|
||||||
isIntentSending,
|
isIntentSending,
|
||||||
pendingIntentClarificationMsgId,
|
pendingIntentClarificationMsgId,
|
||||||
intentClarificationSelections,
|
intentClarificationSelections,
|
||||||
|
|||||||
@@ -746,7 +746,7 @@
|
|||||||
|
|
||||||
/* 岗位会商左侧总面板:仅收窄滚动条宽度,沿用全局既有的滑块颜色、圆角和轨道样式。 */
|
/* 岗位会商左侧总面板:仅收窄滚动条宽度,沿用全局既有的滑块颜色、圆角和轨道样式。 */
|
||||||
.position-roundtable-sidebar-scrollbar::-webkit-scrollbar {
|
.position-roundtable-sidebar-scrollbar::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-shell {
|
.workspace-shell {
|
||||||
|
|||||||