Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.314"
VERSION = "0.261.315"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
75 changes: 40 additions & 35 deletions application/v2_ui/src/components/chat/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import { modelSelectionKey, findModel, type ModelCatalogEntry } from '../../lib/models';
import { promptUrls, resolveGating } from '../../lib/composerGating';
import { resolveDocumentScope } from '../../lib/documentScope';
import { readPublicWorkspaceSelection } from '../../lib/publicChatScope';
import { usePublicWorkspaceLabels } from '../../lib/publicWorkspaceLabels';
import {
addContextItem,
Expand Down Expand Up @@ -165,7 +164,7 @@
)}
>
{icon}
<span className="hidden lg:inline">{label}</span>
<span className="hidden max-w-48 truncate lg:inline">{label}</span>

Check warning on line 167 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Moderate - Changed line contains obfuscation, dynamic loading, or hidden payload marker. Recommendation%3A Confirm the changed code is not hiding behavior, decoding payloads, or bypassing normal review.
</button>
);
}
Expand Down Expand Up @@ -391,6 +390,11 @@
// when orchestrating with manual controls left reachable.
const manualControlsVisible =
!orchestrating || (manualControlsGovernable && manualControlsOpen);
useEffect(() => {
if (!manualControlsVisible) {
setPickerOpen(false);
}
}, [manualControlsVisible]);

