mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2026-03-03 03:07:00 +00:00
chore(eslint): update eslint config and downgrade strict rules
This commit is contained in:
@@ -12,7 +12,35 @@ const eslintConfig = defineConfig([
|
|||||||
"out/**",
|
"out/**",
|
||||||
"build/**",
|
"build/**",
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
|
// Compiled outputs
|
||||||
|
"packages/*/dist/**",
|
||||||
|
// Packages with their own ESLint config
|
||||||
|
"packages/raycast-extension/**",
|
||||||
|
// Scripts - may use CommonJS
|
||||||
|
"scripts/**",
|
||||||
|
// Prisma scripts
|
||||||
|
"prisma/**",
|
||||||
]),
|
]),
|
||||||
|
// Downgrade strict rules to warnings for gradual adoption
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
// React hooks compiler rules - many false positives in complex state patterns
|
||||||
|
"react-hooks/set-state-in-effect": "warn",
|
||||||
|
"react-hooks/immutability": "warn",
|
||||||
|
"react-hooks/refs": "warn",
|
||||||
|
"react-hooks/preserve-manual-memoization": "warn",
|
||||||
|
// JSX entity escaping - affects many existing components
|
||||||
|
"react/no-unescaped-entities": "warn",
|
||||||
|
// Function type - affects test mocks
|
||||||
|
"@typescript-eslint/no-unsafe-function-type": "warn",
|
||||||
|
// Display name - affects anonymous components
|
||||||
|
"react/display-name": "warn",
|
||||||
|
// HTML links - sometimes needed for external/special navigation
|
||||||
|
"@next/next/no-html-link-for-pages": "warn",
|
||||||
|
// Children as props - used in some component patterns
|
||||||
|
"react/no-children-prop": "warn",
|
||||||
|
},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ function getJSDocComment(node: ts.Node, sourceFile: ts.SourceFile): { descriptio
|
|||||||
const examples: string[] = [];
|
const examples: string[] = [];
|
||||||
let description: string | undefined;
|
let description: string | undefined;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TypeScript AST doesn't expose jsDoc in types
|
||||||
const jsDocNodes = (node as any).jsDoc as ts.JSDoc[] | undefined;
|
const jsDocNodes = (node as any).jsDoc as ts.JSDoc[] | undefined;
|
||||||
if (jsDocNodes && jsDocNodes.length > 0) {
|
if (jsDocNodes && jsDocNodes.length > 0) {
|
||||||
const jsDoc = jsDocNodes[0];
|
const jsDoc = jsDocNodes[0];
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ export function PromptList({
|
|||||||
return (
|
return (
|
||||||
<Box flexDirection="column" height={terminalHeight} padding={1}>
|
<Box flexDirection="column" height={terminalHeight} padding={1}>
|
||||||
<Text color="red">Error: {error}</Text>
|
<Text color="red">Error: {error}</Text>
|
||||||
<Text dimColor>Press 'r' to retry, 'q' to quit</Text>
|
<Text dimColor>Press 'r' to retry, 'q' to quit</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function parseSimpleYaml(content: string): Record<string, unknown> {
|
|||||||
const lines = content.split('\n');
|
const lines = content.split('\n');
|
||||||
|
|
||||||
let currentKey: string | null = null;
|
let currentKey: string | null = null;
|
||||||
let currentValue: unknown = null;
|
const _currentValue: unknown = null; // Placeholder for future use
|
||||||
let inArray = false;
|
let inArray = false;
|
||||||
let inMultiline = false;
|
let inMultiline = false;
|
||||||
let multilineContent = '';
|
let multilineContent = '';
|
||||||
|
|||||||
@@ -16,21 +16,24 @@ interface LevelContentWrapperProps {
|
|||||||
levelNumber: string;
|
levelNumber: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelContentWrapperProps) {
|
export function LevelContentWrapper({ children, levelSlug, levelNumber: _levelNumber }: LevelContentWrapperProps) {
|
||||||
const t = useTranslations("kids");
|
const t = useTranslations("kids");
|
||||||
const setLevelSlug = useSetLevelSlug();
|
const setLevelSlug = useSetLevelSlug();
|
||||||
const {
|
const {
|
||||||
currentSection,
|
currentSection,
|
||||||
setCurrentSection,
|
setCurrentSection,
|
||||||
completedSections,
|
completedSections: _completedSections,
|
||||||
markSectionComplete,
|
markSectionComplete,
|
||||||
isSectionComplete,
|
isSectionComplete: _isSectionComplete,
|
||||||
sectionRequiresCompletion,
|
sectionRequiresCompletion,
|
||||||
} = useSectionNavigation();
|
} = useSectionNavigation();
|
||||||
|
|
||||||
// Track section completion state from localStorage
|
// Track section completion state from localStorage
|
||||||
const [sectionCompletionState, setSectionCompletionState] = useState<Record<number, boolean>>({});
|
const [sectionCompletionState, setSectionCompletionState] = useState<Record<number, boolean>>({});
|
||||||
|
|
||||||
|
// Track the highest section the user has visited (moved before early returns)
|
||||||
|
const [highestVisitedSection, setHighestVisitedSection] = useState(0);
|
||||||
|
|
||||||
// Check localStorage for section completion on mount and when section changes
|
// Check localStorage for section completion on mount and when section changes
|
||||||
const checkSectionCompletion = useCallback(() => {
|
const checkSectionCompletion = useCallback(() => {
|
||||||
const newState: Record<number, boolean> = {};
|
const newState: Record<number, boolean> = {};
|
||||||
@@ -39,27 +42,40 @@ export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelC
|
|||||||
}
|
}
|
||||||
setSectionCompletionState(newState);
|
setSectionCompletionState(newState);
|
||||||
}, [levelSlug]);
|
}, [levelSlug]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkSectionCompletion();
|
checkSectionCompletion();
|
||||||
// Re-check periodically to catch component completions
|
// Re-check periodically to catch component completions
|
||||||
const interval = setInterval(checkSectionCompletion, 500);
|
const interval = setInterval(checkSectionCompletion, 500);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [checkSectionCompletion, currentSection]);
|
}, [checkSectionCompletion, currentSection]);
|
||||||
|
|
||||||
// Set the level slug in context when component mounts
|
// Set the level slug in context when component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLevelSlug(levelSlug);
|
setLevelSlug(levelSlug);
|
||||||
|
|
||||||
// Track level view
|
// Track level view
|
||||||
const level = getLevelBySlug(levelSlug);
|
const level = getLevelBySlug(levelSlug);
|
||||||
if (level) {
|
if (level) {
|
||||||
analyticsKids.viewLevel(levelSlug, level.world);
|
analyticsKids.viewLevel(levelSlug, level.world);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => setLevelSlug(""); // Clear when unmounting
|
return () => setLevelSlug(""); // Clear when unmounting
|
||||||
}, [levelSlug, setLevelSlug]);
|
}, [levelSlug, setLevelSlug]);
|
||||||
|
|
||||||
|
// Update highest visited when current section changes
|
||||||
|
useEffect(() => {
|
||||||
|
setHighestVisitedSection(prev => Math.max(prev, currentSection));
|
||||||
|
}, [currentSection]);
|
||||||
|
|
||||||
|
// Reset to first section and visited state when level changes
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentSection(0);
|
||||||
|
setHighestVisitedSection(0);
|
||||||
|
setSectionCompletionState({});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- setCurrentSection is stable
|
||||||
|
}, [levelSlug]);
|
||||||
|
|
||||||
// Extract Section components from children
|
// Extract Section components from children
|
||||||
const sections: ReactElement[] = [];
|
const sections: ReactElement[] = [];
|
||||||
let hasExplicitSections = false;
|
let hasExplicitSections = false;
|
||||||
@@ -104,19 +120,11 @@ export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelC
|
|||||||
const totalSections = sections.length;
|
const totalSections = sections.length;
|
||||||
const isFirstSection = currentSection === 0;
|
const isFirstSection = currentSection === 0;
|
||||||
const isLastSection = currentSection === totalSections - 1;
|
const isLastSection = currentSection === totalSections - 1;
|
||||||
|
|
||||||
// Check if current section is complete (from localStorage) OR doesn't require completion
|
// Check if current section is complete (from localStorage) OR doesn't require completion
|
||||||
const currentSectionRequiresCompletion = sectionRequiresCompletion(currentSection);
|
const currentSectionRequiresCompletion = sectionRequiresCompletion(currentSection);
|
||||||
const isCurrentSectionComplete = !currentSectionRequiresCompletion || sectionCompletionState[currentSection] || false;
|
const isCurrentSectionComplete = !currentSectionRequiresCompletion || sectionCompletionState[currentSection] || false;
|
||||||
|
|
||||||
// Track the highest section the user has visited
|
|
||||||
const [highestVisitedSection, setHighestVisitedSection] = useState(0);
|
|
||||||
|
|
||||||
// Update highest visited when current section changes
|
|
||||||
useEffect(() => {
|
|
||||||
setHighestVisitedSection(prev => Math.max(prev, currentSection));
|
|
||||||
}, [currentSection]);
|
|
||||||
|
|
||||||
// Can navigate to a section if it's:
|
// Can navigate to a section if it's:
|
||||||
// 1. The current section
|
// 1. The current section
|
||||||
// 2. A previously visited section (but NOT future sections)
|
// 2. A previously visited section (but NOT future sections)
|
||||||
@@ -139,27 +147,20 @@ export function LevelContentWrapper({ children, levelSlug, levelNumber }: LevelC
|
|||||||
setCurrentSection((prev) => prev - 1);
|
setCurrentSection((prev) => prev - 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDotClick = (targetSection: number) => {
|
const handleDotClick = (targetSection: number) => {
|
||||||
if (canNavigateToSection(targetSection)) {
|
if (canNavigateToSection(targetSection)) {
|
||||||
setCurrentSection(targetSection);
|
setCurrentSection(targetSection);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mark section as complete manually (for sections without interactive elements)
|
// Mark section as complete manually (for sections without interactive elements)
|
||||||
const handleMarkComplete = () => {
|
const _handleMarkComplete = () => {
|
||||||
markSectionCompleted(levelSlug, currentSection);
|
markSectionCompleted(levelSlug, currentSection);
|
||||||
markSectionComplete(currentSection);
|
markSectionComplete(currentSection);
|
||||||
checkSectionCompletion();
|
checkSectionCompletion();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset to first section and visited state when level changes
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentSection(0);
|
|
||||||
setHighestVisitedSection(0);
|
|
||||||
setSectionCompletionState({});
|
|
||||||
}, [levelSlug]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{/* Content area */}
|
{/* Content area */}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
|
||||||
import { useTranslations, useLocale } from "next-intl";
|
import { useTranslations, useLocale } from "next-intl";
|
||||||
import { formatDistanceToNow } from "@/lib/date";
|
|
||||||
import { getPromptUrl } from "@/lib/urls";
|
import { getPromptUrl } from "@/lib/urls";
|
||||||
import { ArrowBigUp, Lock, Copy, ImageIcon, Download, Play, BadgeCheck, Volume2, Link2 } from "lucide-react";
|
import { ArrowBigUp, Lock, Copy, ImageIcon, Download, Play, BadgeCheck, Volume2, Link2 } from "lucide-react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -91,7 +89,7 @@ export interface PromptCardProps {
|
|||||||
export function PromptCard({ prompt, showPinButton = false, isPinned = false }: PromptCardProps) {
|
export function PromptCard({ prompt, showPinButton = false, isPinned = false }: PromptCardProps) {
|
||||||
const t = useTranslations("prompts");
|
const t = useTranslations("prompts");
|
||||||
const tCommon = useTranslations("common");
|
const tCommon = useTranslations("common");
|
||||||
const locale = useLocale();
|
const _locale = useLocale();
|
||||||
const outgoingCount = prompt._count?.outgoingConnections || 0;
|
const outgoingCount = prompt._count?.outgoingConnections || 0;
|
||||||
const incomingCount = prompt._count?.incomingConnections || 0;
|
const incomingCount = prompt._count?.incomingConnections || 0;
|
||||||
const isFlowStart = outgoingCount > 0 && incomingCount === 0;
|
const isFlowStart = outgoingCount > 0 && incomingCount === 0;
|
||||||
@@ -104,7 +102,7 @@ export function PromptCard({ prompt, showPinButton = false, isPinned = false }:
|
|||||||
const isVideo = prompt.type === "VIDEO";
|
const isVideo = prompt.type === "VIDEO";
|
||||||
const hasMediaBackground = prompt.type === "IMAGE" || isVideo || (isStructuredInput && !!prompt.mediaUrl && !isAudio);
|
const hasMediaBackground = prompt.type === "IMAGE" || isVideo || (isStructuredInput && !!prompt.mediaUrl && !isAudio);
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [_isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
// Autoplay video when visible in viewport
|
// Autoplay video when visible in viewport
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -794,6 +794,7 @@ function FlowGraph({ nodes, edges, currentPromptId, currentUserId, isAdmin, onNo
|
|||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="relative">
|
<div ref={containerRef} className="relative">
|
||||||
<svg ref={svgRef} className="w-full" />
|
<svg ref={svgRef} className="w-full" />
|
||||||
|
{/* eslint-disable-next-line react-hooks/refs -- Container dimensions needed for tooltip positioning */}
|
||||||
{hoveredNode && (() => {
|
{hoveredNode && (() => {
|
||||||
// Calculate position with viewport awareness
|
// Calculate position with viewport awareness
|
||||||
const tooltipWidth = 320;
|
const tooltipWidth = 320;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useTranslations } from "next-intl";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Loader2, Upload, X, ArrowDown, Play, Image as ImageIcon, Video, Volume2, Paperclip, Search, Sparkles, BookOpen, ExternalLink, ChevronDown, Settings2 } from "lucide-react";
|
import { Loader2, Upload, X, ArrowDown, Image as ImageIcon, Video, Volume2, Paperclip, Search, Sparkles, BookOpen, ExternalLink, ChevronDown, Settings2 } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { VariableToolbar } from "./variable-toolbar";
|
import { VariableToolbar } from "./variable-toolbar";
|
||||||
import { VariableWarning } from "./variable-warning";
|
import { VariableWarning } from "./variable-warning";
|
||||||
@@ -21,17 +21,10 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import {
|
import {
|
||||||
parseSkillFiles,
|
|
||||||
serializeSkillFiles,
|
|
||||||
getLanguageFromFilename,
|
|
||||||
validateFilename,
|
|
||||||
suggestFilename,
|
|
||||||
generateSkillContentWithFrontmatter,
|
generateSkillContentWithFrontmatter,
|
||||||
updateSkillFrontmatter,
|
updateSkillFrontmatter,
|
||||||
validateSkillFrontmatter,
|
validateSkillFrontmatter,
|
||||||
DEFAULT_SKILL_FILE,
|
DEFAULT_SKILL_FILE,
|
||||||
DEFAULT_SKILL_CONTENT,
|
|
||||||
type SkillFile,
|
|
||||||
} from "@/lib/skill-files";
|
} from "@/lib/skill-files";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@@ -61,7 +54,7 @@ import { toast } from "sonner";
|
|||||||
import { prettifyJson } from "@/lib/format";
|
import { prettifyJson } from "@/lib/format";
|
||||||
import { analyticsPrompt } from "@/lib/analytics";
|
import { analyticsPrompt } from "@/lib/analytics";
|
||||||
import { getPromptUrl } from "@/lib/urls";
|
import { getPromptUrl } from "@/lib/urls";
|
||||||
import { AI_MODELS, getModelsByProvider, type PromptMCPConfig } from "@/lib/works-best-with";
|
import { AI_MODELS, getModelsByProvider } from "@/lib/works-best-with";
|
||||||
|
|
||||||
interface MediaFieldProps {
|
interface MediaFieldProps {
|
||||||
form: ReturnType<typeof useForm<PromptFormValues>>;
|
form: ReturnType<typeof useForm<PromptFormValues>>;
|
||||||
@@ -1324,6 +1317,7 @@ export function PromptForm({ categories, tags, initialData, initialContributors
|
|||||||
</div>
|
</div>
|
||||||
{/* Code output content */}
|
{/* Code output content */}
|
||||||
<div className="p-4 text-xs space-y-1" style={{ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace' }}>
|
<div className="p-4 text-xs space-y-1" style={{ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace' }}>
|
||||||
|
{/* eslint-disable-next-line react/jsx-no-comment-textnodes -- Intentional code preview text */}
|
||||||
<div><span className="text-[#6a9955]">// Code generated by skill...</span></div>
|
<div><span className="text-[#6a9955]">// Code generated by skill...</span></div>
|
||||||
<div><span className="text-[#c586c0]">export</span> <span className="text-[#569cd6]">function</span> <span className="text-[#dcdcaa]">handler</span><span className="text-[#d4d4d4]">()</span> <span className="text-[#d4d4d4]">{'{'}</span></div>
|
<div><span className="text-[#c586c0]">export</span> <span className="text-[#569cd6]">function</span> <span className="text-[#dcdcaa]">handler</span><span className="text-[#d4d4d4]">()</span> <span className="text-[#d4d4d4]">{'{'}</span></div>
|
||||||
<div><span className="text-[#d4d4d4]"> </span><span className="text-[#c586c0]">return</span> <span className="text-[#ce9178]">"..."</span><span className="text-[#d4d4d4]">;</span></div>
|
<div><span className="text-[#d4d4d4]"> </span><span className="text-[#c586c0]">return</span> <span className="text-[#ce9178]">"..."</span><span className="text-[#d4d4d4]">;</span></div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Sparkles, ArrowBigUp } from "lucide-react";
|
import { ArrowBigUp } from "lucide-react";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { getPromptUrl } from "@/lib/urls";
|
import { getPromptUrl } from "@/lib/urls";
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ export function RunPromptButton({
|
|||||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line react-hooks/immutability -- Valid browser navigation for custom URL schemes
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
analyticsPrompt.run(promptId, platform.name);
|
analyticsPrompt.run(promptId, platform.name);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useCallback, useMemo, useEffect } from "react";
|
import { useState, useCallback, useMemo } from "react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { DiffEditor } from "@monaco-editor/react";
|
import { DiffEditor } from "@monaco-editor/react";
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import {
|
|||||||
validateFilename,
|
validateFilename,
|
||||||
suggestFilename,
|
suggestFilename,
|
||||||
DEFAULT_SKILL_FILE,
|
DEFAULT_SKILL_FILE,
|
||||||
DEFAULT_SKILL_CONTENT,
|
|
||||||
type SkillFile,
|
type SkillFile,
|
||||||
} from "@/lib/skill-files";
|
} from "@/lib/skill-files";
|
||||||
|
|
||||||
@@ -357,6 +356,7 @@ export function SkillEditor({ value, onChange, className }: SkillEditorProps) {
|
|||||||
|
|
||||||
// Only update if the value changed externally
|
// Only update if the value changed externally
|
||||||
if (value !== currentSerialized) {
|
if (value !== currentSerialized) {
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- Intentional sync from external prop
|
||||||
setFiles(parsed);
|
setFiles(parsed);
|
||||||
// Ensure active file exists
|
// Ensure active file exists
|
||||||
if (!parsed.some((f) => f.filename === activeFile)) {
|
if (!parsed.some((f) => f.filename === activeFile)) {
|
||||||
@@ -372,7 +372,7 @@ export function SkillEditor({ value, onChange, className }: SkillEditorProps) {
|
|||||||
|
|
||||||
// File icon based on extension
|
// File icon based on extension
|
||||||
const getFileIcon = (filename: string) => {
|
const getFileIcon = (filename: string) => {
|
||||||
const ext = filename.split(".").pop()?.toLowerCase();
|
const _ext = filename.split(".").pop()?.toLowerCase();
|
||||||
// Could add more specific icons here
|
// Could add more specific icons here
|
||||||
return <File className="h-4 w-4 text-muted-foreground" />;
|
return <File className="h-4 w-4 text-muted-foreground" />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { AlertTriangle, Braces, FileCode } from "lucide-react";
|
import { Braces, FileCode } from "lucide-react";
|
||||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
|||||||
@@ -101,12 +101,12 @@ export function CodeView({ content, language = "json", className, maxLines, font
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{viewMode === "tree" && isValidJson ? (
|
{viewMode === "tree" && isValidJson ? (
|
||||||
<JsonTreeViewWrapper
|
<JsonTreeViewWrapper
|
||||||
content={content}
|
content={content}
|
||||||
className={className}
|
className={className}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
onExpandAll={expandAllRef}
|
onExpandAllRef={expandAllRef}
|
||||||
onCollapseAll={collapseAllRef}
|
onCollapseAllRef={collapseAllRef}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<pre suppressHydrationWarning className={cn("font-mono bg-muted rounded p-2", preview ? "overflow-hidden" : "overflow-y-auto max-h-[500px]", {
|
<pre suppressHydrationWarning className={cn("font-mono bg-muted rounded p-2", preview ? "overflow-hidden" : "overflow-y-auto max-h-[500px]", {
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
import { useState, useMemo, useCallback, useEffect } from "react";
|
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { ChevronRight, ChevronDown, ChevronsDown, ChevronsUp } from "lucide-react";
|
import { ChevronRight, ChevronDown } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
interface JsonNode {
|
interface JsonNode {
|
||||||
key: string | null;
|
key: string | null;
|
||||||
@@ -14,17 +12,46 @@ interface JsonNode {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pure helper function - defined outside component to avoid recreation
|
||||||
|
const getNodeType = (value: unknown): JsonNode["type"] => {
|
||||||
|
if (value === null) return "null";
|
||||||
|
if (Array.isArray(value)) return "array";
|
||||||
|
if (typeof value === "object") return "object";
|
||||||
|
return typeof value as "string" | "number" | "boolean";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recursive helper function - defined outside component
|
||||||
|
const collectExpandablePaths = (value: unknown, path: string, maxDepth: number, depth: number = 0): string[] => {
|
||||||
|
const paths: string[] = [];
|
||||||
|
const type = getNodeType(value);
|
||||||
|
|
||||||
|
if ((type === "object" || type === "array") && depth < maxDepth) {
|
||||||
|
paths.push(path);
|
||||||
|
|
||||||
|
if (type === "array") {
|
||||||
|
(value as unknown[]).forEach((item, index) => {
|
||||||
|
paths.push(...collectExpandablePaths(item, `${path}.${index}`, maxDepth, depth + 1));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Object.entries(value as Record<string, unknown>).forEach(([k, v]) => {
|
||||||
|
paths.push(...collectExpandablePaths(v, `${path}.${k}`, maxDepth, depth + 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
};
|
||||||
|
|
||||||
interface JsonTreeViewProps {
|
interface JsonTreeViewProps {
|
||||||
data: unknown;
|
data: unknown;
|
||||||
className?: string;
|
className?: string;
|
||||||
fontSize?: "xs" | "sm" | "base";
|
fontSize?: "xs" | "sm" | "base";
|
||||||
maxDepth?: number;
|
maxDepth?: number;
|
||||||
onExpandAll?: React.MutableRefObject<(() => void) | undefined>;
|
onExpandAllRef?: React.MutableRefObject<(() => void) | undefined>;
|
||||||
onCollapseAll?: React.MutableRefObject<(() => void) | undefined>;
|
onCollapseAllRef?: React.MutableRefObject<(() => void) | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpandAll, onCollapseAll }: JsonTreeViewProps) {
|
function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpandAllRef, onCollapseAllRef }: JsonTreeViewProps) {
|
||||||
const t = useTranslations("common");
|
|
||||||
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set(["root"]));
|
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set(["root"]));
|
||||||
|
|
||||||
const togglePath = (path: string) => {
|
const togglePath = (path: string) => {
|
||||||
@@ -39,38 +66,9 @@ function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpan
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNodeType = (value: unknown): JsonNode["type"] => {
|
|
||||||
if (value === null) return "null";
|
|
||||||
if (Array.isArray(value)) return "array";
|
|
||||||
if (typeof value === "object") return "object";
|
|
||||||
return typeof value as "string" | "number" | "boolean";
|
|
||||||
};
|
|
||||||
|
|
||||||
// Collect all expandable paths recursively
|
|
||||||
const collectExpandablePaths = useCallback((value: unknown, path: string, depth: number = 0): string[] => {
|
|
||||||
const paths: string[] = [];
|
|
||||||
const type = getNodeType(value);
|
|
||||||
|
|
||||||
if ((type === "object" || type === "array") && depth < maxDepth) {
|
|
||||||
paths.push(path);
|
|
||||||
|
|
||||||
if (type === "array") {
|
|
||||||
(value as unknown[]).forEach((item, index) => {
|
|
||||||
paths.push(...collectExpandablePaths(item, `${path}.${index}`, depth + 1));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Object.entries(value as Record<string, unknown>).forEach(([k, v]) => {
|
|
||||||
paths.push(...collectExpandablePaths(v, `${path}.${k}`, depth + 1));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
}, [maxDepth, getNodeType]);
|
|
||||||
|
|
||||||
const allExpandablePaths = useMemo(() => {
|
const allExpandablePaths = useMemo(() => {
|
||||||
return collectExpandablePaths(data, "root");
|
return collectExpandablePaths(data, "root", maxDepth);
|
||||||
}, [data, collectExpandablePaths]);
|
}, [data, maxDepth]);
|
||||||
|
|
||||||
const expandAll = useCallback(() => {
|
const expandAll = useCallback(() => {
|
||||||
setExpandedPaths(new Set(allExpandablePaths));
|
setExpandedPaths(new Set(allExpandablePaths));
|
||||||
@@ -109,7 +107,7 @@ function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpan
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderNode = (node: JsonNode, depth: number = 0, isLast: boolean = true): React.ReactNode => {
|
const renderNode = (node: JsonNode, depth: number = 0, _isLast: boolean = true): React.ReactNode => {
|
||||||
const { key, value, type, path } = node;
|
const { key, value, type, path } = node;
|
||||||
const isExpanded = expandedPaths.has(path);
|
const isExpanded = expandedPaths.has(path);
|
||||||
const isComplex = type === "object" || type === "array";
|
const isComplex = type === "object" || type === "array";
|
||||||
@@ -239,13 +237,13 @@ function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpan
|
|||||||
|
|
||||||
// Expose expand/collapse functions via useEffect
|
// Expose expand/collapse functions via useEffect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onExpandAll) {
|
if (onExpandAllRef) {
|
||||||
onExpandAll.current = expandAll;
|
onExpandAllRef.current = expandAll;
|
||||||
}
|
}
|
||||||
if (onCollapseAll) {
|
if (onCollapseAllRef) {
|
||||||
onCollapseAll.current = collapseAll;
|
onCollapseAllRef.current = collapseAll;
|
||||||
}
|
}
|
||||||
}, [expandAll, collapseAll, onExpandAll, onCollapseAll]);
|
}, [expandAll, collapseAll, onExpandAllRef, onCollapseAllRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -264,18 +262,18 @@ function JsonTreeView({ data, className, fontSize = "xs", maxDepth = 10, onExpan
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function JsonTreeViewWrapper({
|
export function JsonTreeViewWrapper({
|
||||||
content,
|
content,
|
||||||
className,
|
className,
|
||||||
fontSize = "xs",
|
fontSize = "xs",
|
||||||
onExpandAll,
|
onExpandAllRef,
|
||||||
onCollapseAll
|
onCollapseAllRef
|
||||||
}: {
|
}: {
|
||||||
content: string;
|
content: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
fontSize?: "xs" | "sm" | "base";
|
fontSize?: "xs" | "sm" | "base";
|
||||||
onExpandAll?: React.MutableRefObject<(() => void) | undefined>;
|
onExpandAllRef?: React.MutableRefObject<(() => void) | undefined>;
|
||||||
onCollapseAll?: React.MutableRefObject<(() => void) | undefined>;
|
onCollapseAllRef?: React.MutableRefObject<(() => void) | undefined>;
|
||||||
}) {
|
}) {
|
||||||
const parsedData = useMemo(() => {
|
const parsedData = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
@@ -294,12 +292,12 @@ export function JsonTreeViewWrapper({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JsonTreeView
|
<JsonTreeView
|
||||||
data={parsedData}
|
data={parsedData}
|
||||||
className={className}
|
className={className}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
onExpandAll={onExpandAll}
|
onExpandAllRef={onExpandAllRef}
|
||||||
onCollapseAll={onCollapseAll}
|
onCollapseAllRef={onCollapseAllRef}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { PrismaAdapter } from "@auth/prisma-adapter";
|
|||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { getConfig } from "@/lib/config";
|
import { getConfig } from "@/lib/config";
|
||||||
import { initializePlugins, getAuthPlugin } from "@/lib/plugins";
|
import { initializePlugins, getAuthPlugin } from "@/lib/plugins";
|
||||||
import type { User } from "@prisma/client";
|
|
||||||
import type { Adapter, AdapterUser } from "next-auth/adapters";
|
import type { Adapter, AdapterUser } from "next-auth/adapters";
|
||||||
|
|
||||||
// Initialize plugins before use
|
// Initialize plugins before use
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { z } from "zod";
|
|||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { isValidApiKeyFormat } from "@/lib/api-key";
|
import { isValidApiKeyFormat } from "@/lib/api-key";
|
||||||
import { improvePrompt } from "@/lib/ai/improve-prompt";
|
import { improvePrompt } from "@/lib/ai/improve-prompt";
|
||||||
import { parseSkillFiles, serializeSkillFiles, DEFAULT_SKILL_FILE, DEFAULT_SKILL_CONTENT } from "@/lib/skill-files";
|
import { parseSkillFiles, serializeSkillFiles, DEFAULT_SKILL_FILE } from "@/lib/skill-files";
|
||||||
|
|
||||||
interface AuthenticatedUser {
|
interface AuthenticatedUser {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user