mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-08 20:25:19 +02:00
fix: public chat copy link button and podcast access
This commit is contained in:
parent
ee65e1377f
commit
271de96cce
7 changed files with 47 additions and 20 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue