refactor(contextual): drop 'floating' branch from useAIChat and useChatStream
UIChatContext is now 'global' | 'project' only. Floating domain signal, scope field, and onDomainSignal callback removed. ChatInputBox no longer defines floating variant. TaskBriefChat migrated to contextual mode.
This commit is contained in:
@@ -9,7 +9,7 @@ export interface ChatInputBoxHandle {
|
||||
focus: () => void;
|
||||
}
|
||||
|
||||
type ChatInputBoxVariant = 'panel' | 'floating' | 'comment';
|
||||
type ChatInputBoxVariant = 'panel' | 'comment';
|
||||
|
||||
interface ChatInputBoxProps {
|
||||
cacheKey: string;
|
||||
@@ -27,12 +27,6 @@ const VARIANT_STYLES = {
|
||||
button: 'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground shadow-sm transition-all hover:bg-primary/90 active:scale-95 disabled:opacity-40 disabled:cursor-not-allowed disabled:active:scale-100',
|
||||
iconSize: 16,
|
||||
},
|
||||
floating: {
|
||||
container: 'flex items-center gap-2 px-3 py-2.5',
|
||||
textarea: 'flex-1 resize-none bg-transparent text-sm placeholder:text-muted-foreground/60 outline-none max-h-20 overflow-y-auto',
|
||||
button: 'flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground shadow-sm transition-all hover:bg-primary/90 active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed',
|
||||
iconSize: 14,
|
||||
},
|
||||
comment: {
|
||||
container: 'flex items-center gap-2 px-3 py-2',
|
||||
textarea: 'flex-1 resize-none bg-transparent text-sm placeholder:text-muted-foreground outline-none max-h-32 overflow-y-auto',
|
||||
@@ -49,7 +43,7 @@ export const ChatInputBox = forwardRef<ChatInputBoxHandle, ChatInputBoxProps>(
|
||||
const valueRef = useRef(value);
|
||||
valueRef.current = value;
|
||||
|
||||
// Re-init when the cache key changes (context switches in FloatingChat).
|
||||
// Re-init when the cache key changes (context switches).
|
||||
const prevKeyRef = useRef(cacheKey);
|
||||
useEffect(() => {
|
||||
if (prevKeyRef.current !== cacheKey) {
|
||||
|
||||
@@ -177,7 +177,7 @@ export function TaskBriefChat({ taskId, projectId, initialBriefing, onBriefingRe
|
||||
message: trimmed,
|
||||
conversationHistory,
|
||||
sessionId,
|
||||
mode: 'floating',
|
||||
mode: 'contextual',
|
||||
scope: { type: 'task', id: taskId },
|
||||
briefMode: true,
|
||||
briefingContext: briefingText || undefined,
|
||||
|
||||
@@ -1,33 +1,16 @@
|
||||
import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
||||
import { trpc } from '@/lib/trpc';
|
||||
|
||||
export type FloatingDomainSignal =
|
||||
| 'tasks'
|
||||
| 'notes'
|
||||
| 'timelines'
|
||||
| 'projects'
|
||||
| {
|
||||
type: 'task' | 'timeline' | 'project' | 'note' | 'node';
|
||||
id?: string | null;
|
||||
section?: 'task' | 'timeline' | 'note' | null;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Renderer-only context describing where the user is in the UI.
|
||||
* Retained for call-site compatibility; mode/scope fields support v3 routing.
|
||||
*/
|
||||
export interface UIChatContext {
|
||||
type: 'global' | 'project' | 'floating';
|
||||
type: 'global' | 'project';
|
||||
projectId?: string;
|
||||
/** For floating mode — the entity scope to pass to the backend. */
|
||||
scope?: {
|
||||
type: 'task' | 'project' | 'note' | 'timeline';
|
||||
id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
@@ -47,10 +30,6 @@ interface UseAIChatReturn {
|
||||
cacheKey: string;
|
||||
}
|
||||
|
||||
interface UseAIChatOptions {
|
||||
onDomainSignal?: (domain: FloatingDomainSignal) => void;
|
||||
}
|
||||
|
||||
interface CachedChatState {
|
||||
messages: ChatMessage[];
|
||||
/** Written by ChatInputBox; read on mount to restore draft. Not written by this hook. */
|
||||
@@ -62,11 +41,7 @@ const chatSessionCache = new Map<string, CachedChatState>();
|
||||
|
||||
function getContextCacheKey(ctx: UIChatContext): string {
|
||||
if (ctx.type === 'global') return 'global';
|
||||
if (ctx.type === 'project') return `project:${ctx.projectId ?? ''}`;
|
||||
|
||||
// Floating chat should keep a single continuous session while the panel is open,
|
||||
// even when route/section context changes due floating-domain navigation.
|
||||
return 'floating';
|
||||
return `project:${ctx.projectId ?? ''}`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -121,10 +96,10 @@ export function parseMutationsToEntityTags(mutations: unknown[] | undefined): st
|
||||
// Hook
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function useAIChat(defaultContext: UIChatContext, options?: UseAIChatOptions): UseAIChatReturn {
|
||||
export function useAIChat(defaultContext: UIChatContext): UseAIChatReturn {
|
||||
const contextCacheKey = useMemo(
|
||||
() => getContextCacheKey(defaultContext),
|
||||
[defaultContext.type, defaultContext.projectId, defaultContext.scope?.type, defaultContext.scope?.id],
|
||||
[defaultContext.type, defaultContext.projectId],
|
||||
);
|
||||
|
||||
const [messages, setMessages] = useState<ChatMessage[]>(
|
||||
@@ -148,9 +123,6 @@ export function useAIChat(defaultContext: UIChatContext, options?: UseAIChatOpti
|
||||
messagesRef.current = messages;
|
||||
const sessionIdRef = useRef(sessionId);
|
||||
sessionIdRef.current = sessionId;
|
||||
const onDomainSignalRef = useRef(options?.onDomainSignal);
|
||||
onDomainSignalRef.current = options?.onDomainSignal;
|
||||
|
||||
// Keep local state aligned when the chat context changes in-place.
|
||||
useEffect(() => {
|
||||
const cached = chatSessionCache.get(contextCacheKey);
|
||||
@@ -234,9 +206,6 @@ export function useAIChat(defaultContext: UIChatContext, options?: UseAIChatOpti
|
||||
break;
|
||||
}
|
||||
|
||||
case 'floating_domain':
|
||||
onDomainSignalRef.current?.(event.domain);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -246,17 +215,12 @@ export function useAIChat(defaultContext: UIChatContext, options?: UseAIChatOpti
|
||||
content: m.content,
|
||||
}));
|
||||
|
||||
const isFloating = ctx.type === 'floating';
|
||||
|
||||
chatMutationRef.current.mutate(
|
||||
{
|
||||
requestId,
|
||||
message: trimmed,
|
||||
conversationHistory,
|
||||
sessionId: sessionIdRef.current,
|
||||
...(isFloating && ctx.scope
|
||||
? { mode: 'floating' as const, scope: ctx.scope }
|
||||
: {}),
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
|
||||
@@ -14,15 +14,12 @@ export interface UseChatStreamArgs {
|
||||
onAssistantMessage: (msg: ChatMessage) => void;
|
||||
/** Called when the request fails. */
|
||||
onError: (msg: ChatMessage) => void;
|
||||
/** Optional: legacy floating_domain pivot signal. Kept until M6 removes floating. */
|
||||
onDomainSignal?: (domain: unknown) => void;
|
||||
}
|
||||
|
||||
export function useChatStream({
|
||||
sessionId,
|
||||
onAssistantMessage,
|
||||
onError,
|
||||
onDomainSignal,
|
||||
}: UseChatStreamArgs) {
|
||||
const [isStreaming, setIsStreaming] = useState(false);
|
||||
const [streamingContent, setStreamingContent] = useState('');
|
||||
@@ -30,9 +27,6 @@ export function useChatStream({
|
||||
const mutation = trpc.ai.chat.useMutation();
|
||||
const mutationRef = useRef(mutation);
|
||||
mutationRef.current = mutation;
|
||||
const domainRef = useRef(onDomainSignal);
|
||||
domainRef.current = onDomainSignal;
|
||||
|
||||
const send = useCallback(
|
||||
(args: {
|
||||
message: string;
|
||||
@@ -67,9 +61,6 @@ export function useChatStream({
|
||||
unsubscribe();
|
||||
break;
|
||||
}
|
||||
case 'floating_domain':
|
||||
domainRef.current?.(event.domain);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user