walidsobhie-code commited on
Commit
a9b0b85
·
1 Parent(s): faf9686
hf_space/Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HuggingFace Spaces Dockerfile for Stack 2.9
2
+ # Use this for free inference hosting on HF Spaces
3
+ # https://huggingface.co/docs/hub/spaces-sdks-docker
4
+
5
+ FROM python:3.11-slim
6
+
7
+ # Set environment
8
+ ENV PYTHONUNBUFFERED=1
9
+ ENV PORT=7860
10
+
11
+ # Install dependencies
12
+ RUN pip install --no-cache-dir \
13
+ fastapi \
14
+ uvicorn[standard] \
15
+ pydantic \
16
+ requests \
17
+ huggingface_hub
18
+
19
+ # Copy app
20
+ COPY app.py .
21
+
22
+ # Expose port
23
+ EXPOSE 7860
24
+
25
+ # Run app
26
+ CMD ["python", "app.py"]
hf_space/README.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stack 2.9 - Fine-tuned Code Assistant
2
+
3
+ A 1.5B parameter code generation model, fine-tuned from Qwen2.5-Coder on Stack Overflow Q&A data.
4
+
5
+ **Model:** [my-ai-stack/Stack-2-9-finetuned](https://huggingface.co/my-ai-stack/Stack-2-9-finetuned)
6
+ **Live Demo:** [stack-2-9-demo](https://huggingface.co/spaces/my-ai-stack/stack-2-9-demo)
7
+
8
+ ## Quick Start
9
+
10
+ ```python
11
+ from transformers import AutoModelForCausalLM, AutoTokenizer
12
+
13
+ model = AutoModelForCausalLM.from_pretrained("my-ai-stack/Stack-2-9-finetuned")
14
+ tokenizer = AutoTokenizer.from_pretrained("my-ai-stack/Stack-2-9-finetuned")
15
+ ```
16
+
17
+ ## Hardware Requirements
18
+
19
+ | Config | GPU | VRAM |
20
+ |--------|-----|------|
21
+ | FP16 | RTX 3060+ | ~4GB |
22
+ | 8-bit | RTX 3060+ | ~2GB |
23
+ | 4-bit | Any modern GPU | ~1GB |
24
+
25
+ ## License
26
+
27
+ Apache 2.0
hf_space/app.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stack 2.9 HuggingFace Space
2
+ # Fine-tuned code assistant powered by Qwen2.5-Coder-1.5B
3
+
4
+ app = gr.Blocks(title="Stack 2.9")
5
+
6
+ with app:
7
+ gr.Markdown("""
8
+ # 💻 Stack 2.9 - Code Assistant
9
+ Fine-tuned on Stack Overflow data · 1.5B parameters · Qwen2.5-Coder base
10
+
11
+ ---
12
+ """)
13
+
14
+ with gr.Row():
15
+ with gr.Column(scale=3):
16
+ chatbot = gr.Chatbot(label="Stack 2.9", height=500)
17
+ msg = gr.Textbox(
18
+ label="Your message",
19
+ placeholder="Ask me to write or explain code...",
20
+ lines=3
21
+ )
22
+ with gr.Row():
23
+ submit_btn = gr.Button("Send", variant="primary")
24
+ clear_btn = gr.Button("Clear")
25
+
26
+ with gr.Column(scale=1):
27
+ gr.Markdown("### ⚙️ Settings")
28
+ temperature = gr.Slider(0.1, 1.5, 0.7, label="Temperature")
29
+ max_tokens = gr.Slider(64, 2048, 1024, step=64, label="Max tokens")
30
+ system_prompt = gr.Textbox(
31
+ value="You are Stack 2.9, a helpful coding assistant.",
32
+ label="System prompt",
33
+ lines=2
34
+ )
35
+ gr.Markdown("### 📊 Model Info")
36
+ gr.Markdown("""
37
+ - **Base**: Qwen2.5-Coder-1.5B
38
+ - **Fine-tuned**: Stack Overflow Q&A
39
+ - **Context**: 32K tokens
40
+ - **License**: Apache 2.0
41
+ """)
42
+
43
+ def respond(message, history, system, temp, tokens):
44
+ import torch
45
+ from transformers import AutoModelForCausalLM, AutoTokenizer
46
+
47
+ model_name = "my-ai-stack/Stack-2-9-finetuned"
48
+ tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
49
+ model = AutoModelForCausalLM.from_pretrained(
50
+ model_name,
51
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
52
+ device_map="auto" if torch.cuda.is_available() else None,
53
+ trust_remote_code=True
54
+ )
55
+
56
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": message}]
57
+ text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
58
+ inputs = tokenizer([text], return_tensors="pt").to(model.device)
59
+ outputs = model.generate(**inputs, max_new_tokens=tokens, temperature=temp, do_sample=True, pad_token_id=tokenizer.pad_token_id)
60
+ response = tokenizer.decode(outputs[0][len(inputs[0]):], skip_special_tokens=True)
61
+ return response
62
+
63
+ submit_btn.click(respond, inputs=[msg, chatbot, system_prompt, temperature, max_tokens], outputs=chatbot)
64
+ msg.submit(respond, inputs=[msg, chatbot, system_prompt, temperature, max_tokens], outputs=chatbot)
65
+ clear_btn.click(lambda: None, outputs=chatbot)
66
+
67
+ app.launch()
requirements_webui.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ streamlit
2
+ requests
src/cli/agent.py CHANGED
@@ -31,6 +31,7 @@ class QueryIntent(Enum):
31
  TASK = "task"
32
  QUESTION = "question"
33
  GENERAL = "general"
 
34
 
35
 
36
  @dataclass
@@ -80,6 +81,7 @@ class QueryUnderstanding:
80
  QueryIntent.FILE_SEARCH: [
81
  r"find\s+(?:files?\s+)?(?:named\s+)?(.+)",
82
  r"search\s+for\s+(?:files?\s+)?(.+)",
 
83
  r"where\s+is\s+(.+)",
84
  r"locate\s+(.+)",
85
  ],
@@ -88,24 +90,33 @@ class QueryUnderstanding:
88
  r"(commit|push|pull|branch)\s+(?:to\s+)?(?:the\s+)?(?:repo|repository)?",
89
  ],
90
  QueryIntent.CODE_EXECUTION: [
91
- r"run\s+(?:the\s+)?(?:command\s+)?(.+)",
92
- r"execute\s+(.+)",
93
- r"start\s+(?:the\s+)?(?:server\s+)?(.+)",
94
- r"test\s+(?:the\s+)?(.+)",
95
- r"lint\s+(.+)",
96
- r"format\s+(.+)",
97
  ],
98
  QueryIntent.WEB_SEARCH: [
99
- r"search\s+(?:the\s+)?web\s+for\s+(.+)",
100
- r"google\s+(.+)",
101
- r"look\s+up\s+(.+)",
102
- r"find\s+information\s+about\s+(.+)",
 
 
103
  ],
104
  QueryIntent.MEMORY: [
105
  r"(remember|recall|what do you remember)\s+(.+)",
106
  r"(save|store)\s+(?:to\s+)?memory\s+(.+)",
107
  r"what('s| is)\s+in\s+(?:the\s+)?memory",
108
  ],
 
 
 
 
 
 
 
109
  QueryIntent.TASK: [
110
  r"(create|add|new)\s+task\s+(.+)",
111
  r"list\s+(?:my\s+)?tasks?",
@@ -182,6 +193,7 @@ class ToolSelector:
182
  QueryIntent.WEB_SEARCH: ["web_search", "fetch"],
183
  QueryIntent.MEMORY: ["memory_recall", "memory_save", "memory_list"],
184
  QueryIntent.TASK: ["create_task", "list_tasks", "update_task"],
 
185
  }
186
 
187
  def select(self, intent: str, context: Dict[str, Any]) -> List[str]:
@@ -198,6 +210,7 @@ class ToolSelector:
198
  "memory": QueryIntent.MEMORY,
199
  "task": QueryIntent.TASK,
200
  "general": QueryIntent.GENERAL,
 
201
  }
202
 
203
  tools = []
@@ -260,21 +273,73 @@ class ToolSelector:
260
  r"search\s+(?:the\s+)?web\s+for\s+(.+)",
261
  r"google\s+(.+)",
262
  r"look\s+up\s+(.+)",
 
 
263
  ]
264
  for pattern in patterns:
265
  match = re.search(pattern, query, re.IGNORECASE)
266
  if match:
267
- params["query"] = match.group(1).strip()
 
 
 
 
 
268
  break
269
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  return params
271
 
272
 
273
  class ResponseGenerator:
274
  """Generates natural language responses."""
275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  def __init__(self):
277
  self.context_manager = create_context_manager()
 
 
278
 
279
  def generate(
280
  self,
@@ -283,14 +348,47 @@ class ResponseGenerator:
283
  context: Dict[str, Any]
284
  ) -> str:
285
  """Generate response from tool results."""
 
 
 
 
 
 
286
  if not tool_results:
287
- return "I couldn't find any results for your query."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
  responses = []
 
290
 
291
  for call in tool_results:
292
  if call.result is None:
293
- responses.append(f"I tried to use {call.tool_name} but got no result.")
294
  continue
295
 
296
  if call.result.get("success"):
@@ -304,6 +402,16 @@ class ResponseGenerator:
304
  content = content[:500] + "..."
