Restore Ultracite + fix sidebar (#1233)

This commit is contained in:
Hayden Bleasel 2025-09-21 11:02:31 -07:00 committed by GitHub
parent 8fbfc253fa
commit 947ed094a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
177 changed files with 6908 additions and 8306 deletions

View file

@ -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]
);
}

View file

@ -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]);
}

View file

@ -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,
});
};

View file

@ -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
View 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
}

View file

@ -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;
}

View file

@ -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() {