feat(public-share): selectable agent-role identity + fix floating-icon overlap

Anonymous public-share AI assistant:
- Add a workspace setting `publicShareAssistantRoleId` so an admin can pick which
  agent role (identity/persona) the anonymous assistant adopts. The role's
  instructions REPLACE the built-in persona while the immutable safety framework
  is still always appended; the role's optional model override takes precedence
  over the cheap publicShareChatModel. Resolved server-authoritatively
  (workspace-scoped, soft-delete aware; disabled/missing roles fall back to the
  built-in persona, so the tool scope remains the real security boundary).
- Plumb the field through the update DTO, ai-settings service, the workspace.repo
  ALLOWED whitelist, resolve()/getMasked(), stream-time role resolution and the
  prompt/model, plus the settings UI: a new "Assistant identity" Select listing
  enabled roles (and surfacing a saved-but-disabled role explicitly).

Public-share branding / floating icon:
- Fix the AI assistant FAB overlapping the "Powered by ..." button (both were
  Affixed bottom-right): stack the FAB above the bottom-right branding.
- Rename "Powered by Docmost" -> "Powered by Gitmost" and point the link at the
  gitmost repo.

Tests: extend public-share-chat.spec (role persona replacement still appends the
safety framework, resolveShareRole edge cases, model-override precedence).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude_code
2026-06-20 19:54:36 +03:00
parent 46688074d8
commit 4fe42ead56
13 changed files with 265 additions and 29 deletions
@@ -34,6 +34,7 @@ export interface UpdateAiSettingsInput {
sttApiStyle?: SttApiStyle;
sttApiKey?: string;
publicShareChatModel?: string;
publicShareAssistantRoleId?: string;
}
/**
@@ -135,6 +136,9 @@ export class AiSettingsService {
// Cheap model id for the anonymous public-share assistant; reuses the chat
// driver/baseUrl/apiKey. Empty/unset → callers fall back to chatModel.
publicShareChatModel: provider.publicShareChatModel,
// Agent-role id whose persona the public-share assistant adopts; empty/unset
// = built-in locked persona.
publicShareAssistantRoleId: provider.publicShareAssistantRoleId,
embeddingModel: provider.embeddingModel,
sttModel: provider.sttModel,
// Plain passthrough, no fallback; the transcribe path defaults unset to
@@ -216,6 +220,7 @@ export class AiSettingsService {
sttApiStyle: provider.sttApiStyle,
systemPrompt: provider.systemPrompt,
publicShareChatModel: provider.publicShareChatModel,
publicShareAssistantRoleId: provider.publicShareAssistantRoleId,
hasApiKey,
hasEmbeddingApiKey,
hasSttApiKey,
@@ -254,6 +259,7 @@ export class AiSettingsService {
'sttApiStyle',
'systemPrompt',
'publicShareChatModel',
'publicShareAssistantRoleId',
] as const) {
if (nonSecret[key] !== undefined) {
(providerPatch as Record<string, unknown>)[key] = nonSecret[key];
@@ -38,6 +38,9 @@ export interface AiProviderSettings {
// `chatModel`. The workspace owner pays for anonymous tokens, so a cheaper
// model is preferred for read-only Q&A over published documentation.
publicShareChatModel?: string;
// Agent-role id whose persona the anonymous public-share assistant adopts;
// empty/unset = built-in locked persona.
publicShareAssistantRoleId?: string;
}
/**
@@ -55,6 +58,9 @@ export interface ResolvedAiConfig extends Partial<AiProviderSettings> {
chatModel?: string;
// Cheap model id for the public-share assistant; reuses the chat creds.
publicShareChatModel?: string;
// Agent-role id whose persona the public-share assistant adopts (empty/unset
// = built-in locked persona). Re-declared for parity with the explicit fields.
publicShareAssistantRoleId?: string;
apiKey?: string;
embeddingApiKey?: string;
sttApiKey?: string;
@@ -76,6 +82,9 @@ export interface MaskedAiSettings {
sttApiStyle?: SttApiStyle;
systemPrompt?: string;
publicShareChatModel?: string;
// Agent-role id whose persona the public-share assistant adopts; empty/unset
// = built-in locked persona.
publicShareAssistantRoleId?: string;
hasApiKey: boolean;
hasEmbeddingApiKey: boolean;
hasSttApiKey: boolean;
@@ -63,4 +63,10 @@ export class UpdateAiSettingsDto {
@IsOptional()
@IsString()
publicShareChatModel?: string;
// Agent-role id whose persona the anonymous public-share assistant adopts;
// empty/unset = built-in locked persona.
@IsOptional()
@IsString()
publicShareAssistantRoleId?: string;
}