305
  responses.append(f"Here's the content:\n```\n{content}\n```")
306
 
 
 
 
 
 
 
 
 
 
 
307
  elif call.tool_name == "grep":
308
  if "matches" in result:
309
  matches = result["matches"]
@@ -313,7 +421,7 @@ class ResponseGenerator:
313
  resp += f"- {m.get('file', '?')}:{m.get('line', '?')} - {m.get('content', '')}\n"
314
  responses.append(resp)
315
  else:
316
- responses.append("No matches found.")
317
 
318
  elif call.tool_name in ["git_status", "git_log"]:
319
  if "files" in result:
 
31
  TASK = "task"
32
  QUESTION = "question"
33
  GENERAL = "general"
34
+ GENERAL_HELP = "general_help"
35
 
36
 
37
  @dataclass
 
81
  QueryIntent.FILE_SEARCH: [
82
  r"find\s+(?:files?\s+)?(?:named\s+)?(.+)",
83
  r"search\s+for\s+(?:files?\s+)?(.+)",
84
+ r"grep\s+for\s+(.+)",
85
  r"where\s+is\s+(.+)",
86
  r"locate\s+(.+)",
87
  ],
 
90
  r"(commit|push|pull|branch)\s+(?:to\s+)?(?:the\s+)?(?:repo|repository)?",
91
  ],
92
  QueryIntent.CODE_EXECUTION: [
93
+ r"^run\s+(?:the\s+)?(?:command\s+)?(.+)",
94
+ r"^execute\s+(.+)",
95
+ r"^start\s+(?:the\s+)?(?:server\s+)?(.+)",
96
+ r"^test\s+(?:the\s+)?(.+)",
97
+ r"^lint\s+(.+)",
98
+ r"^format\s+(.+)",
99
  ],
100
  QueryIntent.WEB_SEARCH: [
101
+ r"^search\s+(?:the\s+)?web\s+for\s+(.+)",
102
+ r"^google\s+(.+)",
103
+ r"^look\s+up\s+(.+)",
104
+ r"^find\s+information\s+about\s+(.+)",
105
+ r"latest\s+ai\s+news",
106
+ r"what('s|\s+is)\s+new\s+in\s+ai",
107
  ],
108
  QueryIntent.MEMORY: [
109
  r"(remember|recall|what do you remember)\s+(.+)",
110
  r"(save|store)\s+(?:to\s+)?memory\s+(.+)",
111
  r"what('s| is)\s+in\s+(?:the\s+)?memory",
112
  ],
113
+ QueryIntent.GENERAL_HELP: [
114
+ r"list\s+(?:all\s+)?tools?",
115
+ r"what\s+tools\s+(?:do\s+you\s+have|can\s+you\s+do)",
116
+ r"help\s+me",
117
+ r"what\s+can\s+you\s+do",
118
+ r"how\s+to\s+use\s+you",
119
+ ],
120
  QueryIntent.TASK: [
121
  r"(create|add|new)\s+task\s+(.+)",
122
  r"list\s+(?:my\s+)?tasks?",
 
193
  QueryIntent.WEB_SEARCH: ["web_search", "fetch"],
194
  QueryIntent.MEMORY: ["memory_recall", "memory_save", "memory_list"],
195
  QueryIntent.TASK: ["create_task", "list_tasks", "update_task"],
196
+ QueryIntent.GENERAL_HELP: [],
197
  }
