extracted terminal to a widget
This commit is contained in:
@@ -1,11 +1,4 @@
|
||||
import { DashboardLayout } from '@/Screens/Dashboard/Layout';
|
||||
|
||||
export const ChatList = () => {
|
||||
|
||||
return (
|
||||
<DashboardLayout mobileFull>
|
||||
<h1>ChatList</h1>
|
||||
</DashboardLayout>
|
||||
);
|
||||
return <h1>ChatList</h1>;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useParams } from 'react-router';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useClaude, useOpenCode, type SessionEntry } from 'plugins/Chat/client';
|
||||
import { ChatPanel } from 'plugins/ChatHistory/client';
|
||||
import { DashboardLayout } from '../Layout';
|
||||
import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels';
|
||||
|
||||
export const ClaudeChat = () => {
|
||||
@@ -15,13 +14,11 @@ export const ClaudeChat = () => {
|
||||
const claudeModels = useVisibleClaudeModels();
|
||||
|
||||
return (
|
||||
<DashboardLayout mobileFull>
|
||||
<div className="flex items-center justify-center h-full md:p-4">
|
||||
<div className="flex flex-col w-full h-full md:w-3/4 md:h-3/4 lg:w-1/2 lg:h-1/2">
|
||||
<ChatPanel chat={claude} provider="claude" availableModels={claudeModels} />
|
||||
</div>
|
||||
<div className="flex items-center justify-center h-full md:p-4">
|
||||
<div className="flex flex-col w-full h-full md:w-3/4 md:h-3/4 lg:w-1/2 lg:h-1/2">
|
||||
<ChatPanel chat={claude} provider="claude" availableModels={claudeModels} />
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -34,13 +31,11 @@ export const OpenCodeChat = () => {
|
||||
const openCodeModels = useVisibleOpenCodeModels();
|
||||
|
||||
return (
|
||||
<DashboardLayout mobileFull>
|
||||
<div className="flex items-center justify-center h-full md:p-4">
|
||||
<div className="flex flex-col w-full h-full md:w-3/4 md:h-3/4 lg:w-1/2 lg:h-1/2">
|
||||
<ChatPanel chat={opencode} provider="opencode" availableModels={openCodeModels} />
|
||||
</div>
|
||||
<div className="flex items-center justify-center h-full md:p-4">
|
||||
<div className="flex flex-col w-full h-full md:w-3/4 md:h-3/4 lg:w-1/2 lg:h-1/2">
|
||||
<ChatPanel chat={opencode} provider="opencode" availableModels={openCodeModels} />
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -69,16 +64,14 @@ export const NewChat = () => {
|
||||
const [provider, setProvider] = useState<'claude' | 'opencode'>('claude');
|
||||
|
||||
return (
|
||||
<DashboardLayout mobileFull>
|
||||
<div className="flex items-center justify-center h-full md:p-4">
|
||||
<div className="flex flex-col w-full h-full md:w-3/4 md:h-3/4 lg:w-1/2 lg:h-1/2">
|
||||
{provider === 'claude' ? (
|
||||
<ClaudeNewChatInner key="claude" onProviderChange={setProvider} />
|
||||
) : (
|
||||
<OpenCodeNewChatInner key="opencode" onProviderChange={setProvider} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-center h-full md:p-4">
|
||||
<div className="flex flex-col w-full h-full md:w-3/4 md:h-3/4 lg:w-1/2 lg:h-1/2">
|
||||
{provider === 'claude' ? (
|
||||
<ClaudeNewChatInner key="claude" onProviderChange={setProvider} />
|
||||
) : (
|
||||
<OpenCodeNewChatInner key="opencode" onProviderChange={setProvider} />
|
||||
)}
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
import { DashboardLayout } from '../Layout';
|
||||
import { ChatLauncher } from './ChatLauncher';
|
||||
import { Widget as FileBrowser } from 'plugins/FileBrowser/client';
|
||||
import { Widget as ChatHistory } from 'plugins/ChatHistory/client';
|
||||
import { Catalog } from 'sounds';
|
||||
import { useServerSettings } from '@/state/useServerSettings';
|
||||
|
||||
export const Home = () => {
|
||||
const { plugins } = useServerSettings();
|
||||
|
||||
export const HomeScreen = () => {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex flex-col md:flex-row gap-4 md:gap-8 h-full p-4 pt-6 md:p-8 md:pt-12 overflow-y-auto">
|
||||
<div className="flex flex-col gap-8 flex-1 min-w-0">
|
||||
<ChatLauncher />
|
||||
{plugins?.FileBrowser !== false && <FileBrowser />}
|
||||
</div>
|
||||
<div className="flex flex-col gap-8 flex-1 min-w-0">
|
||||
<ChatHistory />
|
||||
<Catalog />
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-4 md:gap-8 h-full p-4 pt-6 md:p-8 md:pt-12 overflow-y-auto">
|
||||
<div className="flex flex-col gap-8 flex-1 min-w-0">
|
||||
<ChatLauncher />
|
||||
<FileBrowser />
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
<div className="flex flex-col gap-8 flex-1 min-w-0">
|
||||
<ChatHistory />
|
||||
<Catalog />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Background } from './Background';
|
||||
type AuthenticationLayoutProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export function AuthenticationLayout({ children }: AuthenticationLayoutProps) {
|
||||
return (
|
||||
<div className="relative overflow-hidden h-dvh outline-none inset-0">
|
||||
<section className="relative h-dvh snap-start overflow-hidden">
|
||||
<Background />
|
||||
<div className="absolute inset-0 z-520">
|
||||
<div className="absolute inset-x-0 bottom-0 z-20 flex justify-center pb-8 md:pb-12">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DuckAvatar } from "./DuckAvatar";
|
||||
import { PixelGrid } from "@/components/PixelGrid";
|
||||
import landscapebg from './landscape1.jpg';
|
||||
|
||||
@@ -13,7 +12,6 @@ export function Background() {
|
||||
}}
|
||||
>
|
||||
<PixelGrid />
|
||||
<DuckAvatar showDebug={false} fullControlMode={false} currentSection={0} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Background } from './Background';
|
||||
import { Header } from './Header';
|
||||
import { Dock, dockItems } from './Dock';
|
||||
|
||||
type DashboardLayoutProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
return (
|
||||
<div className="relative overflow-hidden h-dvh outline-none inset-0">
|
||||
<Header />
|
||||
<section className="relative h-dvh snap-start overflow-hidden">
|
||||
<Background />
|
||||
<Dock items={dockItems} />
|
||||
<div className="absolute inset-0 z-2 pt-[52px] md:pt-[64px] pb-[80px] overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+16
-1
@@ -46,7 +46,7 @@ export const Dock = ({ items, className }: DockProps) => {
|
||||
ref={dockRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className={`fixed bottom-4 left-1/2 -translate-x-1/2 z-[550] items-end gap-2 md:gap-6 px-2 py-1.5 md:px-3 md:py-2 rounded-2xl border backdrop-blur-xl shadow-lg ${className ?? 'flex'}`}
|
||||
className={`fixed bottom-4 left-1/2 -translate-x-1/2 z-5 items-end gap-2 md:gap-6 px-2 py-1.5 md:px-3 md:py-2 rounded-2xl border backdrop-blur-xl shadow-lg ${className ?? 'flex'}`}
|
||||
style={{ backgroundColor: 'var(--dock-bg)', borderColor: 'var(--dock-border)' }}
|
||||
>
|
||||
{items.map((item, index) => {
|
||||
@@ -89,3 +89,18 @@ export const Dock = ({ items, className }: DockProps) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
import { Terminal, TerminalSquare, FileText, FolderOpen } from 'lucide-react';
|
||||
import { Sparkles, ClipboardList, ScrollText, Workflow } from 'lucide-react';
|
||||
|
||||
export const dockItems: DockItem[] = [
|
||||
{ label: 'Chat', to: '/chat', icon: Terminal, color: '#60a5fa' },
|
||||
{ label: 'Files', to: '/files', icon: FolderOpen, color: '#fbbf24' },
|
||||
{ label: 'Terminal', to: '/terminal', icon: TerminalSquare, color: '#34d399' },
|
||||
{ label: 'Plans', to: '/plans', icon: FileText, color: '#f472b6' },
|
||||
{ label: 'Skills', to: '/skills', icon: Sparkles, color: '#c084fc' },
|
||||
{ label: 'Tasks', to: '/tasks', icon: ClipboardList, color: '#fb923c' },
|
||||
{ label: 'Processes', to: '/processes', icon: Workflow, color: '#2dd4bf' },
|
||||
{ label: 'Logs', to: '/task-logs', icon: ScrollText, color: '#94a3b8' },
|
||||
];
|
||||
@@ -1,43 +0,0 @@
|
||||
type DebugPanelProps = {
|
||||
fullControlMode: boolean;
|
||||
cameraInfo: {
|
||||
cameraPosition: number[];
|
||||
target: number[];
|
||||
zoom: number;
|
||||
};
|
||||
modelInfo: {
|
||||
position: number[];
|
||||
rotation: number[];
|
||||
scale: number;
|
||||
};
|
||||
};
|
||||
|
||||
export function DebugPanel({ fullControlMode, cameraInfo, modelInfo }: DebugPanelProps) {
|
||||
return (
|
||||
<div className="fixed top-4 left-4 bg-black/80 text-white p-4 rounded text-xs font-mono space-y-2 z-[100]">
|
||||
<div className="font-bold text-blue-400">Controls:</div>
|
||||
<div className={fullControlMode ? 'text-green-300' : 'text-yellow-300'}>
|
||||
Mode: {fullControlMode ? 'FULL CONTROL' : 'ROTATION ONLY'}
|
||||
</div>
|
||||
<div>* Left-click/Touch + drag: Rotate</div>
|
||||
{fullControlMode && (
|
||||
<>
|
||||
<div>* Right-click + drag: Pan</div>
|
||||
<div>* Scroll: Zoom</div>
|
||||
</>
|
||||
)}
|
||||
<div>* Ctrl+Alt+Enter: Toggle full control</div>
|
||||
<div>* Ctrl+Alt+D: Toggle debug panel</div>
|
||||
|
||||
<div className="font-bold text-green-400 pt-2">Camera:</div>
|
||||
<div>Position: [{cameraInfo.cameraPosition.map((v) => v.toFixed(2)).join(', ')}]</div>
|
||||
<div>Target: [{cameraInfo.target.map((v) => v.toFixed(2)).join(', ')}]</div>
|
||||
<div>Zoom: {cameraInfo.zoom.toFixed(2)}</div>
|
||||
|
||||
<div className="font-bold text-yellow-400 pt-2">Model:</div>
|
||||
<div>Position: [{modelInfo.position.map((v) => v.toFixed(2)).join(', ')}]</div>
|
||||
<div>Rotation: [{modelInfo.rotation.map((v) => v.toFixed(2)).join(', ')}]</div>
|
||||
<div>Scale: {modelInfo.scale.toFixed(2)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
import { Canvas, useThree } from '@react-three/fiber';
|
||||
import { OrbitControls } from '@react-three/drei';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { DuckModel } from './DuckModel';
|
||||
import { DebugPanel } from './DebugPanel';
|
||||
|
||||
type CameraInfoProps = {
|
||||
onUpdate: (info: { cameraPosition: number[]; target: number[]; zoom: number }) => void;
|
||||
initialTarget: [number, number, number];
|
||||
fullControlMode: boolean;
|
||||
initialPosition: [number, number, number];
|
||||
};
|
||||
|
||||
function CameraInfo({ onUpdate, initialTarget, fullControlMode, initialPosition }: CameraInfoProps) {
|
||||
const { camera } = useThree();
|
||||
const controlsRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (controlsRef.current) {
|
||||
controlsRef.current.target.set(...initialTarget);
|
||||
camera.position.set(...initialPosition);
|
||||
controlsRef.current.update();
|
||||
}
|
||||
}, [initialTarget, initialPosition, camera]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (controlsRef.current) {
|
||||
const controls = controlsRef.current;
|
||||
onUpdate({
|
||||
cameraPosition: camera.position.toArray(),
|
||||
target: controls.target.toArray(),
|
||||
zoom: camera.zoom,
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [camera, onUpdate]);
|
||||
|
||||
return (
|
||||
<OrbitControls
|
||||
ref={controlsRef}
|
||||
enabled={fullControlMode}
|
||||
enableRotate={fullControlMode}
|
||||
enableZoom={fullControlMode}
|
||||
enablePan={fullControlMode}
|
||||
target={initialTarget}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type DuckAvatarProps = {
|
||||
showDebug: boolean;
|
||||
fullControlMode: boolean;
|
||||
currentSection: number;
|
||||
};
|
||||
|
||||
export function DuckAvatar({ showDebug, fullControlMode, currentSection }: DuckAvatarProps) {
|
||||
const isHeroSection = currentSection === 0;
|
||||
const [cameraInfo, setCameraInfo] = useState({
|
||||
cameraPosition: [2.89, 5.24, 7.38],
|
||||
target: [0.3, 3.29, -0.4],
|
||||
zoom: 1,
|
||||
});
|
||||
|
||||
const [modelInfo, setModelInfo] = useState({
|
||||
position: [0, -0.2, 0],
|
||||
rotation: [-0.1, -0.75, 0],
|
||||
scale: 4.5,
|
||||
});
|
||||
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
const heroClasses = 'fixed top-12 md:top-auto md:bottom-0 left-1/2 -translate-x-1/2 w-[60vh] h-[75vh]';
|
||||
const miniClasses = 'fixed bottom-4 right-4 w-[20vh] h-[25vh]';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${isHeroSection ? heroClasses : miniClasses} overflow-visible z-1 pointer-events-none`}
|
||||
style={{
|
||||
background: 'transparent',
|
||||
opacity: isLoaded ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<div className="w-full h-full pointer-events-none overflow-visible" style={{ background: 'transparent' }}>
|
||||
<Canvas
|
||||
camera={{
|
||||
position: [2.89, 5.24, 7.38],
|
||||
fov: 65,
|
||||
near: 0.1,
|
||||
far: 1000,
|
||||
}}
|
||||
style={{ background: 'transparent' }}
|
||||
>
|
||||
<ambientLight intensity={1.5} />
|
||||
<directionalLight position={[10, 10, 5]} intensity={2} />
|
||||
<directionalLight position={[-10, -10, -5]} intensity={1} />
|
||||
<pointLight position={[0, 5, 0]} intensity={1.5} />
|
||||
<CameraInfo
|
||||
onUpdate={setCameraInfo}
|
||||
initialTarget={[0.3, 3.29, -0.4]}
|
||||
fullControlMode={fullControlMode}
|
||||
initialPosition={[2.89, 5.24, 7.38]}
|
||||
/>
|
||||
<DuckModel
|
||||
onUpdate={setModelInfo}
|
||||
onAssetsLoaded={() => {
|
||||
setIsLoaded(true);
|
||||
}}
|
||||
currentSection={currentSection}
|
||||
/>
|
||||
</Canvas>
|
||||
</div>
|
||||
{showDebug && <DebugPanel fullControlMode={fullControlMode} cameraInfo={cameraInfo} modelInfo={modelInfo} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { useRef, useEffect } from 'react';
|
||||
import { useGLTF, useAnimations } from '@react-three/drei';
|
||||
|
||||
type DuckModelProps = {
|
||||
onUpdate: (info: { position: number[]; rotation: number[]; scale: number }) => void;
|
||||
onAssetsLoaded: () => void;
|
||||
currentSection: number;
|
||||
};
|
||||
|
||||
export function DuckModel({ onUpdate, onAssetsLoaded, currentSection }: DuckModelProps) {
|
||||
const character = useGLTF('/static/duck3D/Character_output.glb');
|
||||
const animations = useGLTF('/static/duck3D/Meshy_Merged_Animations.glb');
|
||||
const meshRef = useRef<any>(null);
|
||||
const { actions } = useAnimations(animations.animations, meshRef);
|
||||
|
||||
useEffect(() => {
|
||||
onAssetsLoaded();
|
||||
}, [character, animations, onAssetsLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!actions) return;
|
||||
|
||||
const isContactSection = currentSection === 6;
|
||||
|
||||
if (isContactSection) {
|
||||
Object.values(actions).forEach((a) => a?.fadeOut(0.4));
|
||||
return;
|
||||
}
|
||||
|
||||
const action = actions['Walking'];
|
||||
if (!action) return;
|
||||
|
||||
Object.values(actions).forEach((a) => a?.fadeOut(0.4));
|
||||
|
||||
action.reset();
|
||||
action.setLoop(2201, Infinity);
|
||||
action.clampWhenFinished = false;
|
||||
action.fadeIn(0.4);
|
||||
action.play();
|
||||
}, [actions, currentSection]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (meshRef.current) {
|
||||
onUpdate({
|
||||
position: meshRef.current.position.toArray(),
|
||||
rotation: meshRef.current.rotation.toArray().slice(0, 3),
|
||||
scale: meshRef.current.scale.x,
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [onUpdate]);
|
||||
|
||||
const isContactSection = currentSection === 6;
|
||||
const rotationY = isContactSection ? 0 : (10 * Math.PI) / 180;
|
||||
|
||||
return (
|
||||
<primitive
|
||||
ref={meshRef}
|
||||
object={character.scene}
|
||||
position={[0, -0.2, 0]}
|
||||
scale={4.5}
|
||||
rotation={[-0.1, rotationY, 0]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './DuckAvatar';
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Link } from 'react-router';
|
||||
import { UserMenu } from './UserMenu';
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header className="fixed z-10 w-full">
|
||||
<div
|
||||
className="shrink-0 border-b backdrop-blur-xl shadow-lg px-3 py-2 md:px-6 md:py-3 flex items-center justify-between"
|
||||
style={{ backgroundColor: 'rgba(255, 255, 255, 0.25)', borderColor: 'rgba(255, 255, 255, 0.35)' }}
|
||||
>
|
||||
<Link to="/">
|
||||
<img
|
||||
src="/static/officer-logo.svg"
|
||||
alt="Officer"
|
||||
className="h-8 md:h-12 w-auto"
|
||||
style={{ transform: 'skew(-15deg, -2deg)' }}
|
||||
/>
|
||||
</Link>
|
||||
<UserMenu />
|
||||
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Link } from 'react-router';
|
||||
import * as Dropdown from '@/components/ui/dropdown-menu';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { User, LogOut, Server, Package, Bot } from 'lucide-react';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
|
||||
const { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } = Dropdown;
|
||||
|
||||
export function UserMenu() {
|
||||
const { user, isLoading } = useAuth();
|
||||
if (isLoading) return null;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="rounded-full outline-none focus-visible:ring-2 focus-visible:ring-duck-yellow cursor-pointer">
|
||||
<Avatar className="h-9 w-9 rounded-full">
|
||||
<AvatarImage src={user?.avatar ?? undefined} />
|
||||
<AvatarFallback className="bg-duck-teal text-duck-yellow text-sm font-bold rounded-full">
|
||||
{user?.name?.charAt(0).toUpperCase() ?? '?'}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="z-10 w-48">
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link to="/settings/profile">
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
Profile
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link to="/settings/ai">
|
||||
<Bot className="mr-2 h-4 w-4" />
|
||||
AI Settings
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link to="/settings/server">
|
||||
<Server className="mr-2 h-4 w-4" />
|
||||
Server Settings
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link to="/settings/resources">
|
||||
<Package className="mr-2 h-4 w-4" />
|
||||
Resources
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link to="/auth/signout">
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sign Out
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Header';
|
||||
@@ -1 +1 @@
|
||||
export * from './AuthenticationLayout';
|
||||
export * from './DashboardLayout';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useServerSettings } from '@/state/useServerSettings';
|
||||
import { DashboardLayout } from '../Layout';
|
||||
import { ServerTypeCard } from './ServerTypeCard';
|
||||
import { AIHarnessesCard } from './AIHarnessesCard';
|
||||
|
||||
@@ -46,15 +45,13 @@ export const OnboardingAdmin = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex items-center justify-center h-full px-4">
|
||||
<div className="w-full max-w-lg">
|
||||
{step === 'server-type' && <ServerTypeCard onNext={nextStep} saveSettings={saveSettings} />}
|
||||
{step === 'ai-harnesses' && (
|
||||
<AIHarnessesCard onNext={nextStep} onBack={prevStep} saveSettings={saveSettings} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-center h-full px-4">
|
||||
<div className="w-full max-w-lg">
|
||||
{step === 'server-type' && <ServerTypeCard onNext={nextStep} saveSettings={saveSettings} />}
|
||||
{step === 'ai-harnesses' && (
|
||||
<AIHarnessesCard onNext={nextStep} onBack={prevStep} saveSettings={saveSettings} />
|
||||
)}
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,8 +5,6 @@ import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { Card } from '@/components/Card';
|
||||
import { DashboardLayout } from '../Layout';
|
||||
|
||||
export const Plans = () => {
|
||||
const client = useClient();
|
||||
const [selectedPlan, setSelectedPlan] = useState<string | null>(null);
|
||||
@@ -29,8 +27,7 @@ export const Plans = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex flex-col h-full p-4">
|
||||
<div className="flex flex-col h-full p-4">
|
||||
<Card className="flex-1 overflow-hidden">
|
||||
{/* Header with plan selector */}
|
||||
<div className="shrink-0 flex items-center gap-3 px-4 py-2 border-b border-duck-dark/10 bg-white/60">
|
||||
@@ -59,7 +56,6 @@ export const Plans = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,8 +11,6 @@ import { useClient } from 'hooks/useClient';
|
||||
import { useVisibleClaudeModels } from '@/state/useModels';
|
||||
import { Card } from '@/components/Card';
|
||||
import { useClaude, EmbeddableChat, type ChatMessage } from 'plugins/Chat/client';
|
||||
import { DashboardLayout } from './Layout';
|
||||
|
||||
type ResourceSummary = {
|
||||
dirName: string;
|
||||
name: string;
|
||||
@@ -180,7 +178,7 @@ export const ResourcePage = ({ kind, endpoint, queryKey }: ResourcePageProps) =>
|
||||
);
|
||||
|
||||
return (
|
||||
<DashboardLayout mobileFull={editing}>
|
||||
<>
|
||||
<div className="flex h-full p-2 md:p-4 gap-2 md:gap-4">
|
||||
{/* Left panel — list */}
|
||||
<Card
|
||||
@@ -377,6 +375,6 @@ export const ResourcePage = ({ kind, endpoint, queryKey }: ResourcePageProps) =>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DashboardLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/ui/accordion';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Card } from '@/components/Card';
|
||||
import { DashboardLayout } from '../Layout';
|
||||
import { useSettings } from '@/state/useSettings';
|
||||
import { useUserState } from '@/state/useUserState';
|
||||
import {
|
||||
@@ -26,9 +25,8 @@ export const AISettings = () => {
|
||||
const [mainTab, setMainTab] = useUserState('ai-settings-tab', 'chat-defaults');
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex justify-center h-full px-4 py-8 overflow-y-auto">
|
||||
<Card className="w-full max-w-2xl h-fit p-6">
|
||||
<div className="flex justify-center h-full px-4 py-8 overflow-y-auto">
|
||||
<Card className="w-full max-w-2xl h-fit p-6">
|
||||
<Tabs value={mainTab} onValueChange={setMainTab}>
|
||||
<TabsList className="w-full mb-6">
|
||||
<TabsTrigger value="chat-defaults" className="flex-1 gap-2 cursor-pointer">
|
||||
@@ -49,9 +47,8 @@ export const AISettings = () => {
|
||||
<ModelVisibilitySection />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,10 @@ import { Search, User, Lock, Globe, ListChecks, Palette } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/ui/accordion';
|
||||
import { Card } from '@/components/Card';
|
||||
import { DashboardLayout } from '../../Layout';
|
||||
import { UserData } from './UserData';
|
||||
import { ChangePassword } from './ChangePassword';
|
||||
import { Languages } from './Languages';
|
||||
import { TaskDefaults } from './TaskDefaults';
|
||||
import { Appearance } from './Appearance';
|
||||
|
||||
const sections = [
|
||||
{
|
||||
@@ -19,11 +17,11 @@ const sections = [
|
||||
content: <UserData />,
|
||||
},
|
||||
{
|
||||
key: 'appearance',
|
||||
icon: Palette,
|
||||
title: 'Appearance',
|
||||
description: 'Color theme and light/dark mode.',
|
||||
content: <Appearance />,
|
||||
key: 'change-password',
|
||||
icon: Lock,
|
||||
title: 'Change Password',
|
||||
description: 'Update your account password.',
|
||||
content: <ChangePassword />,
|
||||
},
|
||||
{
|
||||
key: 'tasks',
|
||||
@@ -39,13 +37,6 @@ const sections = [
|
||||
description: 'Set your spoken languages and translation preferences.',
|
||||
content: <Languages />,
|
||||
},
|
||||
{
|
||||
key: 'change-password',
|
||||
icon: Lock,
|
||||
title: 'Change Password',
|
||||
description: 'Update your account password.',
|
||||
content: <ChangePassword />,
|
||||
},
|
||||
];
|
||||
|
||||
const allKeys = sections.map((s) => s.key);
|
||||
@@ -71,42 +62,40 @@ export const ProfileSettings = () => {
|
||||
}, [search, matchingKeys]);
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex justify-center h-full px-4 py-8 overflow-y-auto">
|
||||
<Card className="w-full max-w-2xl h-fit p-6">
|
||||
<div className="relative mb-6">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-duck-dark/40" />
|
||||
<Input
|
||||
placeholder="Search settings..."
|
||||
value={search}
|
||||
onChange={(ev) => setSearch(ev.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center h-full px-4 py-8 overflow-y-auto">
|
||||
<Card className="w-full max-w-2xl h-fit p-6">
|
||||
<div className="relative mb-6">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-duck-dark/40" />
|
||||
<Input
|
||||
placeholder="Search settings..."
|
||||
value={search}
|
||||
onChange={(ev) => setSearch(ev.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Accordion type="multiple" value={expanded} onValueChange={setExpanded}>
|
||||
{sections.map((section) => (
|
||||
<div
|
||||
key={section.key}
|
||||
ref={(el) => {
|
||||
sectionRefs.current[section.key] = el;
|
||||
}}
|
||||
className={search && !matchingKeys.includes(section.key) ? 'hidden' : ''}
|
||||
>
|
||||
<AccordionItem value={section.key}>
|
||||
<AccordionTrigger className="hover:no-underline">
|
||||
<div className="flex items-center gap-3">
|
||||
<section.icon className="h-5 w-5 text-duck-forest shrink-0" />
|
||||
<div className="text-base font-bold text-duck-dark">{section.title}</div>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>{section.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
<Accordion type="multiple" value={expanded} onValueChange={setExpanded}>
|
||||
{sections.map((section) => (
|
||||
<div
|
||||
key={section.key}
|
||||
ref={(el) => {
|
||||
sectionRefs.current[section.key] = el;
|
||||
}}
|
||||
className={search && !matchingKeys.includes(section.key) ? 'hidden' : ''}
|
||||
>
|
||||
<AccordionItem value={section.key}>
|
||||
<AccordionTrigger className="hover:no-underline">
|
||||
<div className="flex items-center gap-3">
|
||||
<section.icon className="h-5 w-5 text-duck-forest shrink-0" />
|
||||
<div className="text-base font-bold text-duck-dark">{section.title}</div>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>{section.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/Card';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { DashboardLayout } from '../../Layout';
|
||||
import { EmbeddableChat, useClaude, useOpenCode } from 'plugins/Chat/client';
|
||||
import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels';
|
||||
|
||||
@@ -82,8 +81,7 @@ export const ResourceSettings = () => {
|
||||
const [provider, setProvider] = useState<'claude' | 'opencode'>('claude');
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex h-full gap-4 px-4 py-8 overflow-hidden">
|
||||
<div className="flex h-full gap-4 px-4 py-8 overflow-hidden">
|
||||
<Card className="w-full max-w-2xl h-fit p-6 overflow-y-auto">
|
||||
<h2 className="text-lg font-bold text-duck-dark mb-1">Applications</h2>
|
||||
<p className="text-sm text-duck-dark/60 mb-6">System tools and dependencies used by Officer.dev</p>
|
||||
@@ -175,8 +173,7 @@ export const ResourceSettings = () => {
|
||||
<OpenCodeChat key="opencode" onProviderChange={setProvider} />
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { Search, Terminal, Puzzle, Shield } from 'lucide-react';
|
||||
import { Search, Terminal, Shield } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/ui/accordion';
|
||||
import { Card } from '@/components/Card';
|
||||
import { DashboardLayout } from '../../Layout';
|
||||
import { AIHarnessesSection } from './AIHarnessesSection';
|
||||
import { PluginsSection } from './PluginsSection';
|
||||
// import { PluginsSection } from './PluginsSection';
|
||||
import { TerminalSection } from './TerminalSection';
|
||||
|
||||
const sections = [
|
||||
@@ -16,13 +15,13 @@ const sections = [
|
||||
description: 'Which AI coding tools do you use?',
|
||||
content: <AIHarnessesSection />,
|
||||
},
|
||||
{
|
||||
key: 'plugins',
|
||||
icon: Puzzle,
|
||||
title: 'Plugins',
|
||||
description: 'Enable or disable installed plugins.',
|
||||
content: <PluginsSection />,
|
||||
},
|
||||
// {
|
||||
// key: 'plugins',
|
||||
// icon: Puzzle,
|
||||
// title: 'Plugins',
|
||||
// description: 'Enable or disable installed plugins.',
|
||||
// content: <PluginsSection />,
|
||||
// },
|
||||
{
|
||||
key: 'terminal',
|
||||
icon: Shield,
|
||||
@@ -55,45 +54,43 @@ export const ServerSettings = () => {
|
||||
}, [search, matchingKeys]);
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex justify-center h-full px-4 py-8 overflow-y-auto">
|
||||
<Card className="w-full max-w-2xl h-fit p-6">
|
||||
<div className="relative mb-6">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-duck-dark/40" />
|
||||
<Input
|
||||
placeholder="Search settings..."
|
||||
value={search}
|
||||
onChange={(ev) => setSearch(ev.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center h-full px-4 py-8 overflow-y-auto">
|
||||
<Card className="w-full max-w-2xl h-fit p-6">
|
||||
<div className="relative mb-6">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-duck-dark/40" />
|
||||
<Input
|
||||
placeholder="Search settings..."
|
||||
value={search}
|
||||
onChange={(ev) => setSearch(ev.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Accordion type="multiple" value={expanded} onValueChange={setExpanded}>
|
||||
{sections.map((section) => (
|
||||
<div
|
||||
key={section.key}
|
||||
ref={(el) => {
|
||||
sectionRefs.current[section.key] = el;
|
||||
}}
|
||||
className={search && !matchingKeys.includes(section.key) ? 'hidden' : ''}
|
||||
>
|
||||
<AccordionItem value={section.key}>
|
||||
<AccordionTrigger className="hover:no-underline">
|
||||
<div className="flex items-center gap-3">
|
||||
<section.icon className="h-5 w-5 text-duck-forest shrink-0" />
|
||||
<div className="text-left">
|
||||
<div className="text-base font-bold text-duck-dark">{section.title}</div>
|
||||
<div className="text-sm font-normal text-duck-dark/70">{section.description}</div>
|
||||
</div>
|
||||
<Accordion type="multiple" value={expanded} onValueChange={setExpanded}>
|
||||
{sections.map((section) => (
|
||||
<div
|
||||
key={section.key}
|
||||
ref={(el) => {
|
||||
sectionRefs.current[section.key] = el;
|
||||
}}
|
||||
className={search && !matchingKeys.includes(section.key) ? 'hidden' : ''}
|
||||
>
|
||||
<AccordionItem value={section.key}>
|
||||
<AccordionTrigger className="hover:no-underline">
|
||||
<div className="flex items-center gap-3">
|
||||
<section.icon className="h-5 w-5 text-duck-forest shrink-0" />
|
||||
<div className="text-left">
|
||||
<div className="text-base font-bold text-duck-dark">{section.title}</div>
|
||||
<div className="text-sm font-normal text-duck-dark/70">{section.description}</div>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent forceMount>{section.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent forceMount>{section.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,29 +1,4 @@
|
||||
import { Link } from 'react-router';
|
||||
import { Bot, ChevronRight } from 'lucide-react';
|
||||
import { Card } from '@/components/Card';
|
||||
import { DashboardLayout } from '../Layout';
|
||||
|
||||
export const Settings = () => {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex items-center justify-center h-full px-4">
|
||||
<div className="w-full max-w-4xl grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<Link to="/settings/ai">
|
||||
<Card className="p-6 hover:border-duck-teal/50 transition-colors cursor-pointer">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Bot className="h-5 w-5 text-duck-forest" />
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-duck-dark">AI Settings</h2>
|
||||
<p className="text-sm text-duck-dark/60">Chat defaults, model visibility</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="h-5 w-5 text-duck-dark/40" />
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
};
|
||||
export * from './ProfileSettings';
|
||||
export * from './AISettings';
|
||||
export * from './ServerSettings';
|
||||
export * from './ResourceSettings';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Search, AlertCircle, CheckCircle2, Clock, ArrowLeft } from 'lucide-react';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { DashboardLayout } from '../Layout';
|
||||
import { Card } from '@/components/Card';
|
||||
import { MessageBubble, type ChatMessage } from 'plugins/Chat/client';
|
||||
|
||||
@@ -77,8 +76,7 @@ export const TaskLogs = () => {
|
||||
: logs;
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="flex h-full p-3 md:p-6 gap-4">
|
||||
<div className="flex h-full p-3 md:p-6 gap-4">
|
||||
{/* Left panel: list */}
|
||||
<Card
|
||||
className={`md:w-80 shrink-0 flex flex-col overflow-hidden ${showDetail ? 'hidden md:flex' : 'flex-1 md:flex-none'}`}
|
||||
@@ -174,7 +172,6 @@ export const TaskLogs = () => {
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { TerminalView } from 'widgets/TerminalView';
|
||||
|
||||
export const Terminal = () => {
|
||||
return <TerminalView className="h-full w-full p-2" />;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
export * from './Layout';
|
||||
export * from './Home';
|
||||
export * from './Chat';
|
||||
export * from './OnboardingAdmin';
|
||||
export * from './PasskeyGate';
|
||||
export * from './Plans';
|
||||
export * from './Processes';
|
||||
export * from './ResourcePage';
|
||||
export * from './Settings';
|
||||
export * from './Skills';
|
||||
export * from './TaskLogs';
|
||||
export * from './Tasks';
|
||||
export * from './Terminal';
|
||||
Reference in New Issue
Block a user