feat(ai): anonymous AI assistant on public shares
Lets an unauthenticated viewer of a published share ask an AI scoped strictly to that share's page tree. The authenticated agent is untouched; the security boundary is the tool scope (no identity), and nothing is persisted. Server: - workspace toggle settings.ai.publicShareAssistant (default off) + optional settings.ai.provider.publicShareChatModel (cheap model id; reuses the chat driver/baseUrl/key). getChatModel(workspaceId, override) substitutes only the model id, falling back to chatModel. - POST /api/shares/ai/stream (@Public, SSE). Guardrail funnel, each failing before streaming: toggle off -> 404; share missing/wrong-workspace/sharing off -> 404; pageId not in share tree -> 404; provider unconfigured -> 503; per-IP (5/min) and per-workspace (300/h, IP-independent) rate limits -> 429. Uniform 404s never confirm a private page's existence. - forShare read-only in-process toolset: searchSharePages (existing shareId FTS branch, no spaceId/userId), getSharePage (getShareForPage gate + share.id check, content via the public sanitizer), listSharePages. No write/ comment/history/cross-space/external-MCP tools. - Locked share system prompt + immutable safety block; stepCountIs(5). - /shares/page-info exposes an aiAssistant flag (gated behind isSharingAllowed). Client: an ephemeral, text-only Ask-AI widget on the public shared page, shown only when the flag is set; useChat -> /api/shares/ai/stream, credentials omit. Admin toggle + model field in Settings -> AI. Also adds a jest moduleNameMapper for src/-rooted imports (fixes pre-existing unresolvable specs; additive). Implements docs/public-share-assistant-plan.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,10 @@ export class UpdateWorkspaceDto extends PartialType(CreateWorkspaceDto) {
|
||||
@IsBoolean()
|
||||
aiDictation: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
aiPublicShareAssistant: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
|
||||
@@ -511,6 +511,21 @@ export class WorkspaceService {
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof updateWorkspaceDto.aiPublicShareAssistant !== 'undefined') {
|
||||
const prev = settingsBefore?.ai?.publicShareAssistant ?? false;
|
||||
if (prev !== updateWorkspaceDto.aiPublicShareAssistant) {
|
||||
before.aiPublicShareAssistant = prev;
|
||||
after.aiPublicShareAssistant =
|
||||
updateWorkspaceDto.aiPublicShareAssistant;
|
||||
}
|
||||
await this.workspaceRepo.updateAiSettings(
|
||||
workspaceId,
|
||||
'publicShareAssistant',
|
||||
updateWorkspaceDto.aiPublicShareAssistant,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
delete updateWorkspaceDto.restrictApiToAdmins;
|
||||
delete updateWorkspaceDto.aiSearch;
|
||||
delete updateWorkspaceDto.generativeAi;
|
||||
@@ -519,6 +534,7 @@ export class WorkspaceService {
|
||||
delete updateWorkspaceDto.allowMemberTemplates;
|
||||
delete updateWorkspaceDto.aiChat;
|
||||
delete updateWorkspaceDto.aiDictation;
|
||||
delete updateWorkspaceDto.aiPublicShareAssistant;
|
||||
|
||||
await this.workspaceRepo.updateWorkspace(
|
||||
updateWorkspaceDto,
|
||||
|
||||
Reference in New Issue
Block a user