mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 22:32:12 +08:00
feat: add ToggleGroup
This commit is contained in:
@@ -27,6 +27,8 @@
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-toggle": "^1.1.10",
|
||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
||||
"@t3-oss/env-nextjs": "^0.12.0",
|
||||
|
||||
58
frontend/pnpm-lock.yaml
generated
58
frontend/pnpm-lock.yaml
generated
@@ -41,6 +41,12 @@ importers:
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4(@types/react@19.2.8)(react@19.2.3)
|
||||
'@radix-ui/react-toggle':
|
||||
specifier: ^1.1.10
|
||||
version: 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-toggle-group':
|
||||
specifier: ^1.1.11
|
||||
version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -1088,6 +1094,32 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.11':
|
||||
resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-toggle@1.1.10':
|
||||
resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8':
|
||||
resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
|
||||
peerDependencies:
|
||||
@@ -5615,6 +5647,32 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.8
|
||||
|
||||
'@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.8)(react@19.2.3)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.8)(react@19.2.3)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.8)(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.8
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.8)
|
||||
|
||||
'@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.8)(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.8
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.8)
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
|
||||
83
frontend/src/components/ui/toggle-group.tsx
Normal file
83
frontend/src/components/ui/toggle-group.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||
import { type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toggleVariants } from "@/components/ui/toggle";
|
||||
|
||||
const ToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants> & {
|
||||
spacing?: number;
|
||||
}
|
||||
>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
spacing: 0,
|
||||
});
|
||||
|
||||
function ToggleGroup({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
spacing = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants> & {
|
||||
spacing?: number;
|
||||
}) {
|
||||
return (
|
||||
<ToggleGroupPrimitive.Root
|
||||
data-slot="toggle-group"
|
||||
data-variant={variant}
|
||||
data-size={size}
|
||||
data-spacing={spacing}
|
||||
style={{ "--gap": spacing } as React.CSSProperties}
|
||||
className={cn(
|
||||
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ToggleGroupContext.Provider value={{ variant, size, spacing }}>
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleGroupItem({
|
||||
className,
|
||||
children,
|
||||
variant,
|
||||
size,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants>) {
|
||||
const context = React.useContext(ToggleGroupContext);
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
data-slot="toggle-group-item"
|
||||
data-variant={context.variant || variant}
|
||||
data-size={context.size || size}
|
||||
data-spacing={context.spacing}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
}),
|
||||
"w-auto min-w-0 shrink-0 cursor-pointer px-3 focus:z-10 focus-visible:z-10",
|
||||
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
);
|
||||
}
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
47
frontend/src/components/ui/toggle.tsx
Normal file
47
frontend/src/components/ui/toggle.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-2 min-w-9",
|
||||
sm: "h-8 px-1.5 min-w-8",
|
||||
lg: "h-10 px-2.5 min-w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Toggle({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>) {
|
||||
return (
|
||||
<TogglePrimitive.Root
|
||||
data-slot="toggle"
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Toggle, toggleVariants }
|
||||
@@ -4,8 +4,9 @@ import {
|
||||
SquareArrowOutUpRightIcon,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Streamdown } from "streamdown";
|
||||
|
||||
import {
|
||||
Artifact,
|
||||
@@ -22,13 +23,14 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||
import { checkCodeFile, getFileName } from "@/core/utils/files";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { useArtifacts } from "./context";
|
||||
import { FileViewer } from "./file-viewer";
|
||||
|
||||
export function ArtifactFileDetail({
|
||||
className,
|
||||
@@ -50,23 +52,34 @@ export function ArtifactFileDetail({
|
||||
}
|
||||
return filepathFromProps;
|
||||
}, [filepathFromProps, isWriteFile]);
|
||||
const { isCodeFile } = useMemo(() => {
|
||||
const { isCodeFile, language } = useMemo(() => {
|
||||
if (isWriteFile) {
|
||||
let language = checkCodeFile(filepath).language;
|
||||
language ??= "markdown";
|
||||
language ??= "text";
|
||||
return { isCodeFile: true, language };
|
||||
}
|
||||
return checkCodeFile(filepath);
|
||||
}, [filepath, isWriteFile]);
|
||||
const previewable = useMemo(() => {
|
||||
return (language === "html" && !isWriteFile) || language === "markdown";
|
||||
}, [isWriteFile, language]);
|
||||
const { content } = useArtifactContent({
|
||||
threadId,
|
||||
filepath: filepathFromProps,
|
||||
enabled: isCodeFile && !isWriteFile,
|
||||
});
|
||||
const [viewMode, setViewMode] = useState<"code" | "preview">("code");
|
||||
useEffect(() => {
|
||||
if (previewable) {
|
||||
setViewMode("preview");
|
||||
} else {
|
||||
setViewMode("code");
|
||||
}
|
||||
}, [previewable]);
|
||||
return (
|
||||
<Artifact className={cn("rounded-none", className)}>
|
||||
<ArtifactHeader className="px-2">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ArtifactTitle>
|
||||
{isWriteFile ? (
|
||||
<div className="px-2">{getFileName(filepath)}</div>
|
||||
@@ -88,6 +101,23 @@ export function ArtifactFileDetail({
|
||||
)}
|
||||
</ArtifactTitle>
|
||||
</div>
|
||||
<div className="flex min-w-0 grow items-center justify-center">
|
||||
{previewable && (
|
||||
<ToggleGroup
|
||||
className="mx-auto"
|
||||
type="single"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
value={viewMode}
|
||||
onValueChange={(value) =>
|
||||
setViewMode(value as "code" | "preview")
|
||||
}
|
||||
>
|
||||
<ToggleGroupItem value="code">Code</ToggleGroupItem>
|
||||
<ToggleGroupItem value="preview">Preview</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ArtifactActions>
|
||||
{!isWriteFile && (
|
||||
@@ -139,12 +169,57 @@ export function ArtifactFileDetail({
|
||||
</div>
|
||||
</ArtifactHeader>
|
||||
<ArtifactContent className="p-0">
|
||||
<FileViewer
|
||||
className="size-full"
|
||||
threadId={threadId}
|
||||
filepath={filepathFromProps}
|
||||
/>
|
||||
{previewable && viewMode === "preview" && (
|
||||
<ArtifactFilePreview
|
||||
filepath={filepath}
|
||||
threadId={threadId}
|
||||
content={content}
|
||||
language={language ?? "text"}
|
||||
/>
|
||||
)}
|
||||
{isCodeFile && viewMode === "code" && (
|
||||
<Textarea
|
||||
className="size-full resize-none rounded-none border-none"
|
||||
readOnly
|
||||
value={content ?? ""}
|
||||
/>
|
||||
)}
|
||||
{!isCodeFile && (
|
||||
<iframe
|
||||
className="size-full"
|
||||
src={urlOfArtifact({ filepath, threadId })}
|
||||
/>
|
||||
)}
|
||||
</ArtifactContent>
|
||||
</Artifact>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArtifactFilePreview({
|
||||
filepath,
|
||||
threadId,
|
||||
content,
|
||||
language,
|
||||
}: {
|
||||
filepath: string;
|
||||
threadId: string;
|
||||
content: string;
|
||||
language: string;
|
||||
}) {
|
||||
if (language === "markdown") {
|
||||
return (
|
||||
<div className="size-full px-4">
|
||||
<Streamdown className="size-full">{content ?? ""}</Streamdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (language === "html") {
|
||||
return (
|
||||
<iframe
|
||||
className="size-full"
|
||||
src={urlOfArtifact({ filepath, threadId })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
import type { BundledLanguage } from "shiki";
|
||||
|
||||
import { CodeBlock } from "@/components/ai-elements/code-block";
|
||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||
import { checkCodeFile } from "@/core/utils/files";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function FileViewer({
|
||||
className,
|
||||
filepath,
|
||||
threadId,
|
||||
}: {
|
||||
className?: string;
|
||||
filepath: string;
|
||||
threadId: string;
|
||||
}) {
|
||||
const isWriteFile = useMemo(() => {
|
||||
return filepath.startsWith("write-file:");
|
||||
}, [filepath]);
|
||||
const { isCodeFile, language } = useMemo(() => {
|
||||
if (isWriteFile) {
|
||||
const url = new URL(filepath);
|
||||
const path = decodeURIComponent(url.pathname);
|
||||
return checkCodeFile(path);
|
||||
}
|
||||
return checkCodeFile(filepath);
|
||||
}, [filepath, isWriteFile]);
|
||||
if (isWriteFile || (isCodeFile && language !== "html")) {
|
||||
return (
|
||||
<CodeFileView
|
||||
language={language ?? "markdown"}
|
||||
filepath={filepath}
|
||||
threadId={threadId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={cn("size-full border-none", className)}>
|
||||
<iframe
|
||||
className={cn("size-full border-none", className)}
|
||||
src={urlOfArtifact({ filepath, threadId })}
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeFileView({
|
||||
language,
|
||||
filepath,
|
||||
threadId,
|
||||
}: {
|
||||
language: BundledLanguage;
|
||||
filepath: string;
|
||||
threadId: string;
|
||||
}) {
|
||||
const { content: code } = useArtifactContent({
|
||||
filepath,
|
||||
threadId,
|
||||
});
|
||||
if (code) {
|
||||
return (
|
||||
<CodeBlock
|
||||
className="size-full rounded-none border-none"
|
||||
language={language}
|
||||
code={code}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { useMemo } from "react";
|
||||
|
||||
import { useThread } from "@/components/workspace/messages/context";
|
||||
|
||||
import { loadArtifactContent } from "./loader";
|
||||
import { loadArtifactContent, loadArtifactContentFromToolCall } from "./loader";
|
||||
|
||||
export function useArtifactContent({
|
||||
filepath,
|
||||
@@ -20,25 +20,10 @@ export function useArtifactContent({
|
||||
const { thread } = useThread();
|
||||
const content = useMemo(() => {
|
||||
if (isWriteFile) {
|
||||
const url = new URL(filepath);
|
||||
const toolCallId = url.searchParams.get("tool_call_id");
|
||||
const messageId = url.searchParams.get("message_id");
|
||||
if (messageId && toolCallId) {
|
||||
const message = thread.messages.find(
|
||||
(message) => message.id === messageId,
|
||||
);
|
||||
if (message?.type === "ai" && message.tool_calls) {
|
||||
const toolCall = message.tool_calls.find(
|
||||
(toolCall) => toolCall.id === toolCallId,
|
||||
);
|
||||
if (toolCall) {
|
||||
return toolCall.args.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadArtifactContentFromToolCall({ url: filepath, thread });
|
||||
}
|
||||
return null;
|
||||
}, [filepath, isWriteFile, thread.messages]);
|
||||
}, [filepath, isWriteFile, thread]);
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ["artifact", filepath, threadId],
|
||||
queryFn: () => {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import type { UseStream } from "@langchain/langgraph-sdk/react";
|
||||
|
||||
import type { AgentThreadState } from "../threads";
|
||||
|
||||
import { urlOfArtifact } from "./utils";
|
||||
|
||||
export async function loadArtifactContent({
|
||||
@@ -12,3 +16,26 @@ export async function loadArtifactContent({
|
||||
const text = await response.text();
|
||||
return text;
|
||||
}
|
||||
|
||||
export function loadArtifactContentFromToolCall({
|
||||
url: urlString,
|
||||
thread,
|
||||
}: {
|
||||
url: string;
|
||||
thread: UseStream<AgentThreadState>;
|
||||
}) {
|
||||
const url = new URL(urlString);
|
||||
const toolCallId = url.searchParams.get("tool_call_id");
|
||||
const messageId = url.searchParams.get("message_id");
|
||||
if (messageId && toolCallId) {
|
||||
const message = thread.messages.find((message) => message.id === messageId);
|
||||
if (message?.type === "ai" && message.tool_calls) {
|
||||
const toolCall = message.tool_calls.find(
|
||||
(toolCall) => toolCall.id === toolCallId,
|
||||
);
|
||||
if (toolCall) {
|
||||
return toolCall.args.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import type { BundledLanguage } from "shiki";
|
||||
const extensionMap: Record<string, string> = {
|
||||
// Text
|
||||
txt: "text",
|
||||
csv: "csv",
|
||||
log: "text",
|
||||
conf: "text",
|
||||
config: "text",
|
||||
properties: "text",
|
||||
props: "text",
|
||||
|
||||
const extensionMap: Record<string, BundledLanguage> = {
|
||||
// JavaScript/TypeScript ecosystem
|
||||
js: "javascript",
|
||||
jsx: "jsx",
|
||||
@@ -137,14 +144,14 @@ export function getFileExtension(filepath: string) {
|
||||
export function checkCodeFile(
|
||||
filepath: string,
|
||||
):
|
||||
| { isCodeFile: true; language: BundledLanguage }
|
||||
| { isCodeFile: true; language: string }
|
||||
| { isCodeFile: false; language: null } {
|
||||
const extension = getFileExtension(filepath);
|
||||
const isCodeFile = extension in extensionMap;
|
||||
if (isCodeFile) {
|
||||
return {
|
||||
isCodeFile: true,
|
||||
language: extensionMap[extension] as unknown as BundledLanguage,
|
||||
language: extensionMap[extension] ?? "text",
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user