The anonymous public-share "Ask AI" chat labeled every assistant turn
with the generic "AI agent" even when an Assistant identity (agent role)
was configured. Surface the configured identity name instead, falling
back to "AI agent" when no identity is set.
- server: AiSettingsService.resolvePublicShareAssistantName resolves the
configured role's name (null when unset/missing/disabled), mirroring
PublicShareChatService.resolveShareRole; ShareController returns it as
aiAssistantName on /shares/page-info (only when the assistant is on).
- client: thread aiAssistantName -> ShareAiWidget -> MessageList ->
MessageItem/TypingIndicator via an optional assistantName prop; the
internal chat omits it and keeps showing "AI agent".
- i18n: add "{{name}} is typing…" (en-US, ru-RU) for the typing line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
import { useNavigate, useParams } from "react-router-dom";
|
|
import { Helmet } from "react-helmet-async";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useSharePageQuery } from "@/features/share/queries/share-query.ts";
|
|
import { Container } from "@mantine/core";
|
|
import React, { useEffect } from "react";
|
|
import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx";
|
|
import { extractPageSlugId } from "@/lib";
|
|
import { Error404 } from "@/components/ui/error-404.tsx";
|
|
import ShareBranding from "@/features/share/components/share-branding.tsx";
|
|
import ShareAiWidget from "@/features/share/components/share-ai-widget.tsx";
|
|
import { useAtomValue } from "jotai";
|
|
import {
|
|
sharedPageFullWidthAtom,
|
|
sharedTreeDataAtom,
|
|
} from "@/features/share/atoms/shared-page-atom.ts";
|
|
import { isPageInTree } from "@/features/share/utils.ts";
|
|
|
|
export default function SharedPage() {
|
|
const { t } = useTranslation();
|
|
const { pageSlug } = useParams();
|
|
const { shareId } = useParams();
|
|
const navigate = useNavigate();
|
|
|
|
const { data, isLoading, isError, error } = useSharePageQuery({
|
|
pageId: extractPageSlugId(pageSlug),
|
|
});
|
|
|
|
const sharedTreeData = useAtomValue(sharedTreeDataAtom);
|
|
const fullWidth = useAtomValue(sharedPageFullWidthAtom);
|
|
|
|
useEffect(() => {
|
|
if (shareId && data) {
|
|
if (data.share.key !== shareId) {
|
|
|
|
// Check if the current page is part of the active sharing tree (sidebar) - If we are part of it, we will not redirect, keeping the sidebar visible.
|
|
const isPartOfTree =
|
|
sharedTreeData && isPageInTree(sharedTreeData, data.page.slugId);
|
|
|
|
if (!isPartOfTree) {
|
|
navigate(`/share/${data.share.key}/p/${pageSlug}`, { replace: true });
|
|
}
|
|
}
|
|
}
|
|
}, [shareId, data, sharedTreeData]);
|
|
|
|
if (isLoading) {
|
|
return <></>;
|
|
}
|
|
|
|
if (isError || !data) {
|
|
if ([401, 403, 404].includes(error?.["status"])) {
|
|
return <Error404 />;
|
|
}
|
|
return <div>{t("Error fetching page data.")}</div>;
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<Helmet>
|
|
<title>{`${data?.page?.title || t("untitled")}`}</title>
|
|
{!data?.share.searchIndexing && (
|
|
<meta name="robots" content="noindex" />
|
|
)}
|
|
</Helmet>
|
|
|
|
<Container fluid={fullWidth} size={fullWidth ? undefined : 900} p={0}>
|
|
<ReadonlyPageEditor
|
|
key={data.page.id}
|
|
title={data.page.title}
|
|
content={data.page.content}
|
|
pageId={data.page.id}
|
|
shareId={data.share.id}
|
|
/>
|
|
</Container>
|
|
|
|
{data && !shareId && !(data.features?.length > 0) && <ShareBranding />}
|
|
|
|
{/* Anonymous "Ask AI" widget — only when the workspace enables the
|
|
public-share assistant (server-resolved flag on /shares/page-info). */}
|
|
{data?.aiAssistant && data.share?.id && data.page?.id && (
|
|
<ShareAiWidget
|
|
shareId={data.share.id}
|
|
pageId={data.page.id}
|
|
assistantName={data.aiAssistantName ?? undefined}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|