enhanced mobile view
This commit is contained in:
parent
9c1b9676c8
commit
7e45b6a638
10 changed files with 489 additions and 187 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,24 +190,8 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
textTransform: 'uppercase' as const,
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden' }}>
|
||||
{/* Left sidebar */}
|
||||
{!leftCollapsed && (
|
||||
<aside style={{
|
||||
width: _leftResize.width,
|
||||
minWidth: 200,
|
||||
borderRight: '1px solid var(--border-color, #e0e0e0)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<div style={{ padding: '6px 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={() => setLeftCollapsed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}>◀</button>
|
||||
</div>
|
||||
|
||||
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>
|
||||
|
|
@ -217,11 +225,58 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
/>
|
||||
)}
|
||||
</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', position: 'relative' }}>
|
||||
{/* Left sidebar */}
|
||||
{!isMobile && !leftCollapsed && (
|
||||
<aside style={{
|
||||
width: _leftResize.width,
|
||||
minWidth: 200,
|
||||
borderRight: '1px solid var(--border-color, #e0e0e0)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<div style={{ padding: '6px 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={() => setLeftCollapsed(true)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 14, color: '#888' }}>◀</button>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue