| import { createContext, ReactNode, useContext } from "react"; |
| import { useOptionalConversationId } from "#/hooks/use-conversation-id"; |
| import { openWorkspaceFile } from "#/services/canvas-ui"; |
| import EventLogger from "#/utils/event-logger"; |
|
|
| |
| |
| |
| |
| |
| export const PathInteractiveContext = createContext(true); |
|
|
| |
| |
| |
| |
| |
| const decodeHtmlEntities = (text: string): string => { |
| const textarea = document.createElement("textarea"); |
| textarea.innerHTML = text; |
| return textarea.value; |
| }; |
|
|
| |
| |
| |
| |
| |
| const isLikelyDirectory = (path: string): boolean => { |
| if (!path) return false; |
| |
| if (path.endsWith("/") || path.endsWith("\\")) return true; |
| |
| const lastPart = path.split(/[/\\]/).pop() || ""; |
| |
| return !lastPart.includes("."); |
| }; |
|
|
| |
| |
| |
| |
| |
| const extractFilename = (path: string): string => { |
| if (!path) return ""; |
| |
| const parts = path.split(/[/\\]/); |
| const filename = parts[parts.length - 1]; |
|
|
| |
| if (isLikelyDirectory(path) && !filename.endsWith("/")) { |
| return `${filename}/`; |
| } |
|
|
| return filename; |
| }; |
|
|
| |
| |
| |
| |
| function PathComponent(props: { children?: ReactNode }) { |
| const { children } = props; |
| const { conversationId } = useOptionalConversationId(); |
| const interactive = useContext(PathInteractiveContext); |
|
|
| const processPath = (path: string) => { |
| try { |
| const decodedPath = decodeHtmlEntities(path); |
| const filename = extractFilename(decodedPath); |
| if (!interactive) { |
| return ( |
| <span |
| className="font-mono font-normal tracking-tight" |
| title={decodedPath} |
| > |
| {filename} |
| </span> |
| ); |
| } |
| return ( |
| <button |
| type="button" |
| data-testid="path-component-link" |
| className="cursor-pointer font-mono font-normal tracking-tight hover:underline" |
| title={decodedPath} |
| onClick={(event) => { |
| event.stopPropagation(); |
| openWorkspaceFile(decodedPath, conversationId); |
| }} |
| > |
| {filename} |
| </button> |
| ); |
| } catch (e) { |
| EventLogger.error(String(e)); |
| return ( |
| <span className="font-mono font-normal tracking-tight">{path}</span> |
| ); |
| } |
| }; |
|
|
| if (Array.isArray(children)) { |
| const processedChildren = children.map((child, index) => |
| typeof child === "string" ? ( |
| <span key={`${child}-${index}`}>{processPath(child)}</span> |
| ) : ( |
| child |
| ), |
| ); |
|
|
| return ( |
| <span className="font-normal tracking-tight">{processedChildren}</span> |
| ); |
| } |
|
|
| if (typeof children === "string") { |
| return ( |
| <span className="font-normal tracking-tight"> |
| {processPath(children)} |
| </span> |
| ); |
| } |
|
|
| return ( |
| <span className="font-mono font-normal tracking-tight">{children}</span> |
| ); |
| } |
|
|
| export { PathComponent, isLikelyDirectory }; |
|
|