refactor(chat): remove keyboard compatibility layer

This commit is contained in:
2026-07-16 14:36:33 +08:00
parent aec7cee9cf
commit 8b6bcab05c
9 changed files with 26 additions and 629 deletions
@@ -1,14 +1,11 @@
/* ChatInputBar 输入栏容器样式(与 Dart chat_input_bar.dart 对齐) */ /* ChatInputBar 输入栏容器样式(与 Dart chat_input_bar.dart 对齐) */
/* 键盘适配:--chat-input-lift 仅写入当前聊天输入栏。 */
.bar { .bar {
flex: 0 0 auto; flex: 0 0 auto;
padding: 0 padding: 0
calc(var(--chat-inline-padding, 16px) + var(--app-safe-right, 0px)) calc(var(--chat-inline-padding, 16px) + var(--app-safe-right, 0px))
calc( calc(
var(--spacing-lg, 16px) + var(--chat-input-lift, 0px) + var(--spacing-lg, 16px) + var(--app-safe-bottom, 0px)
var(--app-safe-bottom, 0px)
) )
calc(var(--chat-inline-padding, 16px) + var(--app-safe-left, 0px)); calc(var(--chat-inline-padding, 16px) + var(--app-safe-left, 0px));
background: transparent; background: transparent;
+2 -5
View File
@@ -4,7 +4,7 @@ import { useRef, useState } from "react";
import { useChatDispatch } from "@/stores/chat/chat-context"; import { useChatDispatch } from "@/stores/chat/chat-context";
import { Logger } from "@/utils/logger"; import { Logger } from "@/utils/logger";
import { useChatKeyboardAvoidance } from "../hooks/use-chat-keyboard-avoidance"; import { useChatKeyboardDiagnostics } from "../hooks/use-chat-keyboard-diagnostics";
import { ChatInputTextField } from "./chat-input-text-field"; import { ChatInputTextField } from "./chat-input-text-field";
import { ChatSendButton } from "./chat-send-button"; import { ChatSendButton } from "./chat-send-button";
import styles from "./chat-input-bar.module.css"; import styles from "./chat-input-bar.module.css";
@@ -26,10 +26,7 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
const hasContent = input.trim().length > 0; const hasContent = input.trim().length > 0;
useChatKeyboardAvoidance({ useChatKeyboardDiagnostics({ containerRef: barRef });
containerRef: barRef,
active: isFocused,
});
const handleInputChange = (value: string) => { const handleInputChange = (value: string) => {
setInput(value); setInput(value);
@@ -1,90 +0,0 @@
import { describe, expect, it } from "vitest";
import {
resolveChatKeyboardFallbackLift,
resolveChatKeyboardLift,
type ChatKeyboardViewportBaseline,
type ChatKeyboardViewportSnapshot,
} from "../chat-keyboard-geometry";
const baseline: ChatKeyboardViewportBaseline = {
layoutHeight: 800,
visualHeight: 760,
};
function makeSnapshot(
overrides: Partial<ChatKeyboardViewportSnapshot> = {},
): ChatKeyboardViewportSnapshot {
return {
layoutHeight: 800,
visualHeight: 760,
visualScale: 1,
virtualKeyboardHeight: 0,
...overrides,
};
}
describe("resolveChatKeyboardLift", () => {
it("does not add lift when the content viewport already resized", () => {
expect(
resolveChatKeyboardLift(
baseline,
makeSnapshot({ layoutHeight: 500, visualHeight: 500 }),
),
).toEqual({
keyboardVisible: true,
lift: 0,
source: "content-resize",
});
});
it("uses the visual viewport delta for an overlay keyboard", () => {
expect(
resolveChatKeyboardLift(
baseline,
makeSnapshot({ visualHeight: 460 }),
),
).toEqual({
keyboardVisible: true,
lift: 314,
source: "visual-viewport",
});
});
it("prefers Virtual Keyboard geometry when available", () => {
expect(
resolveChatKeyboardLift(
baseline,
makeSnapshot({
visualHeight: 520,
virtualKeyboardHeight: 320,
}),
),
).toEqual({
keyboardVisible: true,
lift: 334,
source: "virtual-keyboard",
});
});
it("ignores visual viewport changes caused by page zoom", () => {
expect(
resolveChatKeyboardLift(
baseline,
makeSnapshot({ visualHeight: 460, visualScale: 1.2 }),
),
).toEqual({
keyboardVisible: false,
lift: 0,
source: "none",
});
});
});
describe("resolveChatKeyboardFallbackLift", () => {
it("scales with viewport height and remains bounded", () => {
expect(resolveChatKeyboardFallbackLift(500)).toBe(260);
expect(resolveChatKeyboardFallbackLift(800)).toBe(336);
expect(resolveChatKeyboardFallbackLift(1_200)).toBe(380);
});
});
@@ -1,178 +0,0 @@
/* @vitest-environment jsdom */
import { act, useRef } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { BrowserDetector } from "@/utils/browser-detect";
import { useChatKeyboardAvoidance } from "../use-chat-keyboard-avoidance";
vi.mock("@/utils/browser-detect", () => ({
BrowserDetector: {
isInAppBrowser: vi.fn(() => true),
},
}));
vi.mock("@/utils/platform-detect", () => ({
PlatformDetector: {
isAndroid: vi.fn(() => true),
},
}));
class MockVisualViewport extends EventTarget {
height = 760;
offsetTop = 0;
scale = 1;
width = 390;
}
function Harness({ active }: { active: boolean }) {
const containerRef = useRef<HTMLDivElement>(null);
useChatKeyboardAvoidance({ active, containerRef });
return <div ref={containerRef} data-testid="chat-input" />;
}
describe("useChatKeyboardAvoidance", () => {
let container: HTMLDivElement;
let root: Root;
let visualViewport: MockVisualViewport;
let innerHeight: number;
beforeEach(() => {
vi.useFakeTimers();
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
.IS_REACT_ACT_ENVIRONMENT = true;
vi.mocked(BrowserDetector.isInAppBrowser).mockReturnValue(true);
innerHeight = 800;
visualViewport = new MockVisualViewport();
Object.defineProperty(window, "innerHeight", {
configurable: true,
get: () => innerHeight,
});
Object.defineProperty(window, "visualViewport", {
configurable: true,
value: visualViewport,
});
vi.stubGlobal(
"requestAnimationFrame",
vi.fn((callback: FrameRequestCallback) =>
window.setTimeout(() => callback(0), 0),
),
);
vi.stubGlobal(
"cancelAnimationFrame",
vi.fn((id: number) => window.clearTimeout(id)),
);
container = document.createElement("div");
document.body.append(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
vi.useRealTimers();
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it("uses a one-shot adaptive fallback without polling", () => {
const intervalSpy = vi.spyOn(window, "setInterval");
renderHarness(root, false);
renderHarness(root, true);
act(() => vi.advanceTimersByTime(350));
expect(getInput(container).style.getPropertyValue("--chat-input-lift")).toBe(
"336px",
);
expect(intervalSpy).not.toHaveBeenCalled();
});
it("does not use the fallback in a regular Android browser", () => {
vi.mocked(BrowserDetector.isInAppBrowser).mockReturnValue(false);
renderHarness(root, false);
renderHarness(root, true);
act(() => vi.advanceTimersByTime(350));
expect(getInput(container).style.getPropertyValue("--chat-input-lift")).toBe(
"",
);
});
it("uses Visual Viewport geometry and clears after keyboard dismissal", () => {
renderHarness(root, false);
renderHarness(root, true);
visualViewport.height = 460;
act(() => {
visualViewport.dispatchEvent(new Event("resize"));
vi.runOnlyPendingTimers();
});
expect(getInput(container).style.getPropertyValue("--chat-input-lift")).toBe(
"314px",
);
visualViewport.height = 760;
act(() => {
visualViewport.dispatchEvent(new Event("resize"));
vi.runOnlyPendingTimers();
});
expect(getInput(container).style.getPropertyValue("--chat-input-lift")).toBe(
"0px",
);
act(() => vi.advanceTimersByTime(1_000));
expect(getInput(container).style.getPropertyValue("--chat-input-lift")).toBe(
"0px",
);
});
it("does not add fallback lift when the layout viewport resized", () => {
renderHarness(root, false);
renderHarness(root, true);
innerHeight = 500;
visualViewport.height = 500;
act(() => {
window.dispatchEvent(new Event("resize"));
vi.advanceTimersByTime(350);
});
expect(getInput(container).style.getPropertyValue("--chat-input-lift")).toBe(
"0px",
);
});
it("removes the local lift when focus leaves or the hook unmounts", () => {
renderHarness(root, false);
renderHarness(root, true);
act(() => vi.advanceTimersByTime(350));
expect(getInput(container).style.getPropertyValue("--chat-input-lift")).toBe(
"336px",
);
renderHarness(root, false);
expect(getInput(container).style.getPropertyValue("--chat-input-lift")).toBe(
"",
);
act(() => root.unmount());
expect(container.querySelector('[data-testid="chat-input"]')).toBeNull();
root = createRoot(container);
});
});
function renderHarness(root: Root, active: boolean): void {
act(() => root.render(<Harness active={active} />));
}
function getInput(container: HTMLElement): HTMLDivElement {
const input = container.querySelector<HTMLDivElement>(
'[data-testid="chat-input"]',
);
if (!input) throw new Error("Chat input test node was not rendered");
return input;
}
@@ -68,6 +68,8 @@ describe("chat keyboard diagnostics", () => {
afterEach(() => { afterEach(() => {
window.__keyboardDebugCleanup?.(); window.__keyboardDebugCleanup?.();
document.documentElement.style.removeProperty("--app-viewport-height");
document.documentElement.style.removeProperty("--app-visible-height");
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
@@ -79,7 +81,8 @@ describe("chat keyboard diagnostics", () => {
it("records viewport events and exposes manual snapshot helpers", () => { it("records viewport events and exposes manual snapshot helpers", () => {
const input = document.createElement("textarea"); const input = document.createElement("textarea");
input.style.setProperty("--chat-input-lift", "314px"); document.documentElement.style.setProperty("--app-viewport-height", "800px");
document.documentElement.style.setProperty("--app-visible-height", "460px");
document.body.append(input); document.body.append(input);
input.focus(); input.focus();
const inputRef = { current: input } as RefObject<HTMLElement | null>; const inputRef = { current: input } as RefObject<HTMLElement | null>;
@@ -96,7 +99,10 @@ describe("chat keyboard diagnostics", () => {
virtualKeyboardRect: { height: 300 }, virtualKeyboardRect: { height: 300 },
activeElement: "TEXTAREA", activeElement: "TEXTAREA",
activeFocused: true, activeFocused: true,
inputLift: "314px", inputBarTop: 0,
inputBarBottom: 0,
appViewportHeight: "800px",
appVisibleHeight: "460px",
platform: { name: "android", deviceModel: "PLB110" }, platform: { name: "android", deviceModel: "PLB110" },
browser: { inAppBrowserName: "facebook" }, browser: { inAppBrowserName: "facebook" },
}); });
@@ -1,95 +0,0 @@
export const CHAT_KEYBOARD_FOCUS_GAP_PX = 14;
export const CHAT_KEYBOARD_VISIBLE_THRESHOLD_PX = 80;
export const CHAT_KEYBOARD_ZOOM_THRESHOLD = 1.05;
export const CHAT_KEYBOARD_FALLBACK_RATIO = 0.42;
export const CHAT_KEYBOARD_FALLBACK_MIN_PX = 260;
export const CHAT_KEYBOARD_FALLBACK_MAX_PX = 380;
const CHAT_KEYBOARD_MAX_LIFT_RATIO = 0.55;
export interface ChatKeyboardViewportBaseline {
layoutHeight: number;
visualHeight: number;
}
export interface ChatKeyboardViewportSnapshot {
layoutHeight: number;
visualHeight: number;
visualScale: number;
virtualKeyboardHeight: number;
}
export type ChatKeyboardLiftSource =
| "content-resize"
| "visual-viewport"
| "virtual-keyboard"
| "none";
export interface ChatKeyboardLiftResolution {
keyboardVisible: boolean;
lift: number;
source: ChatKeyboardLiftSource;
}
export function resolveChatKeyboardLift(
baseline: ChatKeyboardViewportBaseline,
snapshot: ChatKeyboardViewportSnapshot,
): ChatKeyboardLiftResolution {
const contentResize = baseline.layoutHeight - snapshot.layoutHeight;
if (contentResize > CHAT_KEYBOARD_VISIBLE_THRESHOLD_PX) {
return {
keyboardVisible: true,
lift: 0,
source: "content-resize",
};
}
const virtualKeyboardHeight = Math.max(
0,
snapshot.virtualKeyboardHeight,
);
const visualViewportHeight =
snapshot.visualScale <= CHAT_KEYBOARD_ZOOM_THRESHOLD
? Math.max(0, baseline.visualHeight - snapshot.visualHeight)
: 0;
const occludedHeight = Math.max(
virtualKeyboardHeight,
visualViewportHeight,
);
if (occludedHeight <= CHAT_KEYBOARD_VISIBLE_THRESHOLD_PX) {
return {
keyboardVisible: false,
lift: 0,
source: "none",
};
}
const maxLift = Math.round(
baseline.layoutHeight * CHAT_KEYBOARD_MAX_LIFT_RATIO,
);
return {
keyboardVisible: true,
lift: Math.min(
maxLift,
Math.round(occludedHeight + CHAT_KEYBOARD_FOCUS_GAP_PX),
),
source:
virtualKeyboardHeight >= visualViewportHeight
? "virtual-keyboard"
: "visual-viewport",
};
}
export function resolveChatKeyboardFallbackLift(
layoutHeight: number,
): number {
return Math.min(
CHAT_KEYBOARD_FALLBACK_MAX_PX,
Math.max(
CHAT_KEYBOARD_FALLBACK_MIN_PX,
Math.round(layoutHeight * CHAT_KEYBOARD_FALLBACK_RATIO),
),
);
}
@@ -1,246 +0,0 @@
"use client";
import { useEffect, useRef, type RefObject } from "react";
import { BrowserDetector } from "@/utils/browser-detect";
import { Logger } from "@/utils/logger";
import { PlatformDetector } from "@/utils/platform-detect";
import {
resolveChatKeyboardFallbackLift,
resolveChatKeyboardLift,
type ChatKeyboardLiftResolution,
type ChatKeyboardViewportBaseline,
type ChatKeyboardViewportSnapshot,
} from "./chat-keyboard-geometry";
import { useChatKeyboardDiagnostics } from "./use-chat-keyboard-diagnostics";
const CHAT_INPUT_LIFT_VAR = "--chat-input-lift";
const CHAT_KEYBOARD_FALLBACK_DELAY_MS = 350;
const log = new Logger("UseChatKeyboardAvoidance");
interface VirtualKeyboardLike extends EventTarget {
readonly boundingRect?: Pick<DOMRectReadOnly, "height">;
}
interface NavigatorWithVirtualKeyboard extends Navigator {
readonly virtualKeyboard?: VirtualKeyboardLike;
}
export interface UseChatKeyboardAvoidanceOptions {
active: boolean;
containerRef: RefObject<HTMLElement | null>;
}
export function useChatKeyboardAvoidance({
active,
containerRef,
}: UseChatKeyboardAvoidanceOptions): void {
const activeRef = useRef(active);
const baselineRef = useRef<ChatKeyboardViewportBaseline | null>(null);
useChatKeyboardDiagnostics({ containerRef });
useEffect(() => {
activeRef.current = active;
}, [active]);
useEffect(() => {
if (typeof window === "undefined" || !PlatformDetector.isAndroid()) {
return;
}
const updateBaseline = () => {
if (activeRef.current) return;
baselineRef.current = readViewportBaseline();
};
const visualViewport = window.visualViewport;
updateBaseline();
window.addEventListener("resize", updateBaseline);
window.addEventListener("orientationchange", updateBaseline);
visualViewport?.addEventListener("resize", updateBaseline);
visualViewport?.addEventListener("scroll", updateBaseline);
return () => {
window.removeEventListener("resize", updateBaseline);
window.removeEventListener("orientationchange", updateBaseline);
visualViewport?.removeEventListener("resize", updateBaseline);
visualViewport?.removeEventListener("scroll", updateBaseline);
};
}, []);
useEffect(() => {
const container = containerRef.current;
if (typeof window === "undefined" || !container) return;
if (!active || !PlatformDetector.isAndroid()) {
clearInputLift(container);
if (!active) baselineRef.current = readViewportBaseline();
return;
}
const baseline = baselineRef.current ?? readViewportBaseline();
const visualViewport = window.visualViewport;
const virtualKeyboard = getVirtualKeyboard();
const fallbackAllowed = BrowserDetector.isInAppBrowser();
let frameId: number | null = null;
let fallbackTimerId: number | null = null;
let lastLift = -1;
let pendingCanDismiss = false;
let pendingReason = "initial";
let keyboardObserved = false;
let fallbackApplied = false;
let sessionDismissed = false;
const applyLift = (
lift: number,
source: ChatKeyboardLiftResolution["source"] | "fallback" | "reset",
) => {
const nextLift = Math.max(0, Math.round(lift));
if (nextLift === lastLift) return;
lastLift = nextLift;
container.style.setProperty(CHAT_INPUT_LIFT_VAR, `${nextLift}px`);
log.debug("[chat-keyboard] apply", {
source,
lift: nextLift,
});
};
const clearFallbackTimer = () => {
if (fallbackTimerId == null) return;
window.clearTimeout(fallbackTimerId);
fallbackTimerId = null;
};
const measure = (reason: string, canDismiss: boolean) => {
if (sessionDismissed) return;
const resolution = resolveChatKeyboardLift(
baseline,
readViewportSnapshot(),
);
if (resolution.keyboardVisible) {
keyboardObserved = true;
applyLift(resolution.lift, resolution.source);
return;
}
if ((keyboardObserved || fallbackApplied) && canDismiss) {
sessionDismissed = true;
clearFallbackTimer();
applyLift(0, "reset");
log.debug("[chat-keyboard] dismissed", { reason });
}
};
const scheduleMeasure = (reason: string, canDismiss: boolean) => {
pendingReason = reason;
pendingCanDismiss ||= canDismiss;
if (frameId != null) return;
frameId = window.requestAnimationFrame(() => {
frameId = null;
const nextReason = pendingReason;
const nextCanDismiss = pendingCanDismiss;
pendingCanDismiss = false;
measure(nextReason, nextCanDismiss);
});
};
const handleVisualResize = () => scheduleMeasure("visual-resize", true);
const handleVisualScroll = () => scheduleMeasure("visual-scroll", false);
const handleWindowResize = () => scheduleMeasure("window-resize", true);
const handleVirtualKeyboardChange = () =>
scheduleMeasure("virtual-keyboard", true);
const resetSession = (reason: string) => {
sessionDismissed = true;
clearFallbackTimer();
applyLift(0, "reset");
log.debug("[chat-keyboard] reset", { reason });
};
const handleOrientationChange = () => resetSession("orientationchange");
const handleVisibilityChange = () => {
if (document.visibilityState === "hidden") {
resetSession("visibilitychange");
}
};
visualViewport?.addEventListener("resize", handleVisualResize);
visualViewport?.addEventListener("scroll", handleVisualScroll);
virtualKeyboard?.addEventListener(
"geometrychange",
handleVirtualKeyboardChange,
);
window.addEventListener("resize", handleWindowResize);
window.addEventListener("orientationchange", handleOrientationChange);
document.addEventListener("visibilitychange", handleVisibilityChange);
scheduleMeasure("focus", false);
fallbackTimerId = window.setTimeout(() => {
fallbackTimerId = null;
if (!activeRef.current || sessionDismissed) return;
const resolution = resolveChatKeyboardLift(
baseline,
readViewportSnapshot(),
);
if (resolution.keyboardVisible) {
keyboardObserved = true;
applyLift(resolution.lift, resolution.source);
return;
}
if (!fallbackAllowed) return;
fallbackApplied = true;
applyLift(
resolveChatKeyboardFallbackLift(baseline.layoutHeight),
"fallback",
);
}, CHAT_KEYBOARD_FALLBACK_DELAY_MS);
return () => {
if (frameId != null) window.cancelAnimationFrame(frameId);
clearFallbackTimer();
visualViewport?.removeEventListener("resize", handleVisualResize);
visualViewport?.removeEventListener("scroll", handleVisualScroll);
virtualKeyboard?.removeEventListener(
"geometrychange",
handleVirtualKeyboardChange,
);
window.removeEventListener("resize", handleWindowResize);
window.removeEventListener("orientationchange", handleOrientationChange);
document.removeEventListener("visibilitychange", handleVisibilityChange);
clearInputLift(container);
};
}, [active, containerRef]);
}
function readViewportBaseline(): ChatKeyboardViewportBaseline {
return {
layoutHeight: window.innerHeight,
visualHeight: window.visualViewport?.height ?? window.innerHeight,
};
}
function readViewportSnapshot(): ChatKeyboardViewportSnapshot {
const visualViewport = window.visualViewport;
return {
layoutHeight: window.innerHeight,
visualHeight: visualViewport?.height ?? window.innerHeight,
visualScale: visualViewport?.scale ?? 1,
virtualKeyboardHeight: getVirtualKeyboard()?.boundingRect?.height ?? 0,
};
}
function getVirtualKeyboard(): VirtualKeyboardLike | null {
return (
(navigator as NavigatorWithVirtualKeyboard).virtualKeyboard ?? null
);
}
function clearInputLift(container: HTMLElement): void {
container.style.removeProperty(CHAT_INPUT_LIFT_VAR);
}
@@ -6,7 +6,6 @@ import { BrowserDetector } from "@/utils/browser-detect";
import { PlatformDetector } from "@/utils/platform-detect"; import { PlatformDetector } from "@/utils/platform-detect";
const CHAT_KEYBOARD_DEBUG_PARAM = "keyboard_debug"; const CHAT_KEYBOARD_DEBUG_PARAM = "keyboard_debug";
const CHAT_INPUT_LIFT_VAR = "--chat-input-lift";
interface VirtualKeyboardLike extends EventTarget { interface VirtualKeyboardLike extends EventTarget {
readonly boundingRect?: Pick< readonly boundingRect?: Pick<
@@ -41,7 +40,11 @@ export interface ChatKeyboardDebugSnapshot {
activeElement: string | null; activeElement: string | null;
activeBottom: number | null; activeBottom: number | null;
activeFocused: boolean; activeFocused: boolean;
inputLift: string | null; inputBarTop: number | null;
inputBarBottom: number | null;
appViewportHeight: string | null;
appVisibleHeight: string | null;
appVisibleOffsetTop: string | null;
viewportMeta: string | null; viewportMeta: string | null;
platform: { platform: {
name: string; name: string;
@@ -151,6 +154,8 @@ function createChatKeyboardDebugSnapshot(
: null; : null;
const platform = PlatformDetector.getPlatformInfo(); const platform = PlatformDetector.getPlatformInfo();
const browser = BrowserDetector.getBrowserInfo(); const browser = BrowserDetector.getBrowserInfo();
const rootStyle = document.documentElement.style;
const inputBarRect = containerRef.current?.getBoundingClientRect() ?? null;
return { return {
event, event,
@@ -176,8 +181,14 @@ function createChatKeyboardDebugSnapshot(
activeElement: activeElement?.tagName ?? null, activeElement: activeElement?.tagName ?? null,
activeBottom: activeRect?.bottom ?? null, activeBottom: activeRect?.bottom ?? null,
activeFocused: activeElement !== document.body, activeFocused: activeElement !== document.body,
inputLift: inputBarTop: inputBarRect?.top ?? null,
containerRef.current?.style.getPropertyValue(CHAT_INPUT_LIFT_VAR) || null, inputBarBottom: inputBarRect?.bottom ?? null,
appViewportHeight:
rootStyle.getPropertyValue("--app-viewport-height") || null,
appVisibleHeight:
rootStyle.getPropertyValue("--app-visible-height") || null,
appVisibleOffsetTop:
rootStyle.getPropertyValue("--app-visible-offset-top") || null,
viewportMeta: viewportMeta:
document.querySelector<HTMLMetaElement>('meta[name="viewport"]') document.querySelector<HTMLMetaElement>('meta[name="viewport"]')
?.content ?? null, ?.content ?? null,
-5
View File
@@ -1,12 +1,7 @@
import type { Viewport } from "next";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { ChatRouteProviders } from "@/providers/chat-route-providers"; import { ChatRouteProviders } from "@/providers/chat-route-providers";
export const viewport: Viewport = {
interactiveWidget: "resizes-content",
};
export default function ChatLayout({ children }: { children: ReactNode }) { export default function ChatLayout({ children }: { children: ReactNode }) {
return <ChatRouteProviders>{children}</ChatRouteProviders>; return <ChatRouteProviders>{children}</ChatRouteProviders>;
} }