@@ -15,7 +15,7 @@ import {
1515 Alert ,
1616} from "@mui/material" ;
1717import { Colors } from "design/theme" ;
18- import React , { useState } from "react" ;
18+ import React , { useState , useEffect } from "react" ;
1919import { FileItem } from "redux/projects/types/projects.interface" ;
2020
2121interface LLMPanelProps {
@@ -29,47 +29,112 @@ interface LLMProvider {
2929 models : Array < { id : string ; name : string } > ;
3030 noApiKey ?: boolean ;
3131 isAnthropic ?: boolean ;
32+ customUrl ?: boolean ;
3233}
3334
3435const llmProviders : Record < string , LLMProvider > = {
3536 ollama : {
36- name : "Ollama (Local)" ,
37+ name : "Ollama (Local Server )" ,
3738 baseUrl : "http://localhost:11434/v1/chat/completions" ,
3839 models : [
40+ { id : "qwen3-coder:30b" , name : "Qwen 3 Coder" } ,
3941 { id : "qwen2.5-coder:latest" , name : "Qwen 2.5 Coder" } ,
4042 { id : "codellama:latest" , name : "Code Llama" } ,
4143 { id : "llama3.1:latest" , name : "Llama 3.1" } ,
44+ { id : "mistral:latest" , name : "Mistral" } ,
45+ { id : "deepseek-coder:latest" , name : "DeepSeek Coder" } ,
4246 ] ,
4347 noApiKey : true ,
48+ customUrl : true ,
4449 } ,
4550 groq : {
46- name : "Groq" ,
51+ name : "Groq (Free API Key - 14,400 req/day) " ,
4752 baseUrl : "https://api.groq.com/openai/v1/chat/completions" ,
4853 models : [
4954 { id : "llama-3.3-70b-versatile" , name : "Llama 3.3 70B" } ,
5055 { id : "llama-3.1-8b-instant" , name : "Llama 3.1 8B (Fast)" } ,
56+ { id : "mixtral-8x7b-32768" , name : "Mixtral 8x7B" } ,
57+ ] ,
58+ } ,
59+ openrouter : {
60+ name : "OpenRouter (Free models available)" ,
61+ baseUrl : "https://openrouter.ai/api/v1/chat/completions" ,
62+ models : [
63+ {
64+ id : "meta-llama/llama-3.1-8b-instruct:free" ,
65+ name : "Llama 3.1 8B (Free)" ,
66+ } ,
67+ { id : "google/gemma-2-9b-it:free" , name : "Gemma 2 9B (Free)" } ,
68+ { id : "mistralai/mistral-7b-instruct:free" , name : "Mistral 7B (Free)" } ,
5169 ] ,
5270 } ,
5371 anthropic : {
54- name : "Anthropic" ,
72+ name : "Anthropic Claude (Paid) " ,
5573 baseUrl : "https://api.anthropic.com/v1/messages" ,
5674 models : [
5775 { id : "claude-sonnet-4-20250514" , name : "Claude Sonnet 4" } ,
5876 { id : "claude-3-5-haiku-20241022" , name : "Claude 3.5 Haiku" } ,
5977 ] ,
6078 isAnthropic : true ,
6179 } ,
80+ openai : {
81+ name : "OpenAI (Paid)" ,
82+ baseUrl : "https://api.openai.com/v1/chat/completions" ,
83+ models : [
84+ { id : "gpt-4o-mini" , name : "GPT-4o Mini" } ,
85+ { id : "gpt-4o" , name : "GPT-4o" } ,
86+ ] ,
87+ } ,
6288} ;
6389
6490const LLMPanel : React . FC < LLMPanelProps > = ( { files, onClose } ) => {
65- const [ provider , setProvider ] = useState < string > ( "groq" ) ;
66- const [ model , setModel ] = useState < string > ( "llama-3.3-70b-versatile" ) ;
91+ const [ provider , setProvider ] = useState < string > ( "ollama" ) ;
92+ const [ model , setModel ] = useState < string > ( "qwen3-coder:30b" ) ;
93+ const [ ollamaUrl , setOllamaUrl ] = useState < string > (
94+ "http://huo.neu.edu:11434"
95+ ) ;
6796 const [ apiKey , setApiKey ] = useState < string > ( "" ) ;
6897 const [ generatedScript , setGeneratedScript ] = useState < string > ( "" ) ;
6998 const [ loading , setLoading ] = useState ( false ) ;
7099 const [ error , setError ] = useState < string | null > ( null ) ;
71100 const [ status , setStatus ] = useState < string > ( "" ) ;
72101
102+ const [ panelHeight , setPanelHeight ] = useState < number > ( 350 ) ;
103+ const [ isResizing , setIsResizing ] = useState ( false ) ;
104+
105+ const handleMouseDown = ( e : React . MouseEvent ) => {
106+ setIsResizing ( true ) ;
107+ e . preventDefault ( ) ;
108+ } ;
109+
110+ const handleMouseMove = ( e : MouseEvent ) => {
111+ if ( ! isResizing ) return ;
112+
113+ const newHeight = window . innerHeight - e . clientY ;
114+ if ( newHeight >= 100 && newHeight <= window . innerHeight - 100 ) {
115+ setPanelHeight ( newHeight ) ;
116+ }
117+ } ;
118+
119+ const handleMouseUp = ( ) => {
120+ setIsResizing ( false ) ;
121+ } ;
122+
123+ // Add event listeners
124+ useEffect ( ( ) => {
125+ if ( isResizing ) {
126+ document . addEventListener ( "mousemove" , handleMouseMove ) ;
127+ document . addEventListener ( "mouseup" , handleMouseUp ) ;
128+ document . body . style . cursor = "ns-resize" ;
129+
130+ return ( ) => {
131+ document . removeEventListener ( "mousemove" , handleMouseMove ) ;
132+ document . removeEventListener ( "mouseup" , handleMouseUp ) ;
133+ document . body . style . cursor = "" ;
134+ } ;
135+ }
136+ } , [ isResizing ] ) ;
137+
73138 const currentProvider = llmProviders [ provider ] ;
74139
75140 const buildFileSummary = (
@@ -126,7 +191,27 @@ Output ONLY the Python script.`;
126191 try {
127192 let response ;
128193
129- if ( currentProvider . isAnthropic ) {
194+ if ( provider === "ollama" ) {
195+ const ollamaBaseUrl = ollamaUrl || "http://localhost:11434" ;
196+ response = await fetch ( `${ ollamaBaseUrl } /v1/chat/completions` , {
197+ method : "POST" ,
198+ headers : {
199+ "Content-Type" : "application/json" ,
200+ } ,
201+ body : JSON . stringify ( {
202+ model,
203+ messages : [
204+ {
205+ role : "system" ,
206+ content :
207+ "You are a neuroimaging data expert specializing in BIDS format conversion. Output only Python code." ,
208+ } ,
209+ { role : "user" , content : prompt } ,
210+ ] ,
211+ stream : false ,
212+ } ) ,
213+ } ) ;
214+ } else if ( currentProvider . isAnthropic ) {
130215 response = await fetch ( currentProvider . baseUrl , {
131216 method : "POST" ,
132217 headers : {
@@ -214,14 +299,38 @@ Output ONLY the Python script.`;
214299 bottom : 0 ,
215300 left : 0 ,
216301 right : 0 ,
217- height : "50vh" ,
302+ height : ` ${ panelHeight } px` ,
218303 zIndex : 1000 ,
219304 borderTop : 2 ,
220305 borderColor : Colors . purple ,
221306 display : "flex" ,
222307 flexDirection : "column" ,
223308 } }
224309 >
310+ { /* Resize Handle */ }
311+ < Box
312+ onMouseDown = { handleMouseDown }
313+ sx = { {
314+ height : 6 ,
315+ backgroundColor : isResizing ? Colors . lightGray : Colors . lightGray ,
316+ cursor : "ns-resize" ,
317+ display : "flex" ,
318+ alignItems : "center" ,
319+ justifyContent : "center" ,
320+ "&:hover" : {
321+ backgroundColor : Colors . lightGray ,
322+ } ,
323+ } }
324+ >
325+ < Box
326+ sx = { {
327+ width : 40 ,
328+ height : 3 ,
329+ backgroundColor : Colors . secondaryPurple ,
330+ // borderRadius: 2,
331+ } }
332+ />
333+ </ Box >
225334 { /* Header */ }
226335 < Box
227336 sx = { {
@@ -290,6 +399,18 @@ Output ONLY the Python script.`;
290399 </ Select >
291400 </ FormControl >
292401
402+ { /* ✅ ADD THIS: Ollama Server URL field */ }
403+ { provider === "ollama" && (
404+ < TextField
405+ fullWidth
406+ label = "Ollama Server URL"
407+ value = { ollamaUrl }
408+ onChange = { ( e ) => setOllamaUrl ( e . target . value ) }
409+ placeholder = "http://localhost:11434"
410+ sx = { { mb : 2 } }
411+ />
412+ ) }
413+
293414 { ! currentProvider . noApiKey && (
294415 < TextField
295416 fullWidth
0 commit comments