feat(app): show archived agent callout with unarchive button
When viewing an archived agent, display a callout in place of the input area that matches the message input styling. The Unarchive button calls refreshAgent which auto-unarchives server-side.
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { View, Text } from 'react-native'
|
||||||
|
import { StyleSheet } from 'react-native-unistyles'
|
||||||
|
import Animated from 'react-native-reanimated'
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
|
import { FOOTER_HEIGHT, MAX_CONTENT_WIDTH } from '@/constants/layout'
|
||||||
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from '@/runtime/host-runtime'
|
||||||
|
import { useKeyboardShiftStyle } from '@/hooks/use-keyboard-shift-style'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import type { Theme } from '@/styles/theme'
|
||||||
|
|
||||||
|
interface ArchivedAgentCalloutProps {
|
||||||
|
serverId: string
|
||||||
|
agentId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ArchivedAgentCallout({ serverId, agentId }: ArchivedAgentCalloutProps) {
|
||||||
|
const insets = useSafeAreaInsets()
|
||||||
|
const client = useHostRuntimeClient(serverId)
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId)
|
||||||
|
const [isUnarchiving, setIsUnarchiving] = useState(false)
|
||||||
|
|
||||||
|
const { style: keyboardAnimatedStyle } = useKeyboardShiftStyle({ mode: 'translate' })
|
||||||
|
|
||||||
|
async function handleUnarchive() {
|
||||||
|
if (!client || !isConnected || isUnarchiving) return
|
||||||
|
setIsUnarchiving(true)
|
||||||
|
try {
|
||||||
|
await client.refreshAgent(agentId)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ArchivedAgentCallout] Failed to unarchive agent:', error)
|
||||||
|
setIsUnarchiving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={[styles.container, { paddingBottom: insets.bottom }, keyboardAnimatedStyle]}
|
||||||
|
>
|
||||||
|
<View style={styles.inputAreaContainer}>
|
||||||
|
<View style={styles.inputAreaContent}>
|
||||||
|
<View style={styles.callout}>
|
||||||
|
<Text style={styles.calloutText}>This agent is archived</Text>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onPress={handleUnarchive}
|
||||||
|
disabled={!isConnected || isUnarchiving}
|
||||||
|
>
|
||||||
|
Unarchive
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create(((theme: Theme) => ({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
inputAreaContainer: {
|
||||||
|
position: 'relative',
|
||||||
|
minHeight: FOOTER_HEIGHT,
|
||||||
|
marginHorizontal: 'auto',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
overflow: 'visible',
|
||||||
|
padding: theme.spacing[4],
|
||||||
|
},
|
||||||
|
inputAreaContent: {
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: MAX_CONTENT_WIDTH,
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: theme.spacing[3],
|
||||||
|
backgroundColor: theme.colors.surface1,
|
||||||
|
borderWidth: theme.borderWidth[1],
|
||||||
|
borderColor: theme.colors.borderAccent,
|
||||||
|
borderRadius: theme.borderRadius['2xl'],
|
||||||
|
paddingVertical: {
|
||||||
|
xs: theme.spacing[3],
|
||||||
|
md: theme.spacing[4],
|
||||||
|
},
|
||||||
|
paddingHorizontal: {
|
||||||
|
xs: theme.spacing[4],
|
||||||
|
md: theme.spacing[6],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calloutText: {
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
fontSize: theme.fontSize.base,
|
||||||
|
},
|
||||||
|
})) as any) as Record<string, any>
|
||||||
@@ -6,6 +6,7 @@ import { Bot } from "lucide-react-native";
|
|||||||
import invariant from "tiny-invariant";
|
import invariant from "tiny-invariant";
|
||||||
import { AgentStreamView, type AgentStreamViewHandle } from "@/components/agent-stream-view";
|
import { AgentStreamView, type AgentStreamViewHandle } from "@/components/agent-stream-view";
|
||||||
import { AgentInputArea } from "@/components/agent-input-area";
|
import { AgentInputArea } from "@/components/agent-input-area";
|
||||||
|
import { ArchivedAgentCallout } from "@/components/archived-agent-callout";
|
||||||
import { FileDropZone } from "@/components/file-drop-zone";
|
import { FileDropZone } from "@/components/file-drop-zone";
|
||||||
import type { ImageAttachment } from "@/components/message-input";
|
import type { ImageAttachment } from "@/components/message-input";
|
||||||
import { ToastViewport, useToastHost } from "@/components/toast-host";
|
import { ToastViewport, useToastHost } from "@/components/toast-host";
|
||||||
@@ -825,6 +826,8 @@ function AgentPanelBody({
|
|||||||
streamViewRef.current?.scrollToBottom("message-sent");
|
streamViewRef.current?.scrollToBottom("message-sent");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : agentId && agent?.archivedAt ? (
|
||||||
|
<ArchivedAgentCallout serverId={serverId} agentId={agentId} />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{viewState.tag === "ready" &&
|
{viewState.tag === "ready" &&
|
||||||
|
|||||||
Reference in New Issue
Block a user