/**
* The Orchestrate model: Auto (a model chosen per step) or one pinned model.
Expand Down Expand Up @@ -1014,7 +1018,19 @@

const submit = (allowUnfilled = false) => {
if (publicWorkspaceSelection && (!features.enable_public_workspaces || publicScopeConflict || answeringFromStoredResult || (options.imageGeneration && !orchestrating))) {
toast.error('Public search needs public sources only. Clear incompatible sources or choose Current context.');
const recovery = options.imageGeneration && !orchestrating
? 'Turn off Image, then open Documents and choose Current context.'
: answeringFromStoredResult
? 'Remove the saved result, then open Documents and choose Current context.'
: orchestrating && !manualControlsGovernable
? 'Switch to Manual mode, then open Documents and choose Current context, or start a new chat.'
: `Open ${orchestrating ? 'Manual controls, then ' : ''}Documents to clear incompatible references or choose Current context.`;
toast.error(`Public search needs available public sources only. ${recovery}`);

Check warning on line 1028 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
if (!answeringFromStoredResult && !(options.imageGeneration && !orchestrating)
&& (!orchestrating || manualControlsGovernable)) {
setManualControlsOpen(true);
setPickerOpen(true);
}
return;
}
if (streaming || !canPost || uploadsBlocked || workflowResultOpening) {
Expand Down Expand Up @@ -1651,35 +1667,21 @@
</div>
)}

{!shared && (features.enable_public_workspaces || publicWorkspaceSelection) && (
<div className="mb-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
<label htmlFor="public-chat-scope" className="font-medium text-text-2">Document search scope</label>
<select
id="public-chat-scope"
value={publicWorkspaceSelection ?? ''}
disabled={streaming || !canPost}
onChange={(event) => setPublicWorkspaceSelection(readPublicWorkspaceSelection(event.target.value))}
className="max-w-full rounded-lg border border-edge bg-surface-1 px-2 py-1.5 text-text-1 focus:border-accent-ring focus:outline-none"
>
<option value="">Current context</option>
<option value="all" disabled={!features.enable_public_workspaces}>All {publicLabels.plural.toLowerCase()}</option>
<option value="visible" disabled={!features.enable_public_workspaces}>Visible {publicLabels.plural.toLowerCase()}</option>
</select>
{publicWorkspaceSelection && <span className="text-text-3">
{publicWorkspaceSelection === 'all' ? 'Public only, including hidden workspaces.' : 'Public only, using your directory visibility choices.'}
</span>}
{publicScopeConflict && <p role="alert" className="w-full text-danger">
{nonPublicReferences
? 'Clear personal or group references, or choose Current context before sending.'
: 'Clear whole-workspace references, or choose Current context to search a selected workspace.'}
</p>}
{publicWorkspaceSelection && !features.enable_public_workspaces && <p role="alert" className="w-full text-danger">
Public workspaces are currently disabled. Choose Current context to continue.
</p>}
</div>
)}
<ComposerEditor
publicWorkspaceSelection={publicWorkspaceSelection}
documentScopeControl={!shared && (features.enable_public_workspaces || publicWorkspaceSelection) ? {

Check warning on line 1672 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
id: 'composer-document-scope',
publicEnabled: Boolean(features.enable_public_workspaces),

Check warning on line 1674 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
disabled: streaming || !canPost,

Check warning on line 1675 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
onChange: setPublicWorkspaceSelection,
error: publicWorkspaceSelection && !features.enable_public_workspaces

Check warning on line 1677 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
? 'Public workspaces are currently disabled. Choose Current context to continue.'

Check warning on line 1678 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
: publicScopeConflict
? nonPublicReferences
? 'Clear personal or group references, or choose Current context before sending.'
: 'Clear whole-workspace references, or choose Current context to search a selected workspace.'

Check warning on line 1682 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
: undefined,
} : undefined}
id="composer-input"
label="Message"
draft={draft}
Expand All @@ -1704,12 +1706,15 @@
showTools={false}
uploadsDisabled={gating.uploadsDisabledByImageGeneration}
referenceUploadsOnly={directReferenceMode}
pickerOpen={pickerOpen}
pickerOpen={pickerOpen && manualControlsVisible}
onPickerOpenChange={(open) => {
if (open) {
clearAnalysisResultContext();
}
setPickerOpen(open);
if (!open) {
textareaRef.current?.focus();
}
}}
searchAll={options.documentSearch}
onToggleSearchAll={publicWorkspaceSelection ? undefined : () => {
Expand Down Expand Up @@ -1939,17 +1944,17 @@
<span className="mx-0.5 h-6 w-px bg-edge-strong" aria-hidden="true" />

<ToolToggle
active={options.documentSearch || contextItems.length > 0}
active={options.documentSearch || contextItems.length > 0 || Boolean(publicWorkspaceSelection)}

Check warning on line 1947 in application/v2_ui/src/components/chat/Composer.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
disabled={gating.documentsDisabledByImageGeneration}
onClick={() => {
clearAnalysisResultContext();
setPickerOpen((open) => !open);
}}
icon={<Search size={15} />}
label={
contextItems.length > 0
? `Documents · ${contextItems.length}`
: 'Documents'
`Documents${publicWorkspaceSelection
? ` · ${publicWorkspaceSelection === 'all' ? 'All' : 'Visible'} ${publicLabels.plural.toLowerCase()}`
: ''}${contextItems.length > 0 ? ` · ${contextItems.length}` : ''}`
}
/>

Expand Down
7 changes: 5 additions & 2 deletions application/v2_ui/src/components/chat/ComposerEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,15 @@ import {
} from './ComposerHighlight';
import { ContextChips } from './ContextChips';
import { ContextMenu, useContextSuggestions, type ContextSearchScope } from './ContextMenu';
import { DocumentPickerPopover } from './DocumentPickerPopover';
import { DocumentPickerPopover, type DocumentScopeControl } from './DocumentPickerPopover';
import type { PublicWorkspaceSelection } from '../../lib/publicChatScope';
import { MentionMenu, useMentionSuggestions } from './MentionMenu';
import { ComposerMentionChips } from './MentionPills';
import { PromptSlashMenu } from './PromptSlashMenu';

export interface ComposerEditorProps {
publicWorkspaceSelection?: PublicWorkspaceSelection | null;
documentScopeControl?: DocumentScopeControl;
id: string;
label: string;
draft: ComposerDraft;
Expand Down Expand Up @@ -165,6 +166,7 @@ function ComposerUploadThumbnail({ upload }: { upload: ComposerUpload }) {

export function ComposerEditor({
publicWorkspaceSelection,
documentScopeControl,
id,
label,
draft,
Expand Down Expand Up @@ -242,7 +244,7 @@ export function ComposerEditor({
publicEnabled: Boolean(features.enable_public_workspaces),
// A restricted composer's requests can use only documents and tags, so whole
// workspaces are not offered there.
workspacesEnabled: !restricted,
workspacesEnabled: !restricted && !publicWorkspaceSelection,
documentsOnly: restricted && contextDocumentsOnly,
}), [bootstrap?.scope, contextDocumentsOnly, features.enable_group_workspaces, features.enable_public_workspaces, restricted, publicWorkspaceSelection, referenceUploadsOnly]);
const { candidates, loading } = useContextSuggestions(
Expand Down Expand Up @@ -882,6 +884,7 @@ export function ComposerEditor({
)}
{pickerOpen && !disabled && (
<DocumentPickerPopover scope={scope} searchAll={searchAll} selectedKeys={contextKeys}
scopeControl={!shared && !restricted && !referenceUploadsOnly ? documentScopeControl : undefined}
onToggleSearchAll={onToggleSearchAll} onToggle={toggleContextCandidate}
onClear={() => removeContextChips(draft.contextItems)}
onClose={() => setPickerOpen(false)} placement={menuPlacement}
Expand Down
41 changes: 41 additions & 0 deletions application/v2_ui/src/components/chat/DocumentPickerPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,20 @@
} from '../../lib/contextMentions';
import type { ContextSearchScope } from './ContextMenu';
import { isReferenceImageFileName } from '../../lib/imageReferences';
import { readPublicWorkspaceSelection, type PublicWorkspaceSelection } from '../../lib/publicChatScope';
import { usePublicWorkspaceLabels } from '../../lib/publicWorkspaceLabels';
import type { WorkspaceDocument } from '../../lib/types';

const SEARCH_DEBOUNCE_MS = 250;

export interface DocumentScopeControl {
id: string;
publicEnabled: boolean;
disabled: boolean;

Check warning on line 36 in application/v2_ui/src/components/chat/DocumentPickerPopover.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
onChange: (selection: PublicWorkspaceSelection | null) => void;
error?: string;
}

function isReferenceImageDocument(document: WorkspaceDocument): boolean {
return isReferenceImageFileName(String(document.file_name ?? ''));
}
Expand All @@ -42,6 +52,7 @@
onClose,
placement = 'up',
imagesOnly = false,
scopeControl,
}: {
scope: ContextSearchScope;
/** The original Documents boolean: search everything by relevance. */
Expand All @@ -53,7 +64,9 @@
onClose: () => void;
placement?: 'up' | 'down';
imagesOnly?: boolean;
scopeControl?: DocumentScopeControl;
}) {
const publicLabels = usePublicWorkspaceLabels();
const [query, setQuery] = useState('');
const [candidates, setCandidates] = useState<ContextCandidate[]>([]);
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -91,6 +104,7 @@
const controller = new AbortController();
setLoading(true);
setError(null);
setCandidates([]);

const timer = window.setTimeout(() => {
searchContextCandidates({
Expand Down Expand Up @@ -152,6 +166,33 @@
placement === 'up' ? 'bottom-full mb-2' : 'top-full mt-2',
)}
>
{!imagesOnly && scopeControl && (
<div className="shrink-0 space-y-1 px-0.5 pb-2">
<label htmlFor={scopeControl.id} className="block text-xs font-medium text-text-2">
Search in
</label>
<select
id={scopeControl.id}
value={scope.publicWorkspaceSelection ?? ''}
disabled={scopeControl.disabled}
onChange={(event) => scopeControl.onChange(readPublicWorkspaceSelection(event.target.value))}
aria-describedby={scope.publicWorkspaceSelection ? `${scopeControl.id}-description` : undefined}
className="w-full min-w-0 rounded-lg border border-edge bg-surface-1 px-2 py-1.5 text-sm text-text-1 focus:border-accent-ring focus:outline-none"
>
<option value="">Current context</option>
<option value="all" disabled={!scopeControl.publicEnabled}>All {publicLabels.plural.toLowerCase()}</option>
<option value="visible" disabled={!scopeControl.publicEnabled}>Visible {publicLabels.plural.toLowerCase()}</option>
</select>
{scope.publicWorkspaceSelection && (
<p id={`${scopeControl.id}-description`} className="text-xs text-text-3">
{scope.publicWorkspaceSelection === 'all'
? 'Public only, including hidden workspaces.'
: 'Public only, using your directory visibility choices.'}
</p>
)}
{scopeControl.error && <p role="alert" className="text-xs text-danger">{scopeControl.error}</p>}
</div>
)}
<div className="relative shrink-0 px-0.5 pb-1.5">
<Search
size={13}
Expand Down
12 changes: 9 additions & 3 deletions docs/explanation/features/CHAT_CONTEXT_PICKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Choosing which documents a message is grounded in, and seeing that choice before
you send it.

**Implemented in version:** 0.261.089
**Updated in version:** 0.261.310 (`application/single_app/config.py`)
**Updated in version:** 0.261.315 (`application/single_app/config.py`)
**Inline answers implemented in version:** 0.261.096
**Interface:** V2 only. The classic interface is unchanged.
**Dependencies:** `enable_user_workspace` for personal documents,
Expand Down Expand Up @@ -80,13 +80,19 @@ index is briefly unavailable does not empty the menu of your personal documents.

## The Documents picker

From **0.261.310**, the main composer's **Document search scope** can select
All or Visible public workspaces. In either aggregate mode, Documents and `#`
From **0.261.315**, the main composer's **Documents** picker contains **Search in**:
Current context, All public workspaces, or Visible public workspaces.
There is no standalone scope row above the message editor. In Orchestrate,
open **Manual controls → Documents** when manual controls are enabled.
The Documents button indicates an active All or Visible scope when visible.
In either aggregate mode, Documents and `#`
offer only public sources and tags; All also retrieves from hidden workspaces.
The scope stays selected after sending even though this turn's chips clear.
Return to Current context to use ordinary mixed sources or generate an image.
An incompatible existing personal/group chip is retained and explained, not
silently dropped. See [Aggregate public chat](V2_AGGREGATE_PUBLIC_CHAT.md).
The aggregate choices are opt-in to the main chat editor, not controls on
inline answers, restricted editors, shared chat or image-reference pickers.

Clicking **Documents** opens a panel upward over the composer containing:

Expand Down
Loading
Loading