fix: public chat copy link button and podcast access

This commit is contained in:
CREDO23 2026-01-26 20:10:03 +02:00
parent ee65e1377f
commit 271de96cce
7 changed files with 47 additions and 20 deletions

View file

@ -84,12 +84,17 @@ async def read_podcasts(
async def read_podcast(
podcast_id: int,
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
user: User | None = Depends(current_optional_user),
):
"""
Get a specific podcast by ID.
Requires PODCASTS_READ permission for the search space.
Access is allowed if:
- User is authenticated with PODCASTS_READ permission, OR
- Podcast belongs to a publicly shared thread
"""
from app.services.public_chat_service import is_podcast_publicly_accessible
try:
result = await session.execute(select(Podcast).filter(Podcast.id == podcast_id))
podcast = result.scalars().first()
@ -100,14 +105,18 @@ async def read_podcast(
detail="Podcast not found",
)
# Check permission for the search space
await check_permission(
session,
user,
podcast.search_space_id,
Permission.PODCASTS_READ.value,
"You don't have permission to read podcasts in this search space",
)
is_public = await is_podcast_publicly_accessible(session, podcast_id)
if not is_public:
if not user:
raise HTTPException(status_code=401, detail="Authentication required")
await check_permission(
session,
user,
podcast.search_space_id,
Permission.PODCASTS_READ.value,
"You don't have permission to read podcasts in this search space",
)
return podcast
except HTTPException as he:

View file

@ -96,6 +96,7 @@ class NewChatThreadRead(NewChatThreadBase, IDModel):
visibility: ChatVisibility
created_by_id: UUID | None = None
public_share_enabled: bool = False
public_share_token: str | None = None
created_at: datetime
updated_at: datetime

View file

@ -27,8 +27,8 @@ def strip_citations(text: str) -> str:
Remove [citation:X] and [citation:doc-X] patterns from text.
Preserves newlines to maintain markdown formatting.
"""
# Remove citation patterns (including Chinese brackets 【】)
text = re.sub(r"[\[【]citation:(doc-)?\d+[\]】]", "", text)
# Remove citation patterns
text = re.sub(r"[\[【]\u200B?citation:(doc-)?\d+\u200B?[\]】]", "", text)
# Collapse multiple spaces/tabs (but NOT newlines) into single space
text = re.sub(r"[^\S\n]+", " ", text)
# Normalize excessive blank lines (3+ newlines → 2)
@ -63,8 +63,17 @@ def sanitize_content_for_public(content: list | str | None) -> list:
sanitized.append({"type": "text", "text": clean_text})
elif part_type == "tool-call":
if part.get("toolName") in UI_TOOLS:
sanitized.append(part)
tool_name = part.get("toolName")
if tool_name not in UI_TOOLS:
continue
# Skip podcasts that are still processing (would cause auth errors)
if tool_name == "generate_podcast":
result = part.get("result", {})
if result.get("status") in ("processing", "already_generating"):
continue
sanitized.append(part)
return sanitized

View file

@ -355,7 +355,7 @@ export default function NewChatPage() {
hasComments: currentThread?.has_comments ?? false,
addingCommentToMessageId: null,
publicShareEnabled: currentThread?.public_share_enabled ?? false,
publicShareToken: null,
publicShareToken: currentThread?.public_share_token ?? null,
});
}, [currentThread, setCurrentThreadState]);

View file

@ -245,17 +245,24 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS
</p>
</div>
{isPublicEnabled && publicShareToken && (
<button
type="button"
<div
role="button"
tabIndex={0}
onClick={(e) => {
e.stopPropagation();
handleCopyPublicLink();
}}
className="shrink-0 p-1.5 rounded-md hover:bg-muted transition-colors"
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.stopPropagation();
handleCopyPublicLink();
}
}}
className="shrink-0 p-1.5 rounded-md hover:bg-muted transition-colors cursor-pointer"
title="Copy public link"
>
<Link2 className="size-4 text-muted-foreground" />
</button>
</div>
)}
</button>
</div>

View file

@ -26,7 +26,7 @@ class BaseApiService {
noAuthEndpoints: string[] = ["/auth/jwt/login", "/auth/register", "/auth/refresh"];
// Prefixes that don't require auth (checked with startsWith)
noAuthPrefixes: string[] = ["/api/v1/public/"];
noAuthPrefixes: string[] = ["/api/v1/public/", "/api/v1/podcasts/"];
// Use a getter to always read fresh token from localStorage
// This ensures the token is always up-to-date after login/logout

View file

@ -25,6 +25,7 @@ export interface ThreadRecord {
updated_at: string;
has_comments?: boolean;
public_share_enabled?: boolean;
public_share_token?: string | null;
}
export interface MessageRecord {