From a32f3a5faf5f603319784a84e62866be22ed577a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=8E=E4=BD=95=E5=BC=80=E5=A7=8B123?= <64304674+yeahhe365@users.noreply.github.com> Date: Fri, 9 Jan 2026 01:07:03 +0800 Subject: [PATCH] bu --- .DS_Store | Bin 6148 -> 6148 bytes prisma/.env.example | 11 -- prisma/.gitignore | 2 - prisma/api.ts | 21 +++- prisma/components/ChatMessage.tsx | 15 +++ prisma/components/InputSection.tsx | 118 +++++++++++++++++++--- prisma/config.ts | 66 ------------ prisma/hooks/useAppLogic.ts | 11 +- prisma/hooks/useDeepThink.ts | 27 +++-- prisma/index.html | 22 ++-- prisma/metadata.json | 5 + prisma/services/deepThink/expert.ts | 40 +++++++- prisma/services/deepThink/manager.ts | 53 +++++++++- prisma/services/deepThink/openaiClient.ts | 7 +- prisma/services/deepThink/synthesis.ts | 40 +++++++- prisma/types.ts | 14 ++- prisma/utils.ts | 17 ++++ 17 files changed, 334 insertions(+), 135 deletions(-) delete mode 100644 prisma/.env.example create mode 100644 prisma/metadata.json diff --git a/.DS_Store b/.DS_Store index d8fb93eca21711aa494e5eaf8e0288b976af92ca..b963966d636f508681f767f5f9e38c6d1ef41dcf 100644 GIT binary patch delta 214 zcmZoMXfc=|#>B`mu~2NHo+2ab#DLw5tdn_IeCtF1g8>5wa4{4x6ftBn6f@*9Br+&4 zXfPNuXnN-4Cnx3PCjsRJ7#J8|0%->%J$%^oFz7K<0nI5u)yotG)Z2}umy;oAayz>+ vJJb!E1v$i6Ha5Ix+|16w&jEBTP~bcBWPTA#pv6F|85o$LG|T1)ku}T!jm2~CoUem>6JIo>Yo)x)DtnrOeH;Eb#+yB*O%$(X#l|VZgB)a2LO#4 z$5;zJej$9Ew=>e9EX{<-bIdrbkat*~`aEGI5d#qe|26}B@5W#dN?=0}-oN;T#*A-5 zL=nw!A7)?@rr`l(U^?tgwy<{-l)4{0^2Bz&=o(Qmu7P+h~+isCn z(`?r*`XV+X2eh~r&*^;!2V?1kV1 zkiL3C+7;H}B`Kl$?LtM>gkS45ebzhcRmEFc+uAR7=$&2NiLRcm-rmHezRUery02c# z>D_s=uwC-_dMBB~ytK1x<@64ps-7t@ciyuqRBbk}_JWN4l!m%~3Rtj;8z-j4{WAF^ zP7EUHUC*-I)skly=QcNeE6ASGS00&ri$Ah=_yvzS&OGz&z-I2snpj9+?aa8Fj3(!c znJe=lf64NELcZ8gOm@ZeJku>%>HT%b%v;W!n`S}4cCt1V#ETHKXUb;5c1x+-$CB1~ z${3cK4yiiRDWmdH=vty~Qi!Sdwp15g=2{e;mEDwaPq}DtgI?!{ut6{BGk68B;SIcl z5AX?&u>sq#A5%DlH*o^fIEAyggv+>vPq2bjtjQb=^;h7!%BcXKlM;FbDjCmpm|G${ zkBP$qZKOLe1+#>Mv`LELO#(;cb$a_z_k2YyU28qxv{AN*frx?sfC2t}NK)fyD6B~o zpALG6D*$1P%tA;Pzy5?tZXh%i)+CaJ1Z60p3`KRtKy`CK8BXJd@~=sh;ecXh%A;mR zbwfchJK2-M9ng>%JtGDp2Eq(9%C64O|LNb~|HDC1FJd5K;J;!3V^f)_G?k>z)-%QN yvo=ZXF*PpSuO?B1&_f-^Lg7boA2lH~mgEYdp|B>AEF{s7fS@6IL=5~@2EGH70s<@m diff --git a/prisma/.env.example b/prisma/.env.example deleted file mode 100644 index 1e8e72a..0000000 --- a/prisma/.env.example +++ /dev/null @@ -1,11 +0,0 @@ -# API Keys Configuration -# Copy this file to .env.local and add your actual API keys - -# Primary API Key (used by default) -# For Google Gemini: https://ai.google.dev/ -# For OpenAI: https://platform.openai.com/ -VITE_API_KEY=your_api_key_here - -# Alternative: Use provider-specific keys (optional) -# GEMINI_API_KEY=your_gemini_key_here -# OPENAI_API_KEY=your_openai_key_here diff --git a/prisma/.gitignore b/prisma/.gitignore index 70454b8..a547bf3 100644 --- a/prisma/.gitignore +++ b/prisma/.gitignore @@ -22,5 +22,3 @@ dist-ssr *.njsproj *.sln *.sw? -.env -# .env.example is allowed by default diff --git a/prisma/api.ts b/prisma/api.ts index afeddf7..7a14457 100644 --- a/prisma/api.ts +++ b/prisma/api.ts @@ -32,7 +32,7 @@ let currentCustomApiUrl: string | null = null; const originalFetch = typeof window !== 'undefined' ? window.fetch.bind(window) : null; if (typeof window !== 'undefined' && originalFetch) { - window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise => { + const proxyFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise => { let urlString: string; if (typeof input === 'string') { urlString = input; @@ -56,11 +56,26 @@ if (typeof window !== 'undefined' && originalFetch) { return originalFetch(input, init); }; + + try { + window.fetch = proxyFetch; + } catch (e) { + try { + Object.defineProperty(window, 'fetch', { + value: proxyFetch, + writable: true, + configurable: true, + enumerable: true + }); + } catch (e2) { + console.error('[API] Failed to intercept fetch:', e2); + } + } } export const getAI = (config?: AIProviderConfig) => { const provider = config?.provider || 'google'; - const apiKey = config?.apiKey || (import.meta.env as any).VITE_API_KEY || process.env.API_KEY; + const apiKey = config?.apiKey || import.meta.env?.VITE_API_KEY || process.env.API_KEY; if (provider === 'openai' || provider === 'deepseek' || provider === 'custom' || provider === 'anthropic' || provider === 'xai' || provider === 'mistral') { const options: any = { @@ -135,4 +150,4 @@ export const getAIProvider = (model: string): ApiProvider => { return 'custom'; } return 'google'; -}; +}; \ No newline at end of file diff --git a/prisma/components/ChatMessage.tsx b/prisma/components/ChatMessage.tsx index 61728b6..4bf7073 100644 --- a/prisma/components/ChatMessage.tsx +++ b/prisma/components/ChatMessage.tsx @@ -102,6 +102,21 @@ const ChatMessageItem = ({ message, isLast }: ChatMessageProps) => { )} + {/* Attachments */} + {message.attachments && message.attachments.length > 0 && ( +
+ {message.attachments.map(att => ( + attachment window.open(att.url || `data:${att.mimeType};base64,${att.data}`, '_blank')} + /> + ))} +
+ )} + {/* Text Content */}
{message.content ? ( diff --git a/prisma/components/InputSection.tsx b/prisma/components/InputSection.tsx index ae0a3b9..ad94666 100644 --- a/prisma/components/InputSection.tsx +++ b/prisma/components/InputSection.tsx @@ -1,12 +1,13 @@ import React, { useRef, useLayoutEffect, useState, useEffect } from 'react'; -import { ArrowUp, Square } from 'lucide-react'; -import { AppState } from '../types'; +import { ArrowUp, Square, Paperclip, X, Image as ImageIcon } from 'lucide-react'; +import { AppState, MessageAttachment } from '../types'; +import { fileToBase64 } from '../utils'; interface InputSectionProps { query: string; setQuery: (q: string) => void; - onRun: () => void; + onRun: (attachments: MessageAttachment[]) => void; onStop: () => void; appState: AppState; focusTrigger?: number; @@ -14,7 +15,9 @@ interface InputSectionProps { const InputSection = ({ query, setQuery, onRun, onStop, appState, focusTrigger }: InputSectionProps) => { const textareaRef = useRef(null); + const fileInputRef = useRef(null); const [isComposing, setIsComposing] = useState(false); + const [attachments, setAttachments] = useState([]); const adjustHeight = () => { if (textareaRef.current) { @@ -36,8 +39,7 @@ const InputSection = ({ query, setQuery, onRun, onStop, appState, focusTrigger } } }; - // Focus input on mount and when app becomes idle (e.g. after "New Chat" or completion) - // or when explicitly triggered by focusTrigger + // Focus input on mount and when app becomes idle useEffect(() => { if (appState === 'idle' && textareaRef.current) { textareaRef.current.focus(); @@ -49,39 +51,125 @@ const InputSection = ({ query, setQuery, onRun, onStop, appState, focusTrigger } adjustHeight(); }, [query]); + const processFile = async (file: File) => { + if (!file.type.startsWith('image/')) return; + + try { + const base64 = await fileToBase64(file); + const newAttachment: MessageAttachment = { + id: Math.random().toString(36).substring(7), + type: 'image', + mimeType: file.type, + data: base64, + url: URL.createObjectURL(file) + }; + setAttachments(prev => [...prev, newAttachment]); + } catch (e) { + console.error("Failed to process file", e); + } + }; + + const handlePaste = (e: React.ClipboardEvent) => { + const items = e.clipboardData.items; + for (let i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image') !== -1) { + const file = items[i].getAsFile(); + if (file) { + e.preventDefault(); + processFile(file); + } + } + } + }; + + const handleFileSelect = (e: React.ChangeEvent) => { + if (e.target.files) { + Array.from(e.target.files).forEach(processFile); + } + // Reset input so same file can be selected again + if (fileInputRef.current) fileInputRef.current.value = ''; + }; + + const removeAttachment = (id: string) => { + setAttachments(prev => prev.filter(a => a.id !== id)); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { - // If user presses Enter without Shift if (e.key === 'Enter' && !e.shiftKey) { - // robust check for IME composition (e.g. Chinese/Japanese inputs) if (isComposing || (e.nativeEvent as any).isComposing) { return; } - e.preventDefault(); - if (query.trim() && appState === 'idle') { - onRun(); + if ((query.trim() || attachments.length > 0) && appState === 'idle') { + handleSubmit(); } } }; + const handleSubmit = () => { + if (!query.trim() && attachments.length === 0) return; + onRun(attachments); + setAttachments([]); + }; + const isRunning = appState !== 'idle'; return (
- {/* Container: Flex items-end ensures button stays at bottom right as text grows */} + {/* Attachments Preview */} + {attachments.length > 0 && ( +
+ {attachments.map(att => ( +
+ attachment + +
+ ))} +
+ )} + + {/* Input Container */}
+ + + +