enhanced mobile view

This commit is contained in:
ValueOn AG 2026-03-16 22:55:27 +01:00
parent 9c1b9676c8
commit 7e45b6a638
10 changed files with 489 additions and 187 deletions

View file

@ -1,16 +1,21 @@
.container {
display: flex;
min-height: 100vh;
min-height: 100dvh;
font-family: "DM Sans", sans-serif;
color: var(--color-bg);
}
@supports not (min-height: 100dvh) {
.container {
min-height: 100vh;
}
}
.mainContent {
flex: 1;
display: flex;
flex-direction: column;
padding: 3rem;
padding: 2.5rem 2rem;
background-color: var(--color-bg);
}
@ -18,39 +23,28 @@
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex: 1;
}
.logoText {
font-size: 35px;
.logo {
display: flex;
justify-content: center;
align-items: center;
letter-spacing: -0.5px;
font-weight: 200;
width: 100%;
}
.logoPower {
color: var(--color-text);
}
.logoOn {
color: var(--color-secondary);
font-weight: 700;
}
.logo img {
height: 40px;
.logoImage {
height: 44px;
width: auto;
object-fit: contain;
}
.loginBox {
background-color: var(--color-bg);
width: 25%;
width: min(100%, 460px);
height: auto;
margin-top: 5%;
margin-top: 2rem;
padding: 2rem;
border-radius: 25px;
@ -297,3 +291,41 @@ button:disabled {
.passwordResetLink .textButton:hover {
color: var(--color-secondary);
}
@media (max-width: 768px) {
.mainContent {
padding: 1rem;
}
.logoImage {
height: 40px;
}
.loginBox {
width: 100%;
margin-top: 1.25rem;
padding: 1.25rem;
border-radius: 20px;
}
.registerLink {
flex-wrap: wrap;
text-align: center;
}
}
@media (max-width: 420px) {
.mainContent {
padding: 0.75rem;
}
.loginBox {
padding: 1rem;
border-radius: 16px;
}
.input,
.button {
height: 48px;
}
}

View file

@ -108,10 +108,11 @@ function Login() {
<div className={styles.container}>
<div className={styles.mainContent}>
<div className={styles.logo}>
<div className={styles.logoText}>
<span className={styles.logoPower}>Power</span>
<span className={styles.logoOn}>On</span>
</div>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.logoImage}
/>
</div>
<div className={styles.loginSection}>
<div className={styles.loginBox}>

View file

@ -1,16 +1,21 @@
.container {
display: flex;
min-height: 100vh;
min-height: 100dvh;
font-family: "DM Sans", sans-serif;
color: var(--color-bg);
}
@supports not (min-height: 100dvh) {
.container {
min-height: 100vh;
}
}
.mainContent {
flex: 1;
display: flex;
flex-direction: column;
padding: 3rem;
padding: 2.5rem 2rem;
background-color: var(--color-bg);
}
@ -18,39 +23,28 @@
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex: 1;
}
.logoText {
font-size: 35px;
.logo {
display: flex;
justify-content: center;
align-items: center;
letter-spacing: -0.5px;
font-weight: 200;
width: 100%;
}
.logoPower {
color: var(--color-text);
}
.logoOn {
color: var(--color-secondary);
font-weight: 700;
}
.logo img {
height: 40px;
.logoImage {
height: 44px;
width: auto;
object-fit: contain;
}
.loginBox {
background-color: var(--color-bg);
width: 25%;
width: min(100%, 460px);
height: auto;
margin-top: 5%;
margin-top: 2rem;
padding: 2rem;
border-radius: 25px;
@ -240,3 +234,41 @@ button:disabled {
.infoMessage p {
margin: 0;
}
@media (max-width: 768px) {
.mainContent {
padding: 1rem;
}
.logoImage {
height: 40px;
}
.loginBox {
width: 100%;
margin-top: 1.25rem;
padding: 1.25rem;
border-radius: 20px;
}
.registerLink {
flex-wrap: wrap;
text-align: center;
}
}
@media (max-width: 420px) {
.mainContent {
padding: 0.75rem;
}
.loginBox {
padding: 1rem;
border-radius: 16px;
}
.input,
.button {
height: 48px;
}
}

View file

@ -54,10 +54,11 @@ function PasswordResetRequest() {
<div className={styles.container}>
<div className={styles.mainContent}>
<div className={styles.logo}>
<div className={styles.logoText}>
<span className={styles.logoPower}>Power</span>
<span className={styles.logoOn}>On</span>
</div>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.logoImage}
/>
</div>
<div className={styles.loginSection}>
<div className={styles.loginBox}>

View file

@ -1,16 +1,21 @@
.container {
display: flex;
min-height: 100vh;
min-height: 100dvh;
font-family: "DM Sans", sans-serif;
color: var(--color-bg);
}
@supports not (min-height: 100dvh) {
.container {
min-height: 100vh;
}
}
.mainContent {
flex: 1;
display: flex;
flex-direction: column;
padding: 3rem;
padding: 2.5rem 2rem;
background-color: var(--color-bg);
}
@ -18,39 +23,28 @@
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex: 1;
}
.logoText {
font-size: 35px;
.logo {
display: flex;
justify-content: center;
align-items: center;
letter-spacing: -0.5px;
font-weight: 200;
width: 100%;
}
.logoPower {
color: var(--color-text);
}
.logoOn {
color: var(--color-secondary);
font-weight: 700;
}
.logo img {
height: 40px;
.logoImage {
height: 44px;
width: auto;
object-fit: contain;
}
.loginBox {
background-color: var(--color-bg);
width: 25%;
width: min(100%, 460px);
height: auto;
margin-top: 5%;
margin-top: 2rem;
padding: 2rem;
border-radius: 25px;
@ -273,3 +267,41 @@ button:disabled {
.infoMessage p {
margin: 0;
}
@media (max-width: 768px) {
.mainContent {
padding: 1rem;
}
.logoImage {
height: 40px;
}
.loginBox {
width: 100%;
margin-top: 1.25rem;
padding: 1.25rem;
border-radius: 20px;
}
.registerLink {
flex-wrap: wrap;
text-align: center;
}
}
@media (max-width: 420px) {
.mainContent {
padding: 0.75rem;
}
.loginBox {
padding: 1rem;
border-radius: 16px;
}
.input,
.button {
height: 48px;
}
}

View file

@ -137,10 +137,11 @@ function Register() {
<div className={styles.container}>
<div className={styles.mainContent}>
<div className={styles.logo}>
<div className={styles.logoText}>
<span className={styles.logoPower}>Power</span>
<span className={styles.logoOn}>On</span>
</div>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.logoImage}
/>
</div>
<div className={styles.loginSection}>
<div className={styles.loginBox}>

View file

@ -1,16 +1,21 @@
.container {
display: flex;
min-height: 100vh;
min-height: 100dvh;
font-family: "DM Sans", sans-serif;
color: var(--color-bg);
}
@supports not (min-height: 100dvh) {
.container {
min-height: 100vh;
}
}
.mainContent {
flex: 1;
display: flex;
flex-direction: column;
padding: 3rem;
padding: 2.5rem 2rem;
background-color: var(--color-bg);
}
@ -18,39 +23,28 @@
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex: 1;
}
.logoText {
font-size: 35px;
.logo {
display: flex;
justify-content: center;
align-items: center;
letter-spacing: -0.5px;
font-weight: 200;
width: 100%;
}
.logoPower {
color: var(--color-text);
}
.logoOn {
color: var(--color-secondary);
font-weight: 700;
}
.logo img {
height: 40px;
.logoImage {
height: 44px;
width: auto;
object-fit: contain;
}
.loginBox {
background-color: var(--color-bg);
width: 25%;
width: min(100%, 460px);
height: auto;
margin-top: 5%;
margin-top: 2rem;
padding: 2rem;
border-radius: 25px;
@ -234,3 +228,41 @@ button:disabled {
font-family: var(--font-family);
margin-bottom: 10px;
}
@media (max-width: 768px) {
.mainContent {
padding: 1rem;
}
.logoImage {
height: 40px;
}
.loginBox {
width: 100%;
margin-top: 1.25rem;
padding: 1.25rem;
border-radius: 20px;
}
.registerLink {
flex-wrap: wrap;
text-align: center;
}
}
@media (max-width: 420px) {
.mainContent {
padding: 0.75rem;
}
.loginBox {
padding: 1rem;
border-radius: 16px;
}
.input,
.button {
height: 48px;
}
}

View file

@ -96,10 +96,11 @@ function Reset() {
<div className={styles.container}>
<div className={styles.mainContent}>
<div className={styles.logo}>
<div className={styles.logoText}>
<span className={styles.logoPower}>Power</span>
<span className={styles.logoOn}>On</span>
</div>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.logoImage}
/>
</div>
<div className={styles.loginSection}>
<div className={styles.loginBox}>
@ -135,10 +136,11 @@ function Reset() {
<div className={styles.container}>
<div className={styles.mainContent}>
<div className={styles.logo}>
<div className={styles.logoText}>
<span className={styles.logoPower}>Power</span>
<span className={styles.logoOn}>On</span>
</div>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.logoImage}
/>
</div>
<div className={styles.loginSection}>
<div className={styles.loginBox}>

View file

@ -44,6 +44,7 @@ interface WorkspaceInputProps {
uploading?: boolean;
selectedProviders?: string[];
onProvidersChange?: (providers: string[]) => void;
isMobile?: boolean;
}
export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
@ -59,6 +60,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
uploading = false,
selectedProviders = [],
onProvidersChange,
isMobile = false,
}) => {
const [prompt, setPrompt] = useState('');
const [showAutocomplete, setShowAutocomplete] = useState(false);
@ -267,6 +269,8 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
: [];
const hasAttachments = attachedFileIds.length > 0 || attachedDataSourceIds.length > 0;
const _horizontalPadding = isMobile ? 12 : 24;
const _controlSize = isMobile ? 38 : 40;
return (
<div style={{
@ -277,7 +281,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
{/* Pending uploaded files */}
{pendingFiles.length > 0 && (
<div style={{
padding: '6px 24px',
padding: `6px ${_horizontalPadding}px`,
display: 'flex',
gap: 6,
flexWrap: 'wrap',
@ -314,7 +318,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
{/* Attachment bar */}
{hasAttachments && (
<div style={{
padding: '6px 24px',
padding: `6px ${_horizontalPadding}px`,
display: 'flex',
gap: 6,
flexWrap: 'wrap',
@ -377,8 +381,8 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
<div style={{
position: 'absolute',
bottom: '100%',
left: 24,
right: 24,
left: _horizontalPadding,
right: _horizontalPadding,
maxHeight: 200,
overflowY: 'auto',
background: '#fff',
@ -410,7 +414,13 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
)}
{/* Main input row */}
<div style={{ padding: '8px 24px 12px', display: 'flex', gap: 8, alignItems: 'flex-end' }}>
<div style={{
padding: `8px ${_horizontalPadding}px 12px`,
display: 'flex',
gap: 8,
alignItems: isMobile ? 'stretch' : 'flex-end',
flexWrap: isMobile ? 'wrap' : 'nowrap',
}}>
<textarea
ref={textareaRef}
value={prompt}
@ -420,7 +430,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
disabled={isProcessing}
style={{
flex: 1,
minHeight: 40,
minHeight: isMobile ? 44 : 40,
maxHeight: 120,
resize: 'vertical',
padding: '10px 14px',
@ -429,6 +439,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
fontSize: 14,
fontFamily: 'inherit',
outline: 'none',
flexBasis: isMobile ? '100%' : undefined,
}}
rows={1}
/>
@ -438,7 +449,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
disabled={uploading || isProcessing}
title="Datei anhängen"
style={{
width: 40, height: 40, borderRadius: 8, border: '1px solid var(--border-color, #ddd)',
width: _controlSize, height: _controlSize, borderRadius: 8, border: '1px solid var(--border-color, #ddd)',
background: 'var(--secondary-bg, #f5f5f5)',
color: uploading ? '#1976d2' : '#666',
cursor: uploading || isProcessing ? 'not-allowed' : 'pointer',
@ -456,7 +467,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
disabled={isProcessing}
title="Datenquellen anhängen"
style={{
width: 40, height: 40, borderRadius: 8, border: '1px solid var(--border-color, #ddd)',
width: _controlSize, height: _controlSize, borderRadius: 8, border: '1px solid var(--border-color, #ddd)',
background: attachedDataSourceIds.length > 0 ? '#e8f5e9' : 'var(--secondary-bg, #f5f5f5)',
color: attachedDataSourceIds.length > 0 ? '#2e7d32' : '#666',
cursor: isProcessing ? 'not-allowed' : 'pointer',
@ -536,7 +547,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
onClick={() => setShowLangPicker(prev => !prev)}
title="Sprache waehlen"
style={{
height: 40, borderRadius: '8px 0 0 8px', border: '1px solid var(--border-color, #ddd)',
height: _controlSize, borderRadius: '8px 0 0 8px', border: '1px solid var(--border-color, #ddd)',
borderRight: 'none',
background: 'var(--secondary-bg, #f5f5f5)',
color: '#666', cursor: 'pointer', fontSize: 10, padding: '0 6px',
@ -549,7 +560,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
onClick={_toggleVoice}
title={voiceActive ? 'Aufnahme stoppen' : 'Sprachaufnahme starten'}
style={{
width: 40, height: 40, borderRadius: '0 8px 8px 0', border: 'none',
width: _controlSize, height: _controlSize, borderRadius: '0 8px 8px 0', border: 'none',
background: voiceActive ? '#f44336' : 'var(--secondary-bg, #f5f5f5)',
color: voiceActive ? '#fff' : '#666',
cursor: 'pointer', fontSize: 18, display: 'flex', alignItems: 'center', justifyContent: 'center',
@ -589,6 +600,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
style={{
padding: '10px 20px', borderRadius: 8, border: 'none',
background: '#f44336', color: '#fff', cursor: 'pointer', fontWeight: 600,
minWidth: isMobile ? 84 : undefined,
}}
>
Stop
@ -601,6 +613,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
padding: '10px 20px', borderRadius: 8, border: 'none',
background: prompt.trim() ? 'var(--primary-color, #1976d2)' : '#ccc',
color: '#fff', cursor: prompt.trim() ? 'pointer' : 'default', fontWeight: 600,
minWidth: isMobile ? 84 : undefined,
}}
>
Send

View file

@ -7,7 +7,7 @@
* Right sidebar: FilePreview, ToolActivityLog
*/
import React, { useState, useCallback, useRef } from 'react';
import React, { useState, useCallback, useRef, useEffect } from 'react';
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
import { useFileOperations } from '../../../hooks/useFiles';
import { useWorkspace } from './useWorkspace';
@ -80,6 +80,27 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
const [isDragOver, setIsDragOver] = useState(false);
const dragCounterRef = useRef(0);
const fileInputRef = useRef<HTMLInputElement>(null);
const [isMobile, setIsMobile] = useState<boolean>(() =>
typeof window !== 'undefined' ? window.innerWidth <= 1024 : false,
);
const [mobileLeftOpen, setMobileLeftOpen] = useState(false);
const [mobileRightOpen, setMobileRightOpen] = useState(false);
useEffect(() => {
const _handleResize = () => {
setIsMobile(window.innerWidth <= 1024);
};
_handleResize();
window.addEventListener('resize', _handleResize);
return () => window.removeEventListener('resize', _handleResize);
}, []);
useEffect(() => {
if (!isMobile) {
setMobileLeftOpen(false);
setMobileRightOpen(false);
}
}, [isMobile]);
const _uploadAndAttach = useCallback(async (file: File) => {
const result = await fileOps.handleFileUpload(file, undefined, instanceId);
@ -147,6 +168,9 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
setSelectedFileId(fileId);
setRightTab('preview');
setRightCollapsed(false);
if (isMobile) {
setMobileRightOpen(true);
}
};
const _handleConversationSelect = (wfId: string) => {
@ -166,10 +190,74 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
textTransform: 'uppercase' as const,
});
const _leftPanelBody = (
<>
<div style={{ display: 'flex', borderBottom: '1px solid var(--border-color, #e0e0e0)' }}>
<button style={tabButtonStyle(leftTab === 'conversations')} onClick={() => setLeftTab('conversations')}>Chats</button>
<button style={tabButtonStyle(leftTab === 'files')} onClick={() => setLeftTab('files')}>Files</button>
<button style={tabButtonStyle(leftTab === 'datasources')} onClick={() => setLeftTab('datasources')}>Sources</button>
</div>
<div style={{ flex: 1, overflow: 'auto' }}>
{leftTab === 'conversations' && (
<ConversationList
instanceId={instanceId}
activeWorkflowId={workspace.workflowId}
onSelect={_handleConversationSelect}
onCreateNew={workspace.resetToNew}
refreshTrigger={workspace.workflowVersion}
/>
)}
{leftTab === 'files' && (
<FileBrowser
instanceId={instanceId}
files={workspace.files}
folders={workspace.folders}
onRefresh={workspace.refreshFiles}
onFileSelect={_handleFileSelect}
/>
)}
{leftTab === 'datasources' && (
<DataSourcePanel
instanceId={instanceId}
dataSources={workspace.dataSources}
onRefresh={workspace.refreshDataSources}
/>
)}
</div>
</>
);
const _rightPanelBody = (
<>
<div style={{ padding: '6px 12px', borderBottom: '1px solid var(--border-color, #e0e0e0)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ display: 'flex', gap: 8 }}>
<button style={tabButtonStyle(rightTab === 'activity')} onClick={() => setRightTab('activity')}>Activity</button>
<button style={tabButtonStyle(rightTab === 'preview')} onClick={() => setRightTab('preview')}>Preview</button>
</div>
{!isMobile && (
<button onClick={() => setRightCollapsed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}></button>
)}
</div>
<div style={{ flex: 1, overflow: 'auto' }}>
{rightTab === 'activity' && (
<ToolActivityLog activities={workspace.toolActivities} />
)}
{rightTab === 'preview' && (
<FilePreview
instanceId={instanceId}
fileId={selectedFileId}
files={workspace.files}
/>
)}
</div>
</>
);
return (
<div style={{ display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden' }}>
<div style={{ display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', position: 'relative' }}>
{/* Left sidebar */}
{!leftCollapsed && (
{!isMobile && !leftCollapsed && (
<aside style={{
width: _leftResize.width,
minWidth: 200,
@ -183,45 +271,12 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
<span style={{ fontWeight: 600, fontSize: 14 }}>Workspace</span>
<button onClick={() => setLeftCollapsed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}></button>
</div>
<div style={{ display: 'flex', borderBottom: '1px solid var(--border-color, #e0e0e0)' }}>
<button style={tabButtonStyle(leftTab === 'conversations')} onClick={() => setLeftTab('conversations')}>Chats</button>
<button style={tabButtonStyle(leftTab === 'files')} onClick={() => setLeftTab('files')}>Files</button>
<button style={tabButtonStyle(leftTab === 'datasources')} onClick={() => setLeftTab('datasources')}>Sources</button>
</div>
<div style={{ flex: 1, overflow: 'auto' }}>
{leftTab === 'conversations' && (
<ConversationList
instanceId={instanceId}
activeWorkflowId={workspace.workflowId}
onSelect={_handleConversationSelect}
onCreateNew={workspace.resetToNew}
refreshTrigger={workspace.workflowVersion}
/>
)}
{leftTab === 'files' && (
<FileBrowser
instanceId={instanceId}
files={workspace.files}
folders={workspace.folders}
onRefresh={workspace.refreshFiles}
onFileSelect={_handleFileSelect}
/>
)}
{leftTab === 'datasources' && (
<DataSourcePanel
instanceId={instanceId}
dataSources={workspace.dataSources}
onRefresh={workspace.refreshDataSources}
/>
)}
</div>
{_leftPanelBody}
</aside>
)}
{/* Left resize handle */}
{!leftCollapsed && (
{!isMobile && !leftCollapsed && (
<div
onMouseDown={e => _leftResize.onMouseDown(e, 1)}
style={{ width: 4, cursor: 'col-resize', background: 'transparent', flexShrink: 0 }}
@ -230,7 +285,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
/>
)}
{leftCollapsed && (
{!isMobile && leftCollapsed && (
<div style={{ width: 32, display: 'flex', alignItems: 'start', justifyContent: 'center', paddingTop: 8, borderRight: '1px solid var(--border-color, #e0e0e0)' }}>
<button onClick={() => setLeftCollapsed(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}></button>
</div>
@ -247,6 +302,57 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
onDragOver={_handleDragOver}
onDrop={_handleDrop}
>
{isMobile && (
<div style={{
display: 'flex',
gap: 8,
padding: '8px 12px',
borderBottom: '1px solid var(--border-color, #e0e0e0)',
background: 'var(--bg-primary, #fff)',
flexWrap: 'wrap',
}}>
<button
onClick={() => setMobileLeftOpen(true)}
style={{
padding: '6px 10px',
borderRadius: 8,
border: '1px solid var(--border-color, #ddd)',
background: '#f7f7f7',
cursor: 'pointer',
fontSize: 12,
fontWeight: 600,
}}
>
Workspace
</button>
<button
onClick={() => { setRightTab('activity'); setMobileRightOpen(true); }}
style={{
padding: '6px 10px',
borderRadius: 8,
border: '1px solid var(--border-color, #ddd)',
background: rightTab === 'activity' ? '#e8f3ff' : '#f7f7f7',
cursor: 'pointer',
fontSize: 12,
}}
>
Activity
</button>
<button
onClick={() => { setRightTab('preview'); setMobileRightOpen(true); }}
style={{
padding: '6px 10px',
borderRadius: 8,
border: '1px solid var(--border-color, #ddd)',
background: rightTab === 'preview' ? '#e8f3ff' : '#f7f7f7',
cursor: 'pointer',
fontSize: 12,
}}
>
Preview
</button>
</div>
)}
{isDragOver && (
<div style={{
position: 'absolute', inset: 0, zIndex: 100,
@ -284,11 +390,12 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
uploading={fileOps.uploadingFile}
selectedProviders={selectedProviders}
onProvidersChange={setSelectedProviders}
isMobile={isMobile}
/>
</main>
{/* Right resize handle */}
{!rightCollapsed && (
{!isMobile && !rightCollapsed && (
<div
onMouseDown={e => _rightResize.onMouseDown(e, -1)}
style={{ width: 4, cursor: 'col-resize', background: 'transparent', flexShrink: 0 }}
@ -298,7 +405,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
)}
{/* Right sidebar */}
{!rightCollapsed && (
{!isMobile && !rightCollapsed && (
<aside style={{
width: _rightResize.width,
minWidth: 200,
@ -308,33 +415,82 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
overflow: 'hidden',
flexShrink: 0,
}}>
<div style={{ padding: '6px 12px', borderBottom: '1px solid var(--border-color, #e0e0e0)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ display: 'flex', gap: 8 }}>
<button style={tabButtonStyle(rightTab === 'activity')} onClick={() => setRightTab('activity')}>Activity</button>
<button style={tabButtonStyle(rightTab === 'preview')} onClick={() => setRightTab('preview')}>Preview</button>
</div>
<button onClick={() => setRightCollapsed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}></button>
</div>
<div style={{ flex: 1, overflow: 'auto' }}>
{rightTab === 'activity' && (
<ToolActivityLog activities={workspace.toolActivities} />
)}
{rightTab === 'preview' && (
<FilePreview
instanceId={instanceId}
fileId={selectedFileId}
files={workspace.files}
/>
)}
</div>
{_rightPanelBody}
</aside>
)}
{rightCollapsed && (
{!isMobile && rightCollapsed && (
<div style={{ width: 32, display: 'flex', alignItems: 'start', justifyContent: 'center', paddingTop: 8, borderLeft: '1px solid var(--border-color, #e0e0e0)' }}>
<button onClick={() => setRightCollapsed(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}></button>
</div>
)}
{isMobile && mobileLeftOpen && (
<div
style={{
position: 'absolute',
inset: 0,
background: 'rgba(0, 0, 0, 0.35)',
zIndex: 120,
display: 'flex',
}}
onClick={() => setMobileLeftOpen(false)}
>
<aside
style={{
width: '100%',
maxWidth: 460,
height: '100%',
background: 'var(--bg-primary, #fff)',
borderRight: '1px solid var(--border-color, #e0e0e0)',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}}
onClick={e => e.stopPropagation()}
>
<div style={{ padding: '10px 12px', borderBottom: '1px solid var(--border-color, #e0e0e0)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontWeight: 600, fontSize: 14 }}>Workspace</span>
<button onClick={() => setMobileLeftOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: '#666' }}>×</button>
</div>
{_leftPanelBody}
</aside>
</div>
)}
{isMobile && mobileRightOpen && (
<div
style={{
position: 'absolute',
inset: 0,
background: 'rgba(0, 0, 0, 0.35)',
zIndex: 120,
display: 'flex',
justifyContent: 'flex-end',
}}
onClick={() => setMobileRightOpen(false)}
>
<aside
style={{
width: '100%',
maxWidth: 460,
height: '100%',
background: 'var(--bg-primary, #fff)',
borderLeft: '1px solid var(--border-color, #e0e0e0)',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}}
onClick={e => e.stopPropagation()}
>
<div style={{ padding: '10px 12px', borderBottom: '1px solid var(--border-color, #e0e0e0)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontWeight: 600, fontSize: 14 }}>{rightTab === 'activity' ? 'Activity' : 'Preview'}</span>
<button onClick={() => setMobileRightOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 18, color: '#666' }}>×</button>
</div>
{_rightPanelBody}
</aside>
</div>
)}
</div>
);
};