198
 
199
  def select(self, intent: str, context: Dict[str, Any]) -> List[str]:
 
210
  "memory": QueryIntent.MEMORY,
211
  "task": QueryIntent.TASK,
212
  "general": QueryIntent.GENERAL,
213
+ "general_help": QueryIntent.GENERAL_HELP,
214
  }
215
 
216
  tools = []
 
273
  r"search\s+(?:the\s+)?web\s+for\s+(.+)",
274
  r"google\s+(.+)",
275
  r"look\s+up\s+(.+)",
276
+ r"latest\s+ai\s+news",
277
+ r"what('s|\s+is)\s+new\s+in\s+ai",
278
  ]
279
  for pattern in patterns:
280
  match = re.search(pattern, query, re.IGNORECASE)
281
  if match:
282
+ # Only extract group(1) if it exists
283
+ if match.groups():
284
+ params["query"] = match.group(1).strip()
285
+ else:
286
+ # For patterns without capture groups, use full match
287
+ params["query"] = match.group(0).strip()
288
  break
289
 
290
+ elif tool_name in ("grep", "search"):
291
+ # Extract pattern to search for - capture full phrase
292
+ # Strategy: split by " in " and take second part as path
293
+ parts = query.split(' in ')
294
+ if len(parts) >= 2:
295
+ # Last part is the path
296
+ path_part = ' in '.join(parts[1:])
297
+ # Clean up path
298
+ if path_part.strip() in ['project', 'this project']:
299
+ path_part = '/Users/walidsobhi/stack-2.9/src'
300
+ elif path_part.strip() == 'src':
301
+ path_part = '/Users/walidsobhi/stack-2.9/src'
302
+ elif path_part.startswith('~') or path_part.startswith('/') or path_part.startswith('./'):
303
+ pass # Keep as-is
304
+ else:
305
+ path_part = '/Users/walidsobhi/stack-2.9/' + path_part.strip()
306
+ params["path"] = path_part
307
+ # First part is the pattern (remove grep/for/search)
308
+ pattern_part = parts[0]
309
+ for prefix in ['grep for', 'search for', 'find for', 'grep', 'search', 'find']:
310
+ if pattern_part.strip().lower().startswith(prefix):
311
+ pattern_part = pattern_part.strip()[len(prefix):].strip()
312
+ break
313
+ params["pattern"] = pattern_part
314
+ else:
315
+ # Default path is workspace root
316
+ params["path"] = "/Users/walidsobhi/stack-2.9/src"
317
+
318
  return params
319
 
320
 
321
  class ResponseGenerator:
322
  """Generates natural language responses."""
323
 
324
+ GREETING_VARIATIONS = [
325
+ "Sure! I can help with that.",
326
+ "Got it! Let me assist with that.",
327
+ "No problem! Here's what I found:",
328
+ "Alright! Here you go:",
329
+ "Sure thing! Let me show you:",
330
+ ]
331
+
332
+ HELP_RESPONSES = [
333
+ "I support these operations:",
334
+ "Here are some things I can do:",
335
+ "Here's my toolkit:",
336
+ "I can help with the following:",
337
+ ]
338
+
339
  def __init__(self):
340
  self.context_manager = create_context_manager()
341
+ self.last_intent = None
342
+ self.last_query = None
343
 
344
  def generate(
345
  self,
 
348
  context: Dict[str, Any]
349
  ) -> str:
350
  """Generate response from tool results."""
351
+ import random
352
+
353
+ # Track intent for conversation flow
354
+ previous_intent = self.last_intent
355
+ self.last_intent = intent
356
+
357
  if not tool_results:
358
+ # Handle queries that don't need tools
359
+ if intent == "question":
360
+ return ("I can help with reading/writing files, running commands, "
361
+ "git operations, web search, and more. "
362
+ "Try asking me something like 'read the file README.md' "
363
+ "or 'check git status'.")
364
+ elif intent == "general_help":
365
+ greeting = random.choice(self.HELP_RESPONSES)
366
+ return (f"{greeting}\n"
367
+ "- Read/write/edit files\n"
368
+ "- Run commands and code\n"
369
+ "- Git operations (status, commit, push, pull)\n"
370
+ "- Code search with grep\n"
371
+ "- Web search\n"
372
+ "- Manage tasks\n\n"
373
+ "Examples:\n"
374
+ "- 'read the file /path/to/file'\n"
375
+ "- 'check git status'\n"
376
+ "- 'grep for def main in ~/project/src'\n"
377
+ "- 'run the command ls'\n"
378
+ "- 'what is 2 + 2'")
379
+ elif intent == "general":
380
+ # Don't repeat the same greeting
381
+ if previous_intent == "general":
382
+ return "What would you like me to help you with?"
383
+ return "What can I help you with?"
384
+ return None
385
 
386
  responses = []
387
+ greeting = random.choice(self.GREETING_VARIATIONS) if tool_results else None
388
 
389
  for call in tool_results:
390
  if call.result is None:
391
+ responses.append(f"Hmm, {call.tool_name} didn't return anything.")
392
  continue
393
 
394
  if call.result.get("success"):
 
402
  content = content[:500] + "..."
403
  responses.append(f"Here's the content:\n```\n{content}\n```")
404
 
405
+ elif call.tool_name == "search":
406
+ # Skip search tool if it has no matches - grep will show results
407
+ if "matches" in result and result["matches"]:
408
+ matches = result["matches"]
409
+ resp = f"Found {len(matches)} matches:\n"
410
+ for m in matches[:10]:
411
+ resp += f"- {m.get('file', '?')}:{m.get('line', '?')} - {m.get('content', '')}\n"
412
+ responses.append(resp)
413
+ # else: skip - grep will show results
414
+
415
  elif call.tool_name == "grep":
416
  if "matches" in result:
417
  matches = result["matches"]
 
421
  resp += f"- {m.get('file', '?')}:{m.get('line', '?')} - {m.get('content', '')}\n"
422
  responses.append(resp)
423
  else:
424
+ responses.append("Didn't find any matches for that.")
425
 
426
  elif call.tool_name in ["git_status", "git_log"]:
427
  if "files" in result:
src/cli/main.py CHANGED
@@ -107,9 +107,6 @@ class Stack29CLI:
107
  response = self.agent.process(user_input)
108
  print(response.content)
109
 
110
- if response.tool_calls:
111
- print(f"\n{self.YELLOW}[Tools called: {', '.join(tc.tool_name for tc in response.tool_calls)}]{self.END}")
112
-
113
  except Exception as e:
114
  print(f"{self.RED}Error: {e}{self.END}")
115
 
 
107
  response = self.agent.process(user_input)
108
  print(response.content)
109
 
 
 
 
110
  except Exception as e:
111
  print(f"{self.RED}Error: {e}{self.END}")
112
 
src/cli/tools.py CHANGED
@@ -92,7 +92,8 @@ def tool_search_files(
92
  ) -> Dict[str, Any]:
93
  """Recursively search for files matching a pattern."""
94
  try:
95
- base_path = Path(path)
 
96
  if not base_path.exists():
97
  return {"success": False, "error": f"Path not found: {path}"}
98
 
@@ -121,7 +122,8 @@ def tool_search_files(
121
  def tool_grep(path: str, pattern: str, context: int = 0) -> Dict[str, Any]:
122
  """Search for pattern in file(s)."""
123
  try:
124
- base_path = Path(path)
 
125
  results = []
126
 
127
  if base_path.is_file():
 
92
  ) -> Dict[str, Any]:
93
  """Recursively search for files matching a pattern."""
94
  try:
95
+ # Expand ~ to home directory
96
+ base_path = Path(os.path.expanduser(path))
97
  if not base_path.exists():
98
  return {"success": False, "error": f"Path not found: {path}"}
99
 
 
122
  def tool_grep(path: str, pattern: str, context: int = 0) -> Dict[str, Any]:
123
  """Search for pattern in file(s)."""
124
  try:
