Restore Ultracite + fix sidebar (#1233)
This commit is contained in:
parent
8fbfc253fa
commit
947ed094a6
177 changed files with 6908 additions and 8306 deletions
|
|
@ -1,15 +1,15 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import useSWR from 'swr';
|
||||
import type { UIArtifact } from '@/components/artifact';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo } from "react";
|
||||
import useSWR from "swr";
|
||||
import type { UIArtifact } from "@/components/artifact";
|
||||
|
||||
export const initialArtifactData: UIArtifact = {
|
||||
documentId: 'init',
|
||||
content: '',
|
||||
kind: 'text',
|
||||
title: '',
|
||||
status: 'idle',
|
||||
documentId: "init",
|
||||
content: "",
|
||||
kind: "text",
|
||||
title: "",
|
||||
status: "idle",
|
||||
isVisible: false,
|
||||
boundingBox: {
|
||||
top: 0,
|
||||
|
|
@ -22,12 +22,14 @@ export const initialArtifactData: UIArtifact = {
|
|||
type Selector<T> = (state: UIArtifact) => T;
|
||||
|
||||
export function useArtifactSelector<Selected>(selector: Selector<Selected>) {
|
||||
const { data: localArtifact } = useSWR<UIArtifact>('artifact', null, {
|
||||
const { data: localArtifact } = useSWR<UIArtifact>("artifact", null, {
|
||||
fallbackData: initialArtifactData,
|
||||
});
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (!localArtifact) return selector(initialArtifactData);
|
||||
if (!localArtifact) {
|
||||
return selector(initialArtifactData);
|
||||
}
|
||||
return selector(localArtifact);
|
||||
}, [localArtifact, selector]);
|
||||
|
||||
|
|
@ -36,15 +38,17 @@ export function useArtifactSelector<Selected>(selector: Selector<Selected>) {
|
|||
|
||||
export function useArtifact() {
|
||||
const { data: localArtifact, mutate: setLocalArtifact } = useSWR<UIArtifact>(
|
||||
'artifact',
|
||||
"artifact",
|
||||
null,
|
||||
{
|
||||
fallbackData: initialArtifactData,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const artifact = useMemo(() => {
|
||||
if (!localArtifact) return initialArtifactData;
|
||||
if (!localArtifact) {
|
||||
return initialArtifactData;
|
||||
}
|
||||
return localArtifact;
|
||||
}, [localArtifact]);
|
||||
|
||||
|
|
@ -53,14 +57,14 @@ export function useArtifact() {
|
|||
setLocalArtifact((currentArtifact) => {
|
||||
const artifactToUpdate = currentArtifact || initialArtifactData;
|
||||
|
||||
if (typeof updaterFn === 'function') {
|
||||
if (typeof updaterFn === "function") {
|
||||
return updaterFn(artifactToUpdate);
|
||||
}
|
||||
|
||||
return updaterFn;
|
||||
});
|
||||
},
|
||||
[setLocalArtifact],
|
||||
[setLocalArtifact]
|
||||
);
|
||||
|
||||
const { data: localArtifactMetadata, mutate: setLocalArtifactMetadata } =
|
||||
|
|
@ -70,7 +74,7 @@ export function useArtifact() {
|
|||
null,
|
||||
{
|
||||
fallbackData: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
|
|
@ -80,6 +84,6 @@ export function useArtifact() {
|
|||
metadata: localArtifactMetadata,
|
||||
setMetadata: setLocalArtifactMetadata,
|
||||
}),
|
||||
[artifact, setArtifact, localArtifactMetadata, setLocalArtifactMetadata],
|
||||
[artifact, setArtifact, localArtifactMetadata, setLocalArtifactMetadata]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import type { UseChatHelpers } from '@ai-sdk/react';
|
||||
import type { ChatMessage } from '@/lib/types';
|
||||
import { useDataStream } from '@/components/data-stream-provider';
|
||||
import type { UseChatHelpers } from "@ai-sdk/react";
|
||||
import { useEffect } from "react";
|
||||
import { useDataStream } from "@/components/data-stream-provider";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
|
||||
export interface UseAutoResumeParams {
|
||||
export type UseAutoResumeParams = {
|
||||
autoResume: boolean;
|
||||
initialMessages: ChatMessage[];
|
||||
resumeStream: UseChatHelpers<ChatMessage>['resumeStream'];
|
||||
setMessages: UseChatHelpers<ChatMessage>['setMessages'];
|
||||
}
|
||||
resumeStream: UseChatHelpers<ChatMessage>["resumeStream"];
|
||||
setMessages: UseChatHelpers<ChatMessage>["setMessages"];
|
||||
};
|
||||
|
||||
export function useAutoResume({
|
||||
autoResume,
|
||||
|
|
@ -21,25 +21,31 @@ export function useAutoResume({
|
|||
const { dataStream } = useDataStream();
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoResume) return;
|
||||
if (!autoResume) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mostRecentMessage = initialMessages.at(-1);
|
||||
|
||||
if (mostRecentMessage?.role === 'user') {
|
||||
if (mostRecentMessage?.role === "user") {
|
||||
resumeStream();
|
||||
}
|
||||
|
||||
// we intentionally run this once
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [autoResume, initialMessages.at, resumeStream]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dataStream) return;
|
||||
if (dataStream.length === 0) return;
|
||||
if (!dataStream) {
|
||||
return;
|
||||
}
|
||||
if (dataStream.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataPart = dataStream[0];
|
||||
|
||||
if (dataPart.type === 'data-appendMessage') {
|
||||
if (dataPart.type === "data-appendMessage") {
|
||||
const message = JSON.parse(dataPart.data);
|
||||
setMessages([...initialMessages, message]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import useSWR, { useSWRConfig } from 'swr';
|
||||
import { unstable_serialize } from 'swr/infinite';
|
||||
import { updateChatVisibility } from '@/app/(chat)/actions';
|
||||
import { useMemo } from "react";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
import { unstable_serialize } from "swr/infinite";
|
||||
import { updateChatVisibility } from "@/app/(chat)/actions";
|
||||
import {
|
||||
getChatHistoryPaginationKey,
|
||||
type ChatHistory,
|
||||
} from '@/components/sidebar-history';
|
||||
import type { VisibilityType } from '@/components/visibility-selector';
|
||||
getChatHistoryPaginationKey,
|
||||
} from "@/components/sidebar-history";
|
||||
import type { VisibilityType } from "@/components/visibility-selector";
|
||||
|
||||
export function useChatVisibility({
|
||||
chatId,
|
||||
|
|
@ -18,20 +18,24 @@ export function useChatVisibility({
|
|||
initialVisibilityType: VisibilityType;
|
||||
}) {
|
||||
const { mutate, cache } = useSWRConfig();
|
||||
const history: ChatHistory = cache.get('/api/history')?.data;
|
||||
const history: ChatHistory = cache.get("/api/history")?.data;
|
||||
|
||||
const { data: localVisibility, mutate: setLocalVisibility } = useSWR(
|
||||
`${chatId}-visibility`,
|
||||
null,
|
||||
{
|
||||
fallbackData: initialVisibilityType,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const visibilityType = useMemo(() => {
|
||||
if (!history) return localVisibility;
|
||||
const chat = history.chats.find((chat) => chat.id === chatId);
|
||||
if (!chat) return 'private';
|
||||
if (!history) {
|
||||
return localVisibility;
|
||||
}
|
||||
const chat = history.chats.find((currentChat) => currentChat.id === chatId);
|
||||
if (!chat) {
|
||||
return "private";
|
||||
}
|
||||
return chat.visibility;
|
||||
}, [history, chatId, localVisibility]);
|
||||
|
||||
|
|
@ -40,7 +44,7 @@ export function useChatVisibility({
|
|||
mutate(unstable_serialize(getChatHistoryPaginationKey));
|
||||
|
||||
updateChatVisibility({
|
||||
chatId: chatId,
|
||||
chatId,
|
||||
visibility: updatedVisibilityType,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useScrollToBottom } from './use-scroll-to-bottom';
|
||||
import type { UseChatHelpers } from '@ai-sdk/react';
|
||||
import type { ChatMessage } from '@/lib/types';
|
||||
import type { UseChatHelpers } from "@ai-sdk/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
import { useScrollToBottom } from "./use-scroll-to-bottom";
|
||||
|
||||
export function useMessages({
|
||||
chatId,
|
||||
status,
|
||||
}: {
|
||||
chatId: string;
|
||||
status: UseChatHelpers<ChatMessage>['status'];
|
||||
status: UseChatHelpers<ChatMessage>["status"];
|
||||
}) {
|
||||
const {
|
||||
containerRef,
|
||||
|
|
@ -22,7 +20,7 @@ export function useMessages({
|
|||
const [hasSentMessage, setHasSentMessage] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'submitted') {
|
||||
if (status === "submitted") {
|
||||
setHasSentMessage(true);
|
||||
}
|
||||
}, [status]);
|
||||
|
|
|
|||
19
hooks/use-mobile.ts
Normal file
19
hooks/use-mobile.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import * as React from 'react';
|
||||
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
};
|
||||
mql.addEventListener('change', onChange);
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
return () => mql.removeEventListener('change', onChange);
|
||||
}, []);
|
||||
|
||||
return !!isMobile;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import useSWR from 'swr';
|
||||
import { useRef, useEffect, useCallback, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
type ScrollFlag = ScrollBehavior | false;
|
||||
|
||||
|
|
@ -9,21 +9,25 @@ export function useScrollToBottom() {
|
|||
const [isAtBottom, setIsAtBottom] = useState(true);
|
||||
|
||||
const { data: scrollBehavior = false, mutate: setScrollBehavior } =
|
||||
useSWR<ScrollFlag>('messages:should-scroll', null, { fallbackData: false });
|
||||
useSWR<ScrollFlag>("messages:should-scroll", null, { fallbackData: false });
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
if (!containerRef.current) return;
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
||||
|
||||
// Check if we are within 100px of the bottom (like v0 does)
|
||||
setIsAtBottom(scrollTop + clientHeight >= scrollHeight - 100);
|
||||
}, []);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = containerRef.current;
|
||||
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
requestAnimationFrame(() => {
|
||||
handleScroll();
|
||||
|
|
@ -43,7 +47,7 @@ export function useScrollToBottom() {
|
|||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['style', 'class', 'data-state']
|
||||
attributeFilter: ["style", "class", "data-state"],
|
||||
});
|
||||
|
||||
handleScroll();
|
||||
|
|
@ -56,13 +60,15 @@ export function useScrollToBottom() {
|
|||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.addEventListener('scroll', handleScroll);
|
||||
container.addEventListener("scroll", handleScroll);
|
||||
handleScroll(); // Check initial state
|
||||
|
||||
return () => {
|
||||
container.removeEventListener('scroll', handleScroll);
|
||||
container.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [handleScroll]);
|
||||
|
||||
|
|
@ -79,10 +85,10 @@ export function useScrollToBottom() {
|
|||
}, [scrollBehavior, setScrollBehavior]);
|
||||
|
||||
const scrollToBottom = useCallback(
|
||||
(scrollBehavior: ScrollBehavior = 'smooth') => {
|
||||
setScrollBehavior(scrollBehavior);
|
||||
(currentScrollBehavior: ScrollBehavior = "smooth") => {
|
||||
setScrollBehavior(currentScrollBehavior);
|
||||
},
|
||||
[setScrollBehavior],
|
||||
[setScrollBehavior]
|
||||
);
|
||||
|
||||
function onViewportEnter() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue