mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 12:25:22 +00:00
memoize all of the things
This commit is contained in:
parent
da3c93e05a
commit
4b7798a94e
10 changed files with 88 additions and 51 deletions
|
|
@ -137,6 +137,7 @@
|
||||||
"embla-carousel-auto-scroll": "^8.3.0",
|
"embla-carousel-auto-scroll": "^8.3.0",
|
||||||
"embla-carousel-react": "^8.3.0",
|
"embla-carousel-react": "^8.3.0",
|
||||||
"escape-string-regexp": "^5.0.0",
|
"escape-string-regexp": "^5.0.0",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"google-auth-library": "^10.1.0",
|
"google-auth-library": "^10.1.0",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { insertMention, slateContentToString } from "@/features/chat/utils";
|
||||||
import { cn, IS_MAC } from "@/lib/utils";
|
import { cn, IS_MAC } from "@/lib/utils";
|
||||||
import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react";
|
import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react";
|
||||||
import { ArrowUp, Loader2, StopCircleIcon, TriangleAlertIcon } from "lucide-react";
|
import { ArrowUp, Loader2, StopCircleIcon, TriangleAlertIcon } from "lucide-react";
|
||||||
import { Fragment, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { Fragment, KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
import { Descendant, insertText } from "slate";
|
import { Descendant, insertText } from "slate";
|
||||||
import { Editable, ReactEditor, RenderElementProps, RenderLeafProps, useFocused, useSelected, useSlate } from "slate-react";
|
import { Editable, ReactEditor, RenderElementProps, RenderLeafProps, useFocused, useSelected, useSlate } from "slate-react";
|
||||||
|
|
@ -19,6 +19,7 @@ import { useSuggestionModeAndQuery } from "./useSuggestionModeAndQuery";
|
||||||
import { useSuggestionsData } from "./useSuggestionsData";
|
import { useSuggestionsData } from "./useSuggestionsData";
|
||||||
import { useToast } from "@/components/hooks/use-toast";
|
import { useToast } from "@/components/hooks/use-toast";
|
||||||
import { SearchContextQuery } from "@/lib/types";
|
import { SearchContextQuery } from "@/lib/types";
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
interface ChatBoxProps {
|
interface ChatBoxProps {
|
||||||
onSubmit: (children: Descendant[], editor: CustomEditor) => void;
|
onSubmit: (children: Descendant[], editor: CustomEditor) => void;
|
||||||
|
|
@ -34,7 +35,7 @@ interface ChatBoxProps {
|
||||||
onContextSelectorOpenChanged: (isOpen: boolean) => void;
|
onContextSelectorOpenChanged: (isOpen: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatBox = ({
|
const ChatBoxComponent = ({
|
||||||
onSubmit: _onSubmit,
|
onSubmit: _onSubmit,
|
||||||
onStop,
|
onStop,
|
||||||
preferredSuggestionsBoxPlacement = "bottom-start",
|
preferredSuggestionsBoxPlacement = "bottom-start",
|
||||||
|
|
@ -368,6 +369,8 @@ export const ChatBox = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ChatBox = memo(ChatBoxComponent, isEqual);
|
||||||
|
|
||||||
const DefaultElement = (props: RenderElementProps) => {
|
const DefaultElement = (props: RenderElementProps) => {
|
||||||
return <p {...props.attributes}>{props.children}</p>
|
return <p {...props.attributes}>{props.children}</p>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { isServiceError } from "@/lib/utils";
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
import { LangfuseWeb } from "langfuse";
|
import { LangfuseWeb } from "langfuse";
|
||||||
import { env } from "@sourcebot/shared/client";
|
import { env } from "@sourcebot/shared/client";
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
interface AnswerCardProps {
|
interface AnswerCardProps {
|
||||||
answerText: string;
|
answerText: string;
|
||||||
|
|
@ -178,4 +179,4 @@ const AnswerCardComponent = forwardRef<HTMLDivElement, AnswerCardProps>(({
|
||||||
|
|
||||||
AnswerCardComponent.displayName = 'AnswerCard';
|
AnswerCardComponent.displayName = 'AnswerCard';
|
||||||
|
|
||||||
export const AnswerCard = memo(AnswerCardComponent);
|
export const AnswerCard = memo(AnswerCardComponent, isEqual);
|
||||||
|
|
@ -8,12 +8,13 @@ import { CSSProperties, forwardRef, memo, useCallback, useEffect, useMemo, useRe
|
||||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||||
import { Reference, referenceSchema, SBChatMessage, Source } from "../../types";
|
import { Reference, referenceSchema, SBChatMessage, Source } from "../../types";
|
||||||
import { useExtractReferences } from '../../useExtractReferences';
|
import { useExtractReferences } from '../../useExtractReferences';
|
||||||
import { getAnswerPartFromAssistantMessage, groupMessageIntoSteps, repairReferences } from '../../utils';
|
import { getAnswerPartFromAssistantMessage, groupMessageIntoSteps, repairReferences, tryResolveFileReference } from '../../utils';
|
||||||
import { AnswerCard } from './answerCard';
|
import { AnswerCard } from './answerCard';
|
||||||
import { DetailsCard } from './detailsCard';
|
import { DetailsCard } from './detailsCard';
|
||||||
import { MarkdownRenderer, REFERENCE_PAYLOAD_ATTRIBUTE } from './markdownRenderer';
|
import { MarkdownRenderer, REFERENCE_PAYLOAD_ATTRIBUTE } from './markdownRenderer';
|
||||||
import { ReferencedSourcesListView } from './referencedSourcesListView';
|
import { ReferencedSourcesListView } from './referencedSourcesListView';
|
||||||
import { uiVisiblePartTypes } from '../../constants';
|
import { uiVisiblePartTypes } from '../../constants';
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
interface ChatThreadListItemProps {
|
interface ChatThreadListItemProps {
|
||||||
userMessage: SBChatMessage;
|
userMessage: SBChatMessage;
|
||||||
|
|
@ -32,7 +33,6 @@ export const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThread
|
||||||
chatId,
|
chatId,
|
||||||
index,
|
index,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
console.log(`re-rendering chat thread list item`, index);
|
|
||||||
const leftPanelRef = useRef<HTMLDivElement>(null);
|
const leftPanelRef = useRef<HTMLDivElement>(null);
|
||||||
const [leftPanelHeight, setLeftPanelHeight] = useState<number | null>(null);
|
const [leftPanelHeight, setLeftPanelHeight] = useState<number | null>(null);
|
||||||
const answerRef = useRef<HTMLDivElement>(null);
|
const answerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -81,7 +81,6 @@ export const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThread
|
||||||
return getAnswerPartFromAssistantMessage(assistantMessage, isStreaming);
|
return getAnswerPartFromAssistantMessage(assistantMessage, isStreaming);
|
||||||
}, [assistantMessage, isStreaming]);
|
}, [assistantMessage, isStreaming]);
|
||||||
|
|
||||||
const references = useExtractReferences(answerPart);
|
|
||||||
|
|
||||||
// Groups parts into steps that are associated with thinking steps that
|
// Groups parts into steps that are associated with thinking steps that
|
||||||
// should be visible to the user. By "steps", we mean parts that originated
|
// should be visible to the user. By "steps", we mean parts that originated
|
||||||
|
|
@ -280,6 +279,26 @@ export const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThread
|
||||||
};
|
};
|
||||||
}, [hoveredReference]);
|
}, [hoveredReference]);
|
||||||
|
|
||||||
|
const references = useExtractReferences(answerPart);
|
||||||
|
|
||||||
|
// Extract the file sources that are referenced by the answer part.
|
||||||
|
const referencedFileSources = useMemo(() => {
|
||||||
|
const fileSources = sources.filter((source) => source.type === 'file');
|
||||||
|
|
||||||
|
return references
|
||||||
|
.filter((reference) => reference.type === 'file')
|
||||||
|
.map((reference) => tryResolveFileReference(reference, fileSources))
|
||||||
|
.filter((file) => file !== undefined)
|
||||||
|
// de-duplicate files
|
||||||
|
.filter((file, index, self) =>
|
||||||
|
index === self.findIndex((t) =>
|
||||||
|
t?.path === file?.path
|
||||||
|
&& t?.repo === file?.repo
|
||||||
|
&& t?.revision === file?.revision
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [references, sources]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -365,11 +384,11 @@ export const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThread
|
||||||
<div
|
<div
|
||||||
className="sticky top-0"
|
className="sticky top-0"
|
||||||
>
|
>
|
||||||
{references.length > 0 ? (
|
{referencedFileSources.length > 0 ? (
|
||||||
<ReferencedSourcesListView
|
<ReferencedSourcesListView
|
||||||
index={index}
|
index={index}
|
||||||
references={references}
|
references={references}
|
||||||
sources={sources}
|
sources={referencedFileSources}
|
||||||
hoveredReference={hoveredReference}
|
hoveredReference={hoveredReference}
|
||||||
selectedReference={selectedReference}
|
selectedReference={selectedReference}
|
||||||
onSelectedReferenceChanged={setSelectedReference}
|
onSelectedReferenceChanged={setSelectedReference}
|
||||||
|
|
@ -396,10 +415,32 @@ export const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThread
|
||||||
|
|
||||||
ChatThreadListItemComponent.displayName = 'ChatThreadListItem';
|
ChatThreadListItemComponent.displayName = 'ChatThreadListItem';
|
||||||
|
|
||||||
// Only allow re-rendering when the message _is_ streaming.
|
// Custom comparison function that handles the known issue where useChat mutates
|
||||||
// This is a performance optimizations to prevent unnecessary
|
// message objects in place during streaming, causing fast-deep-equal to return
|
||||||
// re-renders for chatThreadListItems that are not streaming.
|
// true even when content changes (because it checks reference equality first).
|
||||||
export const ChatThreadListItem = memo(ChatThreadListItemComponent, (_, nextProps) => !nextProps.isStreaming);
|
// See: https://github.com/vercel/ai/issues/6466
|
||||||
|
const arePropsEqual = (
|
||||||
|
prevProps: ChatThreadListItemProps,
|
||||||
|
nextProps: ChatThreadListItemProps
|
||||||
|
): boolean => {
|
||||||
|
// Always re-render if streaming status changes
|
||||||
|
if (prevProps.isStreaming !== nextProps.isStreaming) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If currently streaming, always allow re-render
|
||||||
|
// This bypasses the fast-deep-equal reference check issue when useChat
|
||||||
|
// mutates message objects in place during token streaming
|
||||||
|
if (nextProps.isStreaming) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-streaming messages, use deep equality
|
||||||
|
// At this point, useChat should have finished and created final objects
|
||||||
|
return isEqual(prevProps, nextProps);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChatThreadListItem = memo(ChatThreadListItemComponent, arePropsEqual);
|
||||||
|
|
||||||
// Finds the nearest reference element to the viewport center.
|
// Finds the nearest reference element to the viewport center.
|
||||||
const getNearestReferenceElement = (referenceElements: Element[]) => {
|
const getNearestReferenceElement = (referenceElements: Element[]) => {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { SearchReposToolComponent } from './tools/searchReposToolComponent';
|
||||||
import { ListAllReposToolComponent } from './tools/listAllReposToolComponent';
|
import { ListAllReposToolComponent } from './tools/listAllReposToolComponent';
|
||||||
import { SBChatMessageMetadata, SBChatMessagePart } from '../../types';
|
import { SBChatMessageMetadata, SBChatMessagePart } from '../../types';
|
||||||
import { SearchScopeIcon } from '../searchScopeIcon';
|
import { SearchScopeIcon } from '../searchScopeIcon';
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
|
|
||||||
interface DetailsCardProps {
|
interface DetailsCardProps {
|
||||||
|
|
@ -36,7 +37,6 @@ const DetailsCardComponent = ({
|
||||||
metadata,
|
metadata,
|
||||||
thinkingSteps,
|
thinkingSteps,
|
||||||
}: DetailsCardProps) => {
|
}: DetailsCardProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="mb-4">
|
<Card className="mb-4">
|
||||||
<Collapsible open={isExpanded} onOpenChange={onExpandedChanged}>
|
<Collapsible open={isExpanded} onOpenChange={onExpandedChanged}>
|
||||||
|
|
@ -212,4 +212,4 @@ const DetailsCardComponent = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DetailsCard = memo(DetailsCardComponent);
|
export const DetailsCard = memo(DetailsCardComponent, isEqual);
|
||||||
|
|
@ -20,6 +20,7 @@ import { CodeBlock } from './codeBlock';
|
||||||
import { FILE_REFERENCE_REGEX } from '@/features/chat/constants';
|
import { FILE_REFERENCE_REGEX } from '@/features/chat/constants';
|
||||||
import { createFileReference } from '@/features/chat/utils';
|
import { createFileReference } from '@/features/chat/utils';
|
||||||
import { SINGLE_TENANT_ORG_DOMAIN } from '@/lib/constants';
|
import { SINGLE_TENANT_ORG_DOMAIN } from '@/lib/constants';
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
export const REFERENCE_PAYLOAD_ATTRIBUTE = 'data-reference-payload';
|
export const REFERENCE_PAYLOAD_ATTRIBUTE = 'data-reference-payload';
|
||||||
|
|
||||||
|
|
@ -221,4 +222,4 @@ const MarkdownRendererComponent = forwardRef<HTMLDivElement, MarkdownRendererPro
|
||||||
|
|
||||||
MarkdownRendererComponent.displayName = 'MarkdownRenderer';
|
MarkdownRendererComponent.displayName = 'MarkdownRenderer';
|
||||||
|
|
||||||
export const MarkdownRenderer = memo(MarkdownRendererComponent);
|
export const MarkdownRenderer = memo(MarkdownRendererComponent, isEqual);
|
||||||
|
|
@ -20,6 +20,7 @@ import { createCodeFoldingExtension } from "./codeFoldingExtension";
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
import { CodeHostType } from "@sourcebot/db";
|
import { CodeHostType } from "@sourcebot/db";
|
||||||
import { createAuditAction } from "@/ee/features/audit/actions";
|
import { createAuditAction } from "@/ee/features/audit/actions";
|
||||||
|
import isEqual from "fast-deep-equal/react";
|
||||||
|
|
||||||
const lineDecoration = Decoration.line({
|
const lineDecoration = Decoration.line({
|
||||||
attributes: { class: "cm-range-border-radius chat-lineHighlight" },
|
attributes: { class: "cm-range-border-radius chat-lineHighlight" },
|
||||||
|
|
@ -355,6 +356,6 @@ const ReferencedFileSourceListItem = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(forwardRef(ReferencedFileSourceListItem)) as (
|
export default memo(forwardRef(ReferencedFileSourceListItem), isEqual) as (
|
||||||
props: ReferencedFileSourceListItemProps & { ref?: Ref<ReactCodeMirrorRef> },
|
props: ReferencedFileSourceListItemProps & { ref?: Ref<ReactCodeMirrorRef> },
|
||||||
) => ReturnType<typeof ReferencedFileSourceListItem>;
|
) => ReturnType<typeof ReferencedFileSourceListItem>;
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,14 @@ import { useQueries } from "@tanstack/react-query";
|
||||||
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||||
import { FileReference, FileSource, Reference, Source } from "../../types";
|
import { FileReference, FileSource, Reference } from "../../types";
|
||||||
|
import { tryResolveFileReference } from '../../utils';
|
||||||
import ReferencedFileSourceListItem from "./referencedFileSourceListItem";
|
import ReferencedFileSourceListItem from "./referencedFileSourceListItem";
|
||||||
|
import isEqual from 'fast-deep-equal/react';
|
||||||
|
|
||||||
interface ReferencedSourcesListViewProps {
|
interface ReferencedSourcesListViewProps {
|
||||||
references: FileReference[];
|
references: FileReference[];
|
||||||
sources: Source[];
|
sources: FileSource[];
|
||||||
index: number;
|
index: number;
|
||||||
hoveredReference?: Reference;
|
hoveredReference?: Reference;
|
||||||
onHoveredReferenceChanged: (reference?: Reference) => void;
|
onHoveredReferenceChanged: (reference?: Reference) => void;
|
||||||
|
|
@ -23,13 +25,6 @@ interface ReferencedSourcesListViewProps {
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveFileReference = (reference: FileReference, sources: FileSource[]): FileSource | undefined => {
|
|
||||||
return sources.find(
|
|
||||||
(source) => source.repo.endsWith(reference.repo) &&
|
|
||||||
source.path.endsWith(reference.path)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReferencedSourcesListViewComponent = ({
|
const ReferencedSourcesListViewComponent = ({
|
||||||
references,
|
references,
|
||||||
sources,
|
sources,
|
||||||
|
|
@ -59,43 +54,26 @@ const ReferencedSourcesListViewComponent = ({
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const referencedFileSources = useMemo((): FileSource[] => {
|
|
||||||
const fileSources = sources.filter((source) => source.type === 'file');
|
|
||||||
|
|
||||||
return references
|
|
||||||
.filter((reference) => reference.type === 'file')
|
|
||||||
.map((reference) => resolveFileReference(reference, fileSources))
|
|
||||||
.filter((file) => file !== undefined)
|
|
||||||
// de-duplicate files
|
|
||||||
.filter((file, index, self) =>
|
|
||||||
index === self.findIndex((t) =>
|
|
||||||
t?.path === file?.path
|
|
||||||
&& t?.repo === file?.repo
|
|
||||||
&& t?.revision === file?.revision
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [references, sources]);
|
|
||||||
|
|
||||||
// Memoize the computation of references grouped by file source
|
// Memoize the computation of references grouped by file source
|
||||||
const referencesGroupedByFile = useMemo(() => {
|
const referencesGroupedByFile = useMemo(() => {
|
||||||
const groupedReferences = new Map<string, FileReference[]>();
|
const groupedReferences = new Map<string, FileReference[]>();
|
||||||
|
|
||||||
for (const fileSource of referencedFileSources) {
|
for (const fileSource of sources) {
|
||||||
const fileKey = getFileId(fileSource);
|
const fileKey = getFileId(fileSource);
|
||||||
const referencesInFile = references.filter((reference) => {
|
const referencesInFile = references.filter((reference) => {
|
||||||
if (reference.type !== 'file') {
|
if (reference.type !== 'file') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return resolveFileReference(reference, [fileSource]) !== undefined;
|
return tryResolveFileReference(reference, [fileSource]) !== undefined;
|
||||||
});
|
});
|
||||||
groupedReferences.set(fileKey, referencesInFile);
|
groupedReferences.set(fileKey, referencesInFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupedReferences;
|
return groupedReferences;
|
||||||
}, [references, referencedFileSources, getFileId]);
|
}, [references, sources, getFileId]);
|
||||||
|
|
||||||
const fileSourceQueries = useQueries({
|
const fileSourceQueries = useQueries({
|
||||||
queries: referencedFileSources.map((file) => ({
|
queries: sources.map((file) => ({
|
||||||
queryKey: ['fileSource', file.path, file.repo, file.revision],
|
queryKey: ['fileSource', file.path, file.repo, file.revision],
|
||||||
queryFn: () => unwrapServiceError(getFileSource({
|
queryFn: () => unwrapServiceError(getFileSource({
|
||||||
fileName: file.path,
|
fileName: file.path,
|
||||||
|
|
@ -112,7 +90,7 @@ const ReferencedSourcesListViewComponent = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileSource = resolveFileReference(selectedReference, referencedFileSources);
|
const fileSource = tryResolveFileReference(selectedReference, sources);
|
||||||
if (!fileSource) {
|
if (!fileSource) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +157,7 @@ const ReferencedSourcesListViewComponent = ({
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [getFileId, referencedFileSources, selectedReference]);
|
}, [getFileId, sources, selectedReference]);
|
||||||
|
|
||||||
const onExpandedChanged = useCallback((fileId: string, isExpanded: boolean) => {
|
const onExpandedChanged = useCallback((fileId: string, isExpanded: boolean) => {
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
|
|
@ -200,7 +178,7 @@ const ReferencedSourcesListViewComponent = ({
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (referencedFileSources.length === 0) {
|
if (sources.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 text-center text-muted-foreground text-sm">
|
<div className="p-4 text-center text-muted-foreground text-sm">
|
||||||
No file references found
|
No file references found
|
||||||
|
|
@ -215,7 +193,7 @@ const ReferencedSourcesListViewComponent = ({
|
||||||
>
|
>
|
||||||
<div className="space-y-4 pr-2">
|
<div className="space-y-4 pr-2">
|
||||||
{fileSourceQueries.map((query, index) => {
|
{fileSourceQueries.map((query, index) => {
|
||||||
const fileSource = referencedFileSources[index];
|
const fileSource = sources[index];
|
||||||
const fileName = fileSource.path.split('/').pop() ?? fileSource.path;
|
const fileName = fileSource.path.split('/').pop() ?? fileSource.path;
|
||||||
|
|
||||||
if (query.isLoading) {
|
if (query.isLoading) {
|
||||||
|
|
@ -280,4 +258,4 @@ const ReferencedSourcesListViewComponent = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memoize to prevent unnecessary re-renders
|
// Memoize to prevent unnecessary re-renders
|
||||||
export const ReferencedSourcesListView = memo(ReferencedSourcesListViewComponent);
|
export const ReferencedSourcesListView = memo(ReferencedSourcesListViewComponent, isEqual);
|
||||||
|
|
|
||||||
|
|
@ -374,3 +374,13 @@ export const buildSearchQuery = (options: {
|
||||||
export const getLanguageModelKey = (model: LanguageModelInfo) => {
|
export const getLanguageModelKey = (model: LanguageModelInfo) => {
|
||||||
return `${model.provider}-${model.model}-${model.displayName}`;
|
return `${model.provider}-${model.model}-${model.displayName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a file reference and a list of file sources, attempts to resolve the file source that the reference points to.
|
||||||
|
*/
|
||||||
|
export const tryResolveFileReference = (reference: FileReference, sources: FileSource[]): FileSource | undefined => {
|
||||||
|
return sources.find(
|
||||||
|
(source) => source.repo.endsWith(reference.repo) &&
|
||||||
|
source.path.endsWith(reference.path)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -8197,6 +8197,7 @@ __metadata:
|
||||||
eslint-config-next: "npm:15.5.0"
|
eslint-config-next: "npm:15.5.0"
|
||||||
eslint-plugin-react: "npm:^7.37.5"
|
eslint-plugin-react: "npm:^7.37.5"
|
||||||
eslint-plugin-react-hooks: "npm:^5.2.0"
|
eslint-plugin-react-hooks: "npm:^5.2.0"
|
||||||
|
fast-deep-equal: "npm:^3.1.3"
|
||||||
fuse.js: "npm:^7.0.0"
|
fuse.js: "npm:^7.0.0"
|
||||||
google-auth-library: "npm:^10.1.0"
|
google-auth-library: "npm:^10.1.0"
|
||||||
graphql: "npm:^16.9.0"
|
graphql: "npm:^16.9.0"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue