mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: refactor ChatInput with autocomplete, suggestion management, and settings integration
Extract word detection and suggestion logic into reusable utilities and hooks. Add FloatingOverlay and Autocomplete components for intelligent input assistance. Integrate settings panel and abort button directly into ChatInput component. Simplify SessionHeader by removing settings dialog (now in ChatInput).
This commit is contained in:
@@ -1,9 +1,5 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
import type { ModelMode, PermissionMode, Session } from '@/types/api'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { getTelegramWebApp } from '@/hooks/useTelegram'
|
||||
import { useMemo } from 'react'
|
||||
import type { Session } from '@/types/api'
|
||||
|
||||
function getSessionTitle(session: Session): string {
|
||||
if (session.metadata?.name) {
|
||||
@@ -16,49 +12,36 @@ function getSessionTitle(session: Session): string {
|
||||
return session.id.slice(0, 8)
|
||||
}
|
||||
|
||||
function toPermissionMode(mode: PermissionMode): 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' {
|
||||
if (mode === 'acceptEdits' || mode === 'bypassPermissions' || mode === 'plan') return mode
|
||||
return 'default'
|
||||
}
|
||||
|
||||
function toModelMode(mode: ModelMode): 'default' | 'sonnet' | 'opus' {
|
||||
if (mode === 'sonnet' || mode === 'opus') return mode
|
||||
return 'default'
|
||||
}
|
||||
|
||||
export function SessionHeader(props: {
|
||||
api: ApiClient
|
||||
session: Session
|
||||
onBack: () => void
|
||||
onRefresh: () => void
|
||||
}) {
|
||||
const title = useMemo(() => getSessionTitle(props.session), [props.session])
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
const [isWorking, setIsWorking] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const permissionMode = toPermissionMode(props.session.permissionMode)
|
||||
const modelMode = toModelMode(props.session.modelMode)
|
||||
const controlsDisabled = isWorking || !props.session.active
|
||||
|
||||
async function run(action: () => Promise<void>) {
|
||||
setIsWorking(true)
|
||||
setError(null)
|
||||
try {
|
||||
await action()
|
||||
getTelegramWebApp()?.HapticFeedback?.notificationOccurred('success')
|
||||
props.onRefresh()
|
||||
setSettingsOpen(false)
|
||||
} catch (e) {
|
||||
getTelegramWebApp()?.HapticFeedback?.notificationOccurred('error')
|
||||
setError(e instanceof Error ? e.message : 'Request failed')
|
||||
} finally {
|
||||
setIsWorking(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 bg-[var(--app-bg)] p-3">
|
||||
{/* Back button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onBack}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-full text-[var(--app-hint)] transition-colors hover:bg-[var(--app-secondary-bg)] hover:text-[var(--app-fg)]"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Session info */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span
|
||||
@@ -81,80 +64,6 @@ export function SessionHeader(props: {
|
||||
{props.session.metadata?.host ? `Host: ${props.session.metadata.host}` : props.session.id}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Dialog open={settingsOpen} onOpenChange={setSettingsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary" size="sm">
|
||||
⚙️
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Session Settings</DialogTitle>
|
||||
<DialogDescription className="truncate">
|
||||
{props.session.metadata?.path ?? props.session.id}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="mt-3 flex flex-col gap-4">
|
||||
<div>
|
||||
<div className="mb-2 text-sm font-medium">Permission Mode</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(['default', 'acceptEdits', 'bypassPermissions', 'plan'] as const).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
size="sm"
|
||||
variant={permissionMode === mode ? 'default' : 'secondary'}
|
||||
disabled={controlsDisabled}
|
||||
onClick={() => run(() => props.api.setPermissionMode(props.session.id, mode))}
|
||||
>
|
||||
{mode}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="mb-2 text-sm font-medium">Model</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(['default', 'sonnet', 'opus'] as const).map((model) => (
|
||||
<Button
|
||||
key={model}
|
||||
size="sm"
|
||||
variant={modelMode === model ? 'default' : 'secondary'}
|
||||
disabled={controlsDisabled}
|
||||
onClick={() => run(() => props.api.setModelMode(props.session.id, model))}
|
||||
>
|
||||
{model}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md bg-[var(--app-subtle-bg)] p-2 text-xs text-[var(--app-hint)]">
|
||||
<div>Path: {props.session.metadata?.path ?? '(unknown)'}</div>
|
||||
<div>Host: {props.session.metadata?.host ?? '(unknown)'}</div>
|
||||
<div>Status: {props.session.active ? 'Active' : 'Inactive'}</div>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<div className="text-sm text-red-600">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={controlsDisabled}
|
||||
onClick={() => run(() => props.api.abortSession(props.session.id))}
|
||||
>
|
||||
🛑 Abort Session
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user