feat(settings): redesign modal with improved layout and Agent tab (#66)
* feat(settings): redesign modal with improved layout and Agent tab - Unified desktop/mobile layout using single Dialog component - Desktop: 2-column grid (nav 180px | content) - Mobile: Single column with dropdown navigation - Fixed modal height to prevent content stretching - Removed AI chat panel (commented out for future use) - Renamed 'Slab Memory' to 'Agent' tab - Added mock Agent settings: Signet ID (ETH) input and Configure button - Added useChatStateOptional hook to chat-provider for safe context usage - Fixed provider order in dashboard layout * chore: add auth-bypass.ts to gitignore for local dev --------- Co-authored-by: Avery Felts <averyfelts@Averys-MacBook-Air.local>
This commit is contained in:
parent
67412a0b00
commit
04180d4305
2
.gitignore
vendored
2
.gitignore
vendored
@ -38,3 +38,5 @@ ios/App/build/
|
||||
android/.gradle/
|
||||
android/build/
|
||||
android/app/build/
|
||||
# Local auth bypass (dev only)
|
||||
src/lib/auth-bypass.ts
|
||||
|
||||
@ -42,8 +42,8 @@ export default async function DashboardLayout({
|
||||
: []
|
||||
|
||||
return (
|
||||
<SettingsProvider>
|
||||
<ChatProvider>
|
||||
<SettingsProvider>
|
||||
<ProjectListProvider projects={projectList}>
|
||||
<PageActionsProvider>
|
||||
<CommandMenuProvider>
|
||||
@ -84,7 +84,7 @@ export default async function DashboardLayout({
|
||||
</CommandMenuProvider>
|
||||
</PageActionsProvider>
|
||||
</ProjectListProvider>
|
||||
</ChatProvider>
|
||||
</SettingsProvider>
|
||||
</ChatProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@ -76,6 +76,10 @@ export function useChatState(): ChatStateValue {
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function useChatStateOptional(): ChatStateValue | null {
|
||||
return React.useContext(ChatStateContext)
|
||||
}
|
||||
|
||||
// --- Render state context ---
|
||||
|
||||
interface RenderContextValue {
|
||||
|
||||
@ -81,6 +81,10 @@ type RepoStats = {
|
||||
|
||||
interface ChatViewProps {
|
||||
readonly variant: "page" | "panel"
|
||||
readonly minimal?: boolean
|
||||
readonly hideSuggestions?: boolean
|
||||
onActivate?: () => void
|
||||
readonly inputPlaceholder?: string
|
||||
}
|
||||
|
||||
const REPO = "High-Performance-Structures/compass"
|
||||
@ -387,6 +391,7 @@ function ChatInput({
|
||||
onSend,
|
||||
onNewChat,
|
||||
className,
|
||||
onActivate,
|
||||
}: {
|
||||
readonly textareaRef: React.RefObject<
|
||||
HTMLTextAreaElement | null
|
||||
@ -398,6 +403,7 @@ function ChatInput({
|
||||
readonly onSend: (text: string) => void
|
||||
readonly onNewChat?: () => void
|
||||
readonly className?: string
|
||||
readonly onActivate?: () => void
|
||||
}) {
|
||||
const isRecording = recorder.state === "recording"
|
||||
const isTranscribing = recorder.state === "transcribing"
|
||||
@ -406,6 +412,8 @@ function ChatInput({
|
||||
return (
|
||||
<PromptInput
|
||||
className={className}
|
||||
onClickCapture={onActivate}
|
||||
onFocusCapture={onActivate}
|
||||
onSubmit={({ text }) => {
|
||||
if (!text.trim() || isGenerating) return
|
||||
onSend(text.trim())
|
||||
@ -484,7 +492,13 @@ function ChatInput({
|
||||
)
|
||||
}
|
||||
|
||||
export function ChatView({ variant }: ChatViewProps) {
|
||||
export function ChatView({
|
||||
variant,
|
||||
minimal = false,
|
||||
hideSuggestions = false,
|
||||
onActivate,
|
||||
inputPlaceholder,
|
||||
}: ChatViewProps) {
|
||||
const chat = useChatState()
|
||||
const isPage = variant === "page"
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
@ -809,22 +823,45 @@ export function ChatView({ variant }: ChatViewProps) {
|
||||
}
|
||||
|
||||
// --- PANEL variant ---
|
||||
if (minimal) {
|
||||
return (
|
||||
<div className="w-full p-2">
|
||||
<ChatInput
|
||||
textareaRef={textareaRef}
|
||||
placeholder={inputPlaceholder ?? "Create a new setting"}
|
||||
recorder={recorder}
|
||||
status={chat.status}
|
||||
isGenerating={chat.isGenerating}
|
||||
onSend={handleActiveSend}
|
||||
onActivate={onActivate}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
{/* Conversation */}
|
||||
<Conversation className="flex-1">
|
||||
<ConversationContent>
|
||||
{chat.messages.length === 0 ? (
|
||||
<div className="flex flex-col items-center gap-4 pt-8">
|
||||
<Suggestions>
|
||||
{suggestions.map((s) => (
|
||||
<Suggestion
|
||||
key={s}
|
||||
suggestion={s}
|
||||
onClick={handleSuggestion}
|
||||
/>
|
||||
))}
|
||||
</Suggestions>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-4",
|
||||
hideSuggestions ? "h-full" : "pt-8"
|
||||
)}
|
||||
>
|
||||
{!hideSuggestions && (
|
||||
<Suggestions>
|
||||
{suggestions.map((s) => (
|
||||
<Suggestion
|
||||
key={s}
|
||||
suggestion={s}
|
||||
onClick={handleSuggestion}
|
||||
/>
|
||||
))}
|
||||
</Suggestions>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
chat.messages.map((msg, idx) => (
|
||||
@ -851,7 +888,7 @@ export function ChatView({ variant }: ChatViewProps) {
|
||||
<div className="p-3">
|
||||
<ChatInput
|
||||
textareaRef={textareaRef}
|
||||
placeholder="Ask anything..."
|
||||
placeholder={inputPlaceholder ?? "Ask anything..."}
|
||||
recorder={recorder}
|
||||
status={chat.status}
|
||||
isGenerating={chat.isGenerating}
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { IconPlus } from "@tabler/icons-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import {
|
||||
ResponsiveDialog,
|
||||
ResponsiveDialogBody,
|
||||
} from "@/components/ui/responsive-dialog"
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import {
|
||||
Select,
|
||||
@ -15,17 +23,12 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/components/ui/tabs"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
// import { useChatStateOptional } from "@/components/agent/chat-provider"
|
||||
// import { ChatView } from "@/components/agent/chat-view"
|
||||
import { NetSuiteConnectionStatus } from "@/components/netsuite/connection-status"
|
||||
import { SyncControls } from "@/components/netsuite/sync-controls"
|
||||
import { GoogleDriveConnectionStatus } from "@/components/google/connection-status"
|
||||
import { MemoriesTable } from "@/components/agent/memories-table"
|
||||
import { SkillsTab } from "@/components/settings/skills-tab"
|
||||
import { AIModelTab } from "@/components/settings/ai-model-tab"
|
||||
import { AppearanceTab } from "@/components/settings/appearance-tab"
|
||||
@ -33,6 +36,47 @@ import { ClaudeCodeTab } from "@/components/settings/claude-code-tab"
|
||||
import { useNative } from "@/hooks/use-native"
|
||||
import { useBiometricAuth } from "@/hooks/use-biometric-auth"
|
||||
|
||||
const SETTINGS_TABS = [
|
||||
{ value: "general", label: "General" },
|
||||
{ value: "notifications", label: "Notifications" },
|
||||
{ value: "appearance", label: "Theme" },
|
||||
{ value: "integrations", label: "Integrations" },
|
||||
{ value: "ai-model", label: "AI Model" },
|
||||
{ value: "agent", label: "Agent" },
|
||||
{ value: "skills", label: "Skills" },
|
||||
] as const
|
||||
|
||||
const CREATE_SETTING_TAB = {
|
||||
value: "create-setting",
|
||||
label: "Create Setting",
|
||||
} as const
|
||||
|
||||
interface CustomSettingTab {
|
||||
readonly value: string
|
||||
readonly label: string
|
||||
readonly prompt: string
|
||||
}
|
||||
|
||||
function makeCustomSettingValue(
|
||||
label: string,
|
||||
existingTabs: ReadonlyArray<CustomSettingTab>
|
||||
): string {
|
||||
const normalized = label
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/(^-|-$)/g, "")
|
||||
const base = normalized.length > 0 ? normalized : "setting"
|
||||
|
||||
let candidate = `custom-${base}`
|
||||
let suffix = 2
|
||||
while (existingTabs.some((tab) => tab.value === candidate)) {
|
||||
candidate = `custom-${base}-${suffix}`
|
||||
suffix += 1
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
|
||||
export function SettingsModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
@ -44,177 +88,87 @@ export function SettingsModal({
|
||||
const [pushNotifs, setPushNotifs] = React.useState(true)
|
||||
const [weeklyDigest, setWeeklyDigest] = React.useState(false)
|
||||
const [timezone, setTimezone] = React.useState("America/New_York")
|
||||
const [signetId, setSignetId] = React.useState("")
|
||||
const [customTabs, setCustomTabs] = React.useState<ReadonlyArray<CustomSettingTab>>([])
|
||||
const [activeTab, setActiveTab] = React.useState<string>("general")
|
||||
const [newSettingName, setNewSettingName] = React.useState("")
|
||||
const [newSettingPrompt, setNewSettingPrompt] = React.useState("")
|
||||
// const [isMobileChatOpen, setIsMobileChatOpen] = React.useState(false)
|
||||
// const chatState = useChatStateOptional()
|
||||
|
||||
const menuTabs = React.useMemo(
|
||||
() => [...SETTINGS_TABS, ...customTabs],
|
||||
[customTabs]
|
||||
)
|
||||
|
||||
const sendCreateSettingToChat = React.useCallback(() => {
|
||||
toast.info("AI chat is currently disabled in settings")
|
||||
}, [])
|
||||
|
||||
const openCreateSettingFlow = React.useCallback(() => {
|
||||
setActiveTab(CREATE_SETTING_TAB.value)
|
||||
// setIsMobileChatOpen(true)
|
||||
// chatState?.sendMessage({ text: "Create a new setting" })
|
||||
}, [])
|
||||
|
||||
const handleSectionSelect = React.useCallback((value: string) => {
|
||||
if (value === CREATE_SETTING_TAB.value) {
|
||||
openCreateSettingFlow()
|
||||
return
|
||||
}
|
||||
setActiveTab(value)
|
||||
}, [openCreateSettingFlow])
|
||||
|
||||
const createCustomSetting = React.useCallback(() => {
|
||||
const label = newSettingName.trim()
|
||||
const details = newSettingPrompt.trim()
|
||||
|
||||
if (!label) {
|
||||
toast.error("Add a setting name first")
|
||||
return
|
||||
}
|
||||
if (!details) {
|
||||
toast.error("Describe what the setting should do")
|
||||
return
|
||||
}
|
||||
|
||||
const nextTab: CustomSettingTab = {
|
||||
value: makeCustomSettingValue(label, customTabs),
|
||||
label,
|
||||
prompt: details,
|
||||
}
|
||||
setCustomTabs((prev) => [...prev, nextTab])
|
||||
setActiveTab(nextTab.value)
|
||||
// setIsMobileChatOpen(true)
|
||||
setNewSettingName("")
|
||||
setNewSettingPrompt("")
|
||||
sendCreateSettingToChat()
|
||||
toast.success(`Added ${nextTab.label} to settings`)
|
||||
}, [customTabs, newSettingName, newSettingPrompt, sendCreateSettingToChat])
|
||||
|
||||
const native = useNative()
|
||||
const biometric = useBiometricAuth()
|
||||
|
||||
const generalPage = (
|
||||
<>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="timezone" className="text-xs">
|
||||
Timezone
|
||||
</Label>
|
||||
<Select value={timezone} onValueChange={setTimezone}>
|
||||
<SelectTrigger id="timezone" className="w-full h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="America/New_York">
|
||||
Eastern (ET)
|
||||
</SelectItem>
|
||||
<SelectItem value="America/Chicago">
|
||||
Central (CT)
|
||||
</SelectItem>
|
||||
<SelectItem value="America/Denver">
|
||||
Mountain (MT)
|
||||
</SelectItem>
|
||||
<SelectItem value="America/Los_Angeles">
|
||||
Pacific (PT)
|
||||
</SelectItem>
|
||||
<SelectItem value="Europe/London">
|
||||
London (GMT)
|
||||
</SelectItem>
|
||||
<SelectItem value="Europe/Berlin">
|
||||
Berlin (CET)
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<Label className="text-xs">Weekly digest</Label>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Receive a summary of activity each week.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={weeklyDigest}
|
||||
onCheckedChange={setWeeklyDigest}
|
||||
className="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
const notificationsPage = (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<Label className="text-xs">Email notifications</Label>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Get notified about project updates via email.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={emailNotifs}
|
||||
onCheckedChange={setEmailNotifs}
|
||||
className="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<Label className="text-xs">Push notifications</Label>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Receive push notifications in your browser.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={pushNotifs}
|
||||
onCheckedChange={setPushNotifs}
|
||||
className="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
const appearancePage = <AppearanceTab />
|
||||
|
||||
const integrationsPage = (
|
||||
<>
|
||||
<GoogleDriveConnectionStatus />
|
||||
<Separator />
|
||||
<NetSuiteConnectionStatus />
|
||||
<SyncControls />
|
||||
<Separator />
|
||||
<ClaudeCodeTab />
|
||||
</>
|
||||
)
|
||||
|
||||
const slabMemoryPage = <MemoriesTable />
|
||||
|
||||
const aiModelPage = <AIModelTab />
|
||||
|
||||
const skillsPage = <SkillsTab />
|
||||
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
title="Settings"
|
||||
description="Manage your app preferences."
|
||||
className="sm:max-w-2xl"
|
||||
>
|
||||
<ResponsiveDialogBody
|
||||
pages={[generalPage, notificationsPage, appearancePage, integrationsPage, aiModelPage, slabMemoryPage, skillsPage]}
|
||||
>
|
||||
<Tabs defaultValue="general" className="w-full">
|
||||
<TabsList className="w-full inline-flex justify-start overflow-x-auto">
|
||||
<TabsTrigger value="general" className="text-xs sm:text-sm shrink-0">
|
||||
General
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="notifications" className="text-xs sm:text-sm shrink-0">
|
||||
Notifications
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="appearance" className="text-xs sm:text-sm shrink-0">
|
||||
Appearance
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="integrations" className="text-xs sm:text-sm shrink-0">
|
||||
Integrations
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ai-model" className="text-xs sm:text-sm shrink-0">
|
||||
AI Model
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="slab-memory" className="text-xs sm:text-sm shrink-0">
|
||||
Slab Memory
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="skills" className="text-xs sm:text-sm shrink-0">
|
||||
Skills
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="general" className="space-y-3 pt-3">
|
||||
const renderContent = () => {
|
||||
switch (activeTab) {
|
||||
case "general":
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="timezone" className="text-xs">
|
||||
Timezone
|
||||
</Label>
|
||||
<Select value={timezone} onValueChange={setTimezone}>
|
||||
<SelectTrigger id="timezone" className="w-full h-9">
|
||||
<SelectTrigger id="timezone" className="h-9 w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="America/New_York">
|
||||
Eastern (ET)
|
||||
</SelectItem>
|
||||
<SelectItem value="America/Chicago">
|
||||
Central (CT)
|
||||
</SelectItem>
|
||||
<SelectItem value="America/Denver">
|
||||
Mountain (MT)
|
||||
</SelectItem>
|
||||
<SelectItem value="America/Los_Angeles">
|
||||
Pacific (PT)
|
||||
</SelectItem>
|
||||
<SelectItem value="Europe/London">
|
||||
London (GMT)
|
||||
</SelectItem>
|
||||
<SelectItem value="Europe/Berlin">
|
||||
Berlin (CET)
|
||||
</SelectItem>
|
||||
<SelectItem value="America/New_York">Eastern (ET)</SelectItem>
|
||||
<SelectItem value="America/Chicago">Central (CT)</SelectItem>
|
||||
<SelectItem value="America/Denver">Mountain (MT)</SelectItem>
|
||||
<SelectItem value="America/Los_Angeles">Pacific (PT)</SelectItem>
|
||||
<SelectItem value="Europe/London">London (GMT)</SelectItem>
|
||||
<SelectItem value="Europe/Berlin">Berlin (CET)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@ -222,7 +176,7 @@ export function SettingsModal({
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<Label className="text-xs">Weekly digest</Label>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Receive a summary of activity each week.
|
||||
@ -234,14 +188,14 @@ export function SettingsModal({
|
||||
className="shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</div>
|
||||
)
|
||||
|
||||
<TabsContent
|
||||
value="notifications"
|
||||
className="space-y-3 pt-3"
|
||||
>
|
||||
case "notifications":
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<Label className="text-xs">Email notifications</Label>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Get notified about project updates via email.
|
||||
@ -257,7 +211,7 @@ export function SettingsModal({
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<Label className="text-xs">Push notifications</Label>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{native
|
||||
@ -275,9 +229,8 @@ export function SettingsModal({
|
||||
{native && biometric.isAvailable && (
|
||||
<>
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>
|
||||
<Label className="text-xs">Biometric lock</Label>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Require Face ID or fingerprint when returning to the app.
|
||||
@ -291,49 +244,243 @@ export function SettingsModal({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</TabsContent>
|
||||
</div>
|
||||
)
|
||||
|
||||
<TabsContent
|
||||
value="appearance"
|
||||
className="space-y-3 pt-3"
|
||||
>
|
||||
<AppearanceTab />
|
||||
</TabsContent>
|
||||
case "appearance":
|
||||
return <div className="pt-2"><AppearanceTab /></div>
|
||||
|
||||
<TabsContent
|
||||
value="integrations"
|
||||
className="space-y-3 pt-3"
|
||||
>
|
||||
case "integrations":
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<GoogleDriveConnectionStatus />
|
||||
<Separator />
|
||||
<NetSuiteConnectionStatus />
|
||||
<SyncControls />
|
||||
<Separator />
|
||||
<ClaudeCodeTab />
|
||||
</TabsContent>
|
||||
</div>
|
||||
)
|
||||
|
||||
<TabsContent
|
||||
value="ai-model"
|
||||
className="space-y-3 pt-3"
|
||||
>
|
||||
<AIModelTab />
|
||||
</TabsContent>
|
||||
case "ai-model":
|
||||
return <div className="pt-2"><AIModelTab /></div>
|
||||
|
||||
<TabsContent
|
||||
value="slab-memory"
|
||||
className="space-y-3 pt-3"
|
||||
>
|
||||
<MemoriesTable />
|
||||
</TabsContent>
|
||||
case "agent":
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="signet-id" className="text-xs">
|
||||
Signet ID (ETH)
|
||||
</Label>
|
||||
<Input
|
||||
id="signet-id"
|
||||
value={signetId}
|
||||
onChange={(e) => setSignetId(e.target.value)}
|
||||
placeholder="0x..."
|
||||
className="h-9 font-mono"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TabsContent
|
||||
value="skills"
|
||||
className="space-y-3 pt-3"
|
||||
>
|
||||
<SkillsTab />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ResponsiveDialogBody>
|
||||
</ResponsiveDialog>
|
||||
<Separator />
|
||||
|
||||
<Button className="w-full">
|
||||
Configure your agent
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
case "skills":
|
||||
return <div className="pt-2"><SkillsTab /></div>
|
||||
|
||||
case CREATE_SETTING_TAB.value:
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<div className="rounded-lg border bg-muted/20 p-3">
|
||||
<p className="text-sm font-medium">Create a new setting</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
Describe the setting you want, then send it to AI chat.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="new-setting-name" className="text-xs">
|
||||
Setting name
|
||||
</Label>
|
||||
<Input
|
||||
id="new-setting-name"
|
||||
value={newSettingName}
|
||||
onChange={(e) => setNewSettingName(e.target.value)}
|
||||
placeholder="Example: Project defaults"
|
||||
className="h-9"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="new-setting-prompt" className="text-xs">
|
||||
What should this setting do?
|
||||
</Label>
|
||||
<Textarea
|
||||
id="new-setting-prompt"
|
||||
value={newSettingPrompt}
|
||||
onChange={(e) => setNewSettingPrompt(e.target.value)}
|
||||
placeholder="Describe the controls and behavior you want"
|
||||
rows={4}
|
||||
className="resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button onClick={createCustomSetting} className="w-full">
|
||||
<IconPlus className="size-4" />
|
||||
Create Setting with AI
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
default:
|
||||
const customTab = customTabs.find((tab) => tab.value === activeTab)
|
||||
if (customTab) {
|
||||
return (
|
||||
<div className="space-y-4 pt-2">
|
||||
<div className="rounded-lg border p-3">
|
||||
<p className="text-sm font-medium">{customTab.label}</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs leading-relaxed">
|
||||
{customTab.prompt}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => sendCreateSettingToChat()}
|
||||
>
|
||||
Send to AI Chat
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="h-[85vh] max-h-[700px] w-full max-w-[600px] overflow-hidden p-0 md:h-auto md:max-h-[85vh] md:max-w-[700px]">
|
||||
<DialogHeader className="border-b px-6 py-4">
|
||||
<DialogTitle>Settings</DialogTitle>
|
||||
<DialogDescription>Manage your app preferences.</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex h-[calc(85vh-80px)] flex-col overflow-hidden md:h-[500px]">
|
||||
{/* Desktop: 2 columns | Mobile: Single column */}
|
||||
<div className="flex flex-1 flex-col gap-6 overflow-hidden p-6 md:grid md:grid-cols-[180px_1fr]">
|
||||
|
||||
{/* Left Column - Navigation (Desktop only) */}
|
||||
<aside className="hidden md:flex md:flex-col md:overflow-hidden">
|
||||
<div className="flex h-full flex-col justify-between rounded-xl border bg-muted/20 p-2">
|
||||
<div className="flex flex-col gap-1 overflow-y-auto">
|
||||
{menuTabs.map((tab) => (
|
||||
<Button
|
||||
key={tab.value}
|
||||
type="button"
|
||||
variant={tab.value === activeTab ? "secondary" : "ghost"}
|
||||
className="h-8 w-full justify-start text-sm"
|
||||
onClick={() => setActiveTab(tab.value)}
|
||||
>
|
||||
{tab.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4 shrink-0">
|
||||
<Separator className="mb-4" />
|
||||
<Button
|
||||
type="button"
|
||||
variant={activeTab === CREATE_SETTING_TAB.value ? "secondary" : "outline"}
|
||||
className="h-8 w-full justify-start gap-1.5 text-sm"
|
||||
onClick={openCreateSettingFlow}
|
||||
>
|
||||
<IconPlus className="size-4" />
|
||||
{CREATE_SETTING_TAB.label}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Middle Column - Content */}
|
||||
<div className="flex min-h-0 flex-col overflow-hidden">
|
||||
{/* Mobile Navigation */}
|
||||
<div className="mb-4 md:hidden">
|
||||
<Select value={activeTab} onValueChange={handleSectionSelect}>
|
||||
<SelectTrigger className="h-10 w-full">
|
||||
<SelectValue placeholder="Select section" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{menuTabs.map((tab) => (
|
||||
<SelectItem key={tab.value} value={tab.value}>
|
||||
{tab.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectItem value={CREATE_SETTING_TAB.value}>
|
||||
{CREATE_SETTING_TAB.label}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Settings Content - Scrollable */}
|
||||
<div className="flex-1 overflow-y-auto pr-2">
|
||||
{renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - AI Chat (Desktop only) - COMMENTED OUT */}
|
||||
{/*
|
||||
<div className="hidden overflow-hidden rounded-xl border bg-background/95 shadow-lg backdrop-blur-sm md:block">
|
||||
<ChatView
|
||||
variant="panel"
|
||||
hideSuggestions
|
||||
inputPlaceholder="Create a new setting"
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Chat Overlay - COMMENTED OUT */}
|
||||
{/*
|
||||
<div
|
||||
className={`fixed inset-x-0 bottom-0 z-50 transform transition-transform duration-300 ease-out md:hidden ${
|
||||
isMobileChatOpen ? "translate-y-0" : "translate-y-full"
|
||||
}`}
|
||||
>
|
||||
<div className="mx-4 mb-4 overflow-hidden rounded-xl border bg-background/95 shadow-lg backdrop-blur-sm">
|
||||
<div className="flex items-center justify-between border-b px-4 py-2">
|
||||
<span className="text-sm font-medium">AI Assistant</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsMobileChatOpen(false)}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-[50vh]">
|
||||
<ChatView
|
||||
variant="panel"
|
||||
hideSuggestions
|
||||
inputPlaceholder="Create a new setting"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isMobileChatOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/50 md:hidden"
|
||||
onClick={() => setIsMobileChatOpen(false)}
|
||||
/>
|
||||
)}
|
||||
*/}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@ -158,7 +158,6 @@ export function AppearanceTab() {
|
||||
<SelectContent>
|
||||
<SelectItem value="light">Light</SelectItem>
|
||||
<SelectItem value="dark">Dark</SelectItem>
|
||||
<SelectItem value="system">System</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user