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.323"
VERSION = "0.261.324"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
2 changes: 1 addition & 1 deletion application/v2_ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="icon" href="/static/images/favicon.ico" type="image/x-icon" />
<title>SimpleChat</title>
<script>
Expand Down
18 changes: 16 additions & 2 deletions application/v2_ui/src/components/chat/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Reply,
Search,
ShieldCheck,
SlidersHorizontal,
Square,
Telescope,
Workflow,
Expand Down Expand Up @@ -170,6 +171,7 @@
}

export function Composer({ initialAgentSelection }: { initialAgentSelection?: string } = {}) {
const [mobileToolsOpen, setMobileToolsOpen] = useState(false);

Check warning on line 174 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.
const publicWorkspaceSelection = useChatStore((state) => state.publicWorkspaceSelection);
const setPublicWorkspaceSelection = useChatStore((state) => state.setPublicWorkspaceSelection);
const publicLabels = usePublicWorkspaceLabels();
Expand Down Expand Up @@ -1527,7 +1529,7 @@
};

return (
<div className="shrink-0 px-4 pb-4">
<div className="chat-composer shrink-0 px-2 pb-2 md:px-4 md:pb-4">
{largeRun && (
<LargeRunDialog
estimate={largeRun.estimate}
Expand Down Expand Up @@ -1797,6 +1799,17 @@
)}

<div className="flex flex-wrap items-center gap-1.5 px-1 pt-1" data-tour="composer-tools">
<button type="button" aria-label="Chat tools" aria-expanded={mobileToolsOpen}

Check warning on line 1802 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.
aria-controls="composer-secondary-tools"

Check warning on line 1803 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.
onClick={() => setMobileToolsOpen((open) => !open)}

Check warning on line 1804 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.
className="mobile-tools-toggle flex min-h-11 min-w-0 items-center gap-2 rounded-xl px-2 text-sm text-text-2 hover:bg-surface-2 md:hidden">

Check warning on line 1805 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.

Check warning on line 1805 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.
<SlidersHorizontal size={18} className="shrink-0" />
<span className="min-w-0 truncate">{orchestrating
? `Orchestrate · ${effectiveApprovalMode}`
: activeAgentLabel || modelOptions.find((model) => model.value === options.modelDeployment)?.label || 'Tools'}</span>

Check warning on line 1809 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.
</button>
<div id="composer-secondary-tools" data-open={mobileToolsOpen}

Check warning on line 1811 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.
className="composer-secondary-tools flex flex-wrap items-center gap-1.5 md:contents">

Check warning on line 1812 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.
{orchestrationAvailable && (
<ToolToggle
active={orchestrating}
Expand Down Expand Up @@ -2044,7 +2057,8 @@
</>
)}

<div className="ml-auto flex items-center gap-1.5">
</div>
<div className="composer-primary-actions ml-auto flex shrink-0 items-center gap-1.5">
{features.enable_speech_to_text_input && (
<VoiceInput
onTranscribed={(transcript) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { parseContextTokens } from '../../lib/chatContextTokens';
* defect this constant exists to prevent.
*/
export const COMPOSER_TEXT_CLASS =
'w-full px-3 py-2.5 text-[15px] leading-relaxed whitespace-pre-wrap break-words';
'w-full px-3 py-2.5 text-base md:text-[15px] leading-relaxed whitespace-pre-wrap break-words';

/**
* Caret and selection have to be set explicitly once the text is transparent.
Expand Down
27 changes: 19 additions & 8 deletions application/v2_ui/src/components/documents/DocumentTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
// `MSA_v2_FINAL(3).docx`, so the extracted title leads and the file name sits under it. When
// there is no title the file name is promoted rather than captioned with "Untitled".

import { useEffect, useRef } from 'react';
import { useContext, useEffect, useRef } from 'react';
import { MobileNavigationContext } from '../layout/MobileNavigation';
import { clsx } from 'clsx';
import { ArrowDown, ArrowUp, ChevronsUpDown, Users } from 'lucide-react';
import { groupDocumentOrigin, type DocumentReadScope } from '../../lib/documentReadAdapter';
Expand Down Expand Up @@ -124,7 +125,8 @@ export function DocumentTable({
canReview?: (document: WorkspaceDocument) => boolean;
onReview?: (document: WorkspaceDocument) => void;
}) {
const activeColumns = DOCUMENT_COLUMNS.filter((column) => columns.includes(column.id));
const { mobile } = useContext(MobileNavigationContext);
const activeColumns = DOCUMENT_COLUMNS.filter((column) => mobile ? column.id === 'name' : columns.includes(column.id));
const selectedIds = new Set(selection.ids);
const selectable = documents.filter((document) => !selectionReason(document));
const allSelected = selectable.length > 0 && selectable.every((item) => selectedIds.has(documentId(item)));
Expand All @@ -140,7 +142,7 @@ export function DocumentTable({
}, [someSelected]);

return (
<table className="w-full min-w-[42rem] border-collapse text-sm">
<table className="document-table w-full border-collapse text-sm md:min-w-[42rem]">
<thead className="sticky top-0 z-10 bg-surface-1">
<tr className="border-b border-edge text-left">
<th scope="col" className="w-9 px-2 py-2">
Expand Down Expand Up @@ -228,6 +230,8 @@ export function DocumentTable({
)}
>
<td className="px-2 py-2 align-middle">
<label className="flex min-h-11 items-center justify-center md:min-h-0"
onClick={(event) => event.stopPropagation()}>
<input
type="checkbox"
checked={selected}
Expand All @@ -244,13 +248,14 @@ export function DocumentTable({
}
aria-label={`Select ${primary}`}
className={clsx(
'h-3.5 w-3.5 cursor-pointer accent-[var(--accent)]',
'h-5 w-5 cursor-pointer accent-[var(--accent)] md:h-3.5 md:w-3.5',
// Hidden until useful, like Explorer, but never
// removed: it stays reachable by keyboard.
!selected &&
!mobile && !selected &&
'opacity-0 group-hover:opacity-100 focus:opacity-100',
)}
/>
</label>
</td>

{activeColumns.map((column) => {
Expand All @@ -260,10 +265,10 @@ export function DocumentTable({
<td key={column.id} className="px-2 py-1.5">
<div className="flex min-w-0 items-center gap-2">
<DocumentIcon document={document} />
<div className="min-w-0">
<div className="min-w-0 flex-1">
<button
type="button"
className="max-w-full truncate text-left text-text-1 hover:underline"
className="max-w-full text-left text-text-1 hover:underline max-md:min-h-11 max-md:break-words md:truncate"
aria-label={`Details for ${primary}`}
onClick={(event) => {
event.stopPropagation();
Expand All @@ -273,10 +278,16 @@ export function DocumentTable({
{primary}
</button>
{secondary ? (
<div className="truncate text-xs text-text-3">
<div className="break-words text-xs text-text-3 md:truncate">
{secondary}
</div>
) : null}
{mobile && <div className="mt-1 flex flex-wrap gap-1">
<DocumentStatusBadge document={document} />
{classification && <ClassificationBadge classification={classification}
color={classificationColors[classification]} />}
{blockedReason && <span className="text-xs text-text-3">{blockedReason}</span>}
</div>}
{scope.kind === 'group' ? (
<div className="text-[11px] text-text-3">
{groupDocumentOrigin(document, scope.id)}
Expand Down
17 changes: 15 additions & 2 deletions application/v2_ui/src/components/documents/ExplorerCommandBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// nothing selected rather than hidden, so the bar keeps its shape and the capability stays
// discoverable.

import { useState } from 'react';
import { clsx } from 'clsx';
import {
ArrowDown,
Expand Down Expand Up @@ -103,6 +104,7 @@
onSort: (field: DocumentSortField) => void;
onShowFilters?: () => void;
}) {
const [viewOptionsOpen, setViewOptionsOpen] = useState(false);
const hasSelection = selectionCount > 0;
const canReview = Boolean(onReview && selectedDocuments.length === 1 && availability.canReview?.(selectedDocuments[0]));
const compactActions = Boolean(onShowFilters && (
Expand All @@ -119,7 +121,7 @@
].filter((action) => action.visible);

return (
<div className="flex flex-wrap items-center gap-2 border-b border-edge px-1 pb-2">
<div className="explorer-command-bar flex flex-wrap items-center gap-2 border-b border-edge px-1 pb-2">
{availability.upload ? (
<>
<GlassButton variant="primary" size="sm" onClick={onUpload} disabled={uploading || !availability.allows('upload')}>
Expand Down Expand Up @@ -238,8 +240,18 @@
className="h-8 w-full rounded-lg border border-edge bg-surface-1 pr-2 pl-7 text-sm text-text-1 placeholder:text-text-3 focus:border-accent focus:outline-none"
/>
</div>
<button type="button" aria-label="Document view options"
aria-expanded={viewOptionsOpen} aria-controls="document-view-options"
onClick={() => setViewOptionsOpen((open) => !open)}
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-lg border border-edge bg-surface-1 text-text-2 md:hidden">

Check warning on line 246 in application/v2_ui/src/components/documents/ExplorerCommandBar.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.
<Columns3 size={16} />
</button>
</div>

<div id="document-view-options" className={clsx(
'min-w-0 flex-wrap items-center gap-2 md:contents',
viewOptionsOpen ? 'flex' : 'hidden',
)}>
{canSaveView ? (
<GlassButton
variant="ghost"
Expand Down Expand Up @@ -340,6 +352,7 @@
>
<PanelRight size={15} />
</button>
</div>
</div>
</div>
);
Expand Down Expand Up @@ -446,7 +459,7 @@
const items = paginationItems(page, range.pageCount);

return (
<div className="flex flex-wrap items-center justify-between gap-2 border-t border-edge px-1 pt-2 text-xs text-text-3">
<div className="explorer-status-bar flex flex-wrap items-center justify-between gap-2 border-t border-edge px-1 pt-2 text-xs text-text-3">
<p>
{range.total === 0
? 'No documents'
Expand Down
62 changes: 37 additions & 25 deletions application/v2_ui/src/components/layout/AppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// AppShell.tsx
// Two-column application frame: the collapsible rail on the left, all content on the
// right. The classification banner, when configured, is the only element allowed to span
// the full width, matching the server-rendered interface.
// Desktop rail and content frame, with a shared header/drawer on phones.
// The classification banner spans the frame, matching the server-rendered interface.
//
// The banner carries the same `classification-banner` id the classic layout uses, so a
// deployment styling or asserting against it does not have to know which interface it is
// looking at.

import { useEffect, useRef, useState, type ReactNode } from 'react';
import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
import { MobileNavigation, MobileNavigationContext } from './MobileNavigation';
import { Sidebar } from './Sidebar';
import { Toaster } from '../ui/Toaster';
import { SafetyWarningDialogHost } from '../notifications/SafetyWarningDialog';
Expand All @@ -28,7 +28,7 @@ function ClassificationBanner() {
<div
id="classification-banner"
role="note"
className="flex h-8 shrink-0 items-center justify-center text-sm font-bold tracking-wide"
className="flex min-h-8 shrink-0 items-center justify-center px-3 py-1 text-center text-sm font-bold tracking-wide break-words"
style={{ background: banner.color, color: banner.text_color }}
>
{banner.text}
Expand All @@ -42,6 +42,9 @@ export function AppShell({ children }: { children: ReactNode }) {
const setMobileNavOpen = useUiStore((state) => state.setMobileNavOpen);
const participantsOpen = useCollaborationStore((state) => state.panelTarget !== null);
const contentRef = useRef<HTMLElement>(null);
const [headerCount, setHeaderCount] = useState(0);
const register = useCallback((change: number) => setHeaderCount((count) => count + change), []);
const title = useBootstrapStore((state) => state.data?.branding?.app_title || 'SimpleChat');

useEffect(() => {
const query = window.matchMedia('(max-width: 767px)');
Expand All @@ -63,26 +66,35 @@ export function AppShell({ children }: { children: ReactNode }) {
}, [mobileNavOpen, participantsOpen, setMobileNavOpen]);

return (
<div className="flex h-full flex-col">
<ClassificationBanner />
<div className="relative flex min-h-0 min-w-0 flex-1">
{mobile && <div className="w-[68px] shrink-0" aria-hidden="true" />}
{mobile && mobileNavOpen && (
<button
type="button"
tabIndex={-1}
aria-label="Close navigation"
className="absolute inset-0 z-40 bg-black/40"
onClick={() => setMobileNavOpen(false)}
/>
)}
<Sidebar mobile={mobile} />
<main ref={contentRef} className="flex min-w-0 flex-1 flex-col">{children}</main>
</div>
<Toaster />
<WorkflowAlertCardHost />
<WorkflowAlertLiveRegion />
<SafetyWarningDialogHost />
<MobileNavigationContext.Provider value={{ mobile, register }}>
<div className="app-shell flex h-full min-h-0 flex-col">
<ClassificationBanner />
<div className="relative flex min-h-0 min-w-0 flex-1">
{mobile && mobileNavOpen && (
<button
type="button"
tabIndex={-1}
aria-label="Close navigation"
className="absolute inset-0 z-40 bg-black/40"
onClick={() => setMobileNavOpen(false)}
/>
)}
{(!mobile || mobileNavOpen) && <Sidebar mobile={mobile} />}
<main ref={contentRef} className="flex min-h-0 min-w-0 flex-1 flex-col">
{mobile && headerCount === 0 && (
<header className="glass flex min-h-14 shrink-0 items-center gap-2 px-2">
<MobileNavigation register={false} />
<span className="truncate text-sm font-semibold">{title}</span>
</header>
)}
{children}
</main>
</div>
<Toaster />
<WorkflowAlertCardHost />
<WorkflowAlertLiveRegion />
<SafetyWarningDialogHost />
</div>
</MobileNavigationContext.Provider>
);
}
36 changes: 36 additions & 0 deletions application/v2_ui/src/components/layout/MobileNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// MobileNavigation.tsx

import { createContext, useContext, useLayoutEffect } from 'react';
import { Menu } from 'lucide-react';
import { useUiStore } from '../../stores/uiStore';
import { NotificationBell } from './NotificationBell';

export const MobileNavigationContext = createContext({
mobile: false,
register: (_change: number) => {},
});

export function MobileNavigation({ register = true }: { register?: boolean }) {
const context = useContext(MobileNavigationContext);
const open = useUiStore((state) => state.mobileNavOpen);
const setOpen = useUiStore((state) => state.setMobileNavOpen);

useLayoutEffect(() => {
if (!register) return;
context.register(1);
return () => context.register(-1);
}, [register, context.register]);

if (!context.mobile) return null;

return (
<div className="mobile-navigation-controls flex shrink-0 items-center gap-1">
<button type="button" aria-label="Open navigation" aria-expanded={open}
aria-controls="primary-navigation" onClick={() => setOpen(true)}
className="flex h-11 w-11 items-center justify-center rounded-lg text-text-2 hover:bg-surface-2">
<Menu size={20} />
</button>
<NotificationBell collapsed={false} />
</div>
);
}
Loading
Loading