125
+ # Expand ~ to home directory
126
+ base_path = Path(os.path.expanduser(path))
127
  results = []
128
 
129
  if base_path.is_file():
web_ui.py CHANGED
@@ -3,8 +3,9 @@ Stack 2.9 - Web UI Chat
3
  Simple web interface using Streamlit
4
  """
5
  import streamlit as st
6
- from typing import List, Dict
7
  import os
 
 
8
 
9
  # Configure page
10
  st.set_page_config(
@@ -13,10 +14,6 @@ st.set_page_config(
13
  layout="wide"
14
  )
15
 
16
- # Model configuration
17
- MODEL_NAME = os.environ.get("MODEL_NAME", "minimax-m2.5:cloud")
18
- PROVIDER = os.environ.get("MODEL_PROVIDER", "ollama")
19
-
20
  # Title
21
  st.title("💻 Stack 2.9")
22
  st.caption("AI Coding Assistant")
@@ -27,7 +24,7 @@ with st.sidebar:
27
 
28
  model = st.selectbox(
29
  "Model",
30
- ["minimax-m2.5:cloud", "qwen2.5-coder:1.5b", "llama3"],
31
  index=0
32
  )
33
 
@@ -64,11 +61,10 @@ if prompt := st.chat_input("Type your message..."):
64
  with st.chat_message("assistant"):
65
  with st.spinner("Thinking..."):
66
  try:
67
- import requests
68
-
69
- # Call Ollama API
70
  response = requests.post(
71
- f"http://localhost:11434/api/chat",
72
  json={
73
  "model": model,
74
  "messages": [
@@ -78,17 +74,32 @@ if prompt := st.chat_input("Type your message..."):
78
  "temperature": temperature,
79
  "max_tokens": max_tokens
80
  },
81
- timeout=120
 
82
  )
83
 
84
  if response.status_code == 200:
85
- result = response.json()
86
- assistant_msg = result["message"]["content"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  else:
88
- assistant_msg = f"Error: {response.status_code}"
89
 
90
  except Exception as e:
91
- assistant_msg = f"Error: {str(e)}"
92
 
93
  st.markdown(assistant_msg)
94
  st.session_state.messages.append({"role": "assistant", "content": assistant_msg})
 
3
  Simple web interface using Streamlit
4
  """
5
  import streamlit as st
 
6
  import os
7
+ import requests
8
+ import json
9
 
10
  # Configure page
11
  st.set_page_config(
 
14
  layout="wide"
15
  )
16
 
 
 
 
 
17
  # Title
18
  st.title("💻 Stack 2.9")
19
  st.caption("AI Coding Assistant")
 
24
 
25
  model = st.selectbox(
26
  "Model",
27
+ ["minimax-m2.5:cloud", "qwen2.5-coder:1.5b"],
28
  index=0
29
  )
30
 
 
61
  with st.chat_message("assistant"):
62
  with st.spinner("Thinking..."):
63
  try:
64
+ import json
65
+ # Use local Ollama - your minimax is registered there
 
66
  response = requests.post(
67
+ "http://localhost:11434/api/chat",
68
  json={
69
  "model": model,
70
  "messages": [
 
74
  "temperature": temperature,
75
  "max_tokens": max_tokens
76
  },
77
+ timeout=120,
78
+ stream=False
79
  )
80
 
81
  if response.status_code == 200:
82
+ text = response.text.strip()
83
+ # Try to parse each line until we get content
84
+ assistant_msg = ""
85
+ for line in text.split('\n'):
86
+ if line.strip():
87
+ try:
88
+ result = json.loads(line)
89
+ content = result.get("message", {}).get("content", "")
90
+ if content:
91
+ assistant_msg = content
92
+ break
93
+ except:
94
+ continue
95
+
96
+ if not assistant_msg:
97
+ assistant_msg = text
98
  else:
99
+ assistant_msg = f"Error: {response.status_code}\n{response.text[:200]}"
100
 
101
  except Exception as e:
102
+ assistant_msg = f"Connection Error: {str(e)}\n\nMake sure Ollama is running with: ollama serve"
103
 
104
  st.markdown(assistant_msg)
105
  st.session_state.messages.append({"role": "assistant", "content": assistant_msg})