()
| 85 | } |
| 86 | |
| 87 | const Shell: React.FC = () => { |
| 88 | const [chatOpen, setChatOpen] = useState(true); |
| 89 | const [reportEnabled, setReportEnabled] = useState(true); |
| 90 | const [lang, setLang] = useState<'en' | 'zh'>('en'); |
| 91 | const [liveWallpaper, setLiveWallpaper] = useState(false); |
| 92 | const [uploadOpen, setUploadOpen] = useState(false); |
| 93 | const [uploadedFile, setUploadedFile] = useState<File | null>(null); |
| 94 | const [extractResult, setExtractResult] = useState<ExtractResult | null>(null); |
| 95 | const [extracting, setExtracting] = useState(false); |
| 96 | const [modGenerating, setModGenerating] = useState(false); |
| 97 | const fileInputRef = useRef<HTMLInputElement>(null); |
| 98 | |
| 99 | const handleFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { |
| 100 | const file = e.target.files?.[0]; |
| 101 | if (file) { |
| 102 | setUploadedFile(file); |
| 103 | setExtractResult(null); |
| 104 | } |
| 105 | if (fileInputRef.current) fileInputRef.current.value = ''; |
| 106 | }, []); |
| 107 | |
| 108 | const handleRemoveFile = useCallback(() => { |
| 109 | setUploadedFile(null); |
| 110 | setExtractResult(null); |
| 111 | }, []); |
| 112 | |
| 113 | const generateMod = useCallback(async (character: Manifest['character']): Promise<string> => { |
| 114 | const llmConfig = await loadConfig(); |
| 115 | if (!llmConfig) { |
| 116 | throw new Error( |
| 117 | 'No LLM configuration found. Please open Settings (gear icon) and configure your LLM API key first.', |
| 118 | ); |
| 119 | } |
| 120 | |
| 121 | const prompt = buildModPrompt([], JSON.stringify({ character, apps: [] })); |
| 122 | logger.info('Shell', 'Mod generation prompt built, length:', prompt.length); |
| 123 | |
| 124 | setModGenerating(true); |
| 125 | try { |
| 126 | const response = await chat([{ role: 'user', content: prompt }], [], llmConfig); |
| 127 | logger.info('Shell', 'Mod generation LLM response length:', response.content.length); |
| 128 | |
| 129 | let jsonStr = response.content.trim(); |
| 130 | const fenceMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/); |
| 131 | if (fenceMatch) jsonStr = fenceMatch[1].trim(); |
| 132 | |
| 133 | let modJson: Record<string, unknown>; |
| 134 | try { |
| 135 | modJson = JSON.parse(jsonStr); |
| 136 | } catch { |
| 137 | throw new Error('LLM returned invalid JSON. Try again or use a different model.'); |
| 138 | } |
| 139 | |
| 140 | const modId = generateModId(); |
| 141 | const modConfig: ModConfig = { |
| 142 | id: modId, |
| 143 | mod_name: (modJson.name as string) || (modJson.identifier as string) || 'Generated Mod', |
| 144 | mod_name_en: (modJson.name as string) || (modJson.identifier as string) || 'Generated Mod', |
nothing calls this directly
no test coverage detected