kirikir13 commited on
Commit
d3330ee
·
verified ·
1 Parent(s): 27b6df6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -172
app.py CHANGED
@@ -11,16 +11,22 @@ from pathlib import Path
11
  from typing import List, Optional, Generator
12
 
13
  # --- Config ----------------------------------------------------------------
14
- MODEL_REPO = os.environ.get("MODEL_REPO", "DavidAU/Qwen3.6-27B-Heretic-Uncensored-FINETUNE-NEO-CODE-Di-IMatrix-MAX-GGUF")
15
- MODEL_FILE = os.environ.get("MODEL_FILE", "Qwen3.6-27B-NEO-CODE-HERE-2T-OT-Q4_K_M.gguf")
16
- N_CTX = int(os.environ.get("N_CTX", "8192"))
17
- N_GPU = int(os.environ.get("N_GPU_LAYERS", "-1")) # -1 = all layers on GPU
 
 
 
 
 
 
18
 
19
  SYSTEM_PROMPT = """You are codeMax, an uncensored 27B coding assistant. You excel at:
20
- Writing production code in Python, TypeScript, JavaScript, Rust, Go, C/C++, SQL, shell
21
- Debugging, refactoring, reviewing complex codebases
22
- Explaining algorithms, architecture, system design
23
- Reading shared files and answering about them
24
 
25
  Rules: put code in ```language blocks. Be precise and thorough."""
26
 
@@ -28,6 +34,7 @@ Rules: put code in ```language blocks. Be precise and thorough."""
28
  _llm = None
29
  _model_path: Optional[str] = None
30
 
 
31
  def _download():
32
  global _model_path
33
  if _model_path is not None:
@@ -35,9 +42,10 @@ def _download():
35
  from huggingface_hub import hf_hub_download
36
  print(f"[MODEL] Downloading {MODEL_FILE}...")
37
  _model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
38
- print(f"[MODEL] Cached {_model_path}")
39
  return _model_path
40
 
 
41
  def _load():
42
  global _llm
43
  if _llm is not None:
@@ -45,13 +53,21 @@ def _load():
45
  from llama_cpp import Llama
46
  path = _download()
47
  print(f"[MODEL] Loading (n_gpu_layers={N_GPU})...")
48
- _llm = Llama(model_path=path, n_ctx=N_CTX, n_gpu_layers=N_GPU,
49
- chat_format="chatml", verbose=False, seed=-1)
 
 
 
 
 
 
50
  print("[MODEL] Ready.")
51
  return _llm
52
 
53
- # --- File parsers ----------------------------------------------------------
 
54
  def _read_text(path, enc="utf-8"):
 
55
  for e in [enc, "utf-8-sig", "latin-1", "cp1252", "utf-16"]:
56
  try:
57
  with open(path, "r", encoding=e) as f:
@@ -61,29 +77,39 @@ def _read_text(path, enc="utf-8"):
61
  with open(path, "r", encoding="utf-8", errors="replace") as f:
62
  return f.read()
63
 
64
- CODE_EXTS = {".txt",".md",".py",".ts",".tsx",".js",".jsx",".mjs",".cjs",
65
- ".css",".scss",".less",".html",".htm",".xml",".svg",
66
- ".yaml",".yml",".toml",".ini",".cfg",".conf",
67
- ".sh",".bash",".zsh",".fish",".ps1",".bat",
68
- ".sql",".graphql",".prisma",".cs",
69
- ".c",".cpp",".cc",".cxx",".h",".hpp",".hh",
70
- ".java",".kt",".kts",".scala",".groovy",
71
- ".go",".rs",".rb",".php",".pl",".pm",
72
- ".swift",".r",".lua",".zig",".nim",".dart",
73
- ".env",".gitignore",".editorconfig",
74
- ".tf",".tfvars",".hcl",".vue",".svelte",".astro"}
75
 
76
  def parse_file(file_path, file_name):
 
77
  suffix = Path(file_name).suffix.lower()
78
  name = Path(file_name).name
79
- if suffix in CODE_EXTS:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  content = _read_text(file_path)
81
  lang = suffix.lstrip(".")
82
- if suffix == ".md": lang = "markdown"
83
- elif suffix in (".yml",): lang = "yaml"
84
- elif suffix in (".tf",".tfvars"): lang = "hcl"
85
- elif suffix in (".htm",): lang = "html"
86
- return f"### 📄 `{name}`\n```{lang}\n{content}\n```\n\n---\n"
 
 
 
 
 
 
87
  if suffix == ".json":
88
  content = _read_text(file_path)
89
  try:
@@ -91,210 +117,214 @@ def parse_file(file_path, file_name):
91
  content = json.dumps(parsed, indent=2, ensure_ascii=False)
92
  except Exception:
93
  pass
94
- return f"### 📄 `{name}`\n```json\n{content}\n```\n\n---\n"
 
 
95
  if suffix == ".csv":
96
- import csv, io
 
97
  content = _read_text(file_path)
98
  rows = list(csv.reader(io.StringIO(content)))
99
  if len(rows) > 51:
100
- rows = rows[:50] + [[f" {len(rows)-50} rows truncated"]]
101
- w = [max(len(str(c)) for c in col) for col in zip(*rows)]
102
- sep = "|-" + "-|-".join("-"*x for x in w) + "-|"
103
- t = "\n".join("| " + " | ".join(str(c).ljust(w[i]) for i,c in enumerate(r)) + " |" for r in rows)
104
- t = t.split("\n",1); t.insert(1,sep)
105
- return f"### 📊 `{name}`\n" + "\n".join(t) + "\n\n---\n"
 
 
 
 
 
 
106
  if suffix == ".docx":
107
  try:
108
- import docx; d=docx.Document(file_path)
109
- return f"### 📝 `{name}`\n"+"\n\n".join(p.text for p in d.paragraphs if p.text.strip())+"\n\n---\n"
 
 
110
  except Exception as e:
111
- return f"### ⚠️ `{name}` (DOCX error: {e})\n\n---\n"
 
 
112
  if suffix == ".pdf":
113
  try:
114
  import pdfplumber
115
  with pdfplumber.open(file_path) as pdf:
116
- t = "\n\n".join(p.extract_text() or "" for p in pdf.pages)
117
- return f"### 📑 `{name}`\n{t}\n\n---\n"
 
 
118
  except Exception as e:
119
- return f"### ⚠️ `{name}` (PDF error: {e})\n\n---\n"
 
 
120
  try:
121
- return f"### 📄 `{name}`\n```\n{_read_text(file_path)[:10000]}\n```\n\n---\n"
 
122
  except Exception as e:
123
- return f"### `{name}` ({e})\n\n---\n"
 
124
 
125
  def parse_all_files(files):
126
- if not files: return ""
127
- out = []
 
 
128
  for f in files:
129
  if isinstance(f, dict):
130
  path = f.get("path") or f.get("name")
131
- name = f.get("orig_name") or f.get("name","unknown")
132
- elif hasattr(f,"name"):
133
- path = f.name; name = getattr(f,"orig_name",Path(path).name)
 
134
  else:
135
- path=str(f); name=Path(path).name
 
136
  try:
137
- out.append(parse_file(path,name))
138
  except Exception as e:
139
- out.append(f"### `{name}`\n{e}\n\n---\n")
140
- return "\n".join(out)
 
141
 
142
- # --- Generation ------------------------------------------------------------
143
  @spaces.GPU
144
- def respond(message: str, history: List, uploaded_files) -> Generator[List, None, None]:
145
- fc = parse_all_files(uploaded_files)
146
- msgs = [{"role":"system","content":SYSTEM_PROMPT}]
147
- if fc:
148
- msgs.append({"role":"user","content":"[Uploaded files — read carefully]\n\n"+fc})
149
- msgs.append({"role":"assistant","content":"Got it! I've read all the files. Ask me anything."})
 
 
 
 
 
 
 
 
 
 
 
150
  for entry in history:
151
- r = entry.get("role","user"); c = entry.get("content","")
152
- if c: msgs.append({"role":r,"content":c})
153
- msgs.append({"role":"user","content":message})
 
 
 
 
 
 
154
  llm = _load()
155
- stream = llm.create_chat_completion(messages=msgs, temperature=0.6, top_p=0.8,
156
- top_k=20, max_tokens=4096, stream=True)
 
 
 
 
 
 
 
157
  partial = ""
158
  for chunk in stream:
159
  if chunk.get("choices"):
160
- d = chunk["choices"][0].get("delta",{}).get("content","")
161
- if d: partial += d
162
- yield history + [{"role":"user","content":message},
163
- {"role":"assistant","content":partial}]
164
- yield history + [{"role":"user","content":message},
165
- {"role":"assistant","content":partial}]
166
-
167
- # --- UI --------------------------------------------------------------------
168
- def create_demo():
169
- with gr.Blocks(title="codeMax — Qwen 3.6 27B Coder") as demo:
170
- gr.Markdown("# 🧠 codeMax\n**Qwen 3.6 27B · Uncensored · Code-optimized · ZeroGPU**")
171
- with gr.Row(equal_height=True):
172
- with gr.Column(scale=3):
173
- chatbot = gr.Chatbot(label="Chat", height=580,
174
- avatar_images=(None,"https://huggingface.co/front/assets/huggingface_logo-noborder.svg"))
175
- with gr.Row():
176
- msg = gr.Textbox(placeholder="Ask about code, share files, or chat...",
177
- scale=8, show_label=False, container=False)
178
- send = gr.Button("▶", scale=1, variant="primary", min_width=48)
179
- clear_btn = gr.Button("🗑 Clear", size="sm")
180
- with gr.Column(scale=1, elem_classes="file-upload-col"):
181
- gr.Markdown("### 📎 Drop Files")
182
- files = gr.File(file_count="multiple", label="Code, docs, data...",
183
- file_types=list(CODE_EXTS)+[".json",".csv",".docx",".pdf"])
184
- uploaded_info = gr.Markdown("_No files uploaded._")
185
 
186
- def update_info(uploaded):
187
- if not uploaded: return "_No files uploaded._"
188
- names = [f.get("orig_name","?") if isinstance(f,dict) else getattr(f,"orig_name",Path(str(f)).name) for f in uploaded]
189
- return "**Uploaded:**\n"+"\n".join(f"• `{n}`" for n in names)
190
 
191
- def stream_response(message, history, current_files):
192
- for h in respond(message, history, current_files):
193
- yield h
194
 
195
- msg.submit(stream_response, [msg, chatbot, files], [chatbot]).then(lambda:"", None, [msg])
196
- send.click(stream_response, [msg, chatbot, files], [chatbot]).then(lambda:"", None, [msg])
197
- clear_btn.click(lambda: [], None, chatbot, queue=False)
198
- files.change(update_info, files, uploaded_info)
199
- return demo
 
 
 
200
 
201
- if __name__ == "__main__":
202
- demo = create_demo()
203
- demo.queue(default_concurrency_limit=1, max_size=4)
204
- demo.launch(css=""".file-upload-col{background:var(--background-fill-secondary);border-radius:12px;padding:16px}footer{display:none!important}""")
 
 
 
 
 
 
 
205
  with gr.Row():
206
  msg = gr.Textbox(
207
- placeholder="Ask about your code, or just start chatting …",
208
  scale=8,
209
  show_label=False,
210
  container=False,
211
  )
212
- send = gr.Button("", scale=1, variant="primary", min_width=48)
213
- with gr.Row():
214
- clear_btn = gr.Button("🗑 Clear chat", size="sm", scale=1)
215
 
216
- # ---- RIGHT: File upload + info ----
217
- with gr.Column(scale=1, elem_classes="file-upload-col"):
218
- gr.Markdown("### 📎 Drop Files")
219
  files = gr.File(
220
  file_count="multiple",
221
- label="Upload code, docs, data",
222
  file_types=[
223
- ".py", ".ts", ".tsx", ".js", ".jsx", ".mjs",
224
- ".json", ".yaml", ".yml", ".toml",
225
- ".md", ".txt", ".csv",
226
- ".html", ".css", ".scss", ".xml", ".svg",
227
- ".c", ".cpp", ".h", ".hpp", ".java", ".kt",
228
- ".go", ".rs", ".rb", ".php", ".swift",
229
- ".sql", ".sh", ".bash", ".ps1",
230
- ".docx", ".pdf",
231
- ".env", ".gitignore", ".cfg", ".ini", ".conf",
232
- ".vue", ".svelte", ".dockerfile",
233
  ],
234
  )
235
- gr.Markdown(
236
- """
237
- **Supported:** `.py` `.ts` `.js` `.json` `.md` `.docx` `.pdf` `.csv`
238
- `.yaml` `.toml` `.html` `.css` `.sql` `.go` `.rs` `.java` + more
239
-
240
- Files are parsed and sent as context — the model reads them
241
- before answering.
242
- """
243
- )
244
- uploaded_info = gr.Markdown("_No files uploaded yet._")
245
- file_content_state = gr.State("")
246
-
247
- # ---- Event wiring ----
248
 
249
- def update_file_info(uploaded):
 
250
  if not uploaded:
251
- return "_No files uploaded._", ""
252
  names = []
253
  for f in uploaded:
254
  if isinstance(f, dict):
255
  names.append(f.get("orig_name", "?"))
256
  else:
257
  names.append(getattr(f, "orig_name", Path(str(f)).name))
258
- label = "**Uploaded:**\n" + "\n".join(f" `{n}`" for n in names)
259
- return label, parse_all_files(uploaded)
260
-
261
- def respond(message, history, current_files):
262
- """Generator wrapper that streams into the chatbot."""
263
- for updated_history in chatbot_respond(message, history, current_files):
264
- yield updated_history
265
-
266
- # Trigger response on text submit or send button
267
- msg.submit(
268
- respond,
269
- inputs=[msg, chatbot, files],
270
- outputs=[chatbot],
271
- ).then(lambda: "", None, [msg])
272
-
273
- send.click(
274
- respond,
275
- inputs=[msg, chatbot, files],
276
- outputs=[chatbot],
277
- ).then(lambda: "", None, [msg])
278
-
279
- # Clear chat — return empty list (Gradio 6 "messages" format)
280
- clear_btn.click(lambda: [], None, chatbot, queue=False)
281
 
282
- # File upload feedback
283
- files.change(update_file_info, files, [uploaded_info, file_content_state])
 
284
 
285
- return demo
 
 
 
 
 
 
 
286
 
 
287
 
288
- # ===========================================================================
289
- # LAUNCH
290
- # ===========================================================================
291
 
292
  if __name__ == "__main__":
293
  demo = create_demo()
294
  demo.queue(default_concurrency_limit=1, max_size=4)
295
  demo.launch(
296
  css="""
297
- .file-upload-col { background: var(--background-fill-secondary); border-radius: 12px; padding: 16px; }
298
  footer { display: none !important; }
299
- """,
300
- )
 
11
  from typing import List, Optional, Generator
12
 
13
  # --- Config ----------------------------------------------------------------
14
+ MODEL_REPO = os.environ.get(
15
+ "MODEL_REPO",
16
+ "DavidAU/Qwen3.6-27B-Heretic-Uncensored-FINETUNE-NEO-CODE-Di-IMatrix-MAX-GGUF",
17
+ )
18
+ MODEL_FILE = os.environ.get(
19
+ "MODEL_FILE",
20
+ "Qwen3.6-27B-NEO-CODE-HERE-2T-OT-Q4_K_M.gguf",
21
+ )
22
+ N_CTX = int(os.environ.get("N_CTX", "8192"))
23
+ N_GPU = int(os.environ.get("N_GPU_LAYERS", "-1"))
24
 
25
  SYSTEM_PROMPT = """You are codeMax, an uncensored 27B coding assistant. You excel at:
26
+ - Writing production code in Python, TypeScript, JavaScript, Rust, Go, C/C++, SQL, shell
27
+ - Debugging, refactoring, reviewing complex codebases
28
+ - Explaining algorithms, architecture, system design
29
+ - Reading shared files and answering about them
30
 
31
  Rules: put code in ```language blocks. Be precise and thorough."""
32
 
 
34
  _llm = None
35
  _model_path: Optional[str] = None
36
 
37
+
38
  def _download():
39
  global _model_path
40
  if _model_path is not None:
 
42
  from huggingface_hub import hf_hub_download
43
  print(f"[MODEL] Downloading {MODEL_FILE}...")
44
  _model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
45
+ print(f"[MODEL] Cached -> {_model_path}")
46
  return _model_path
47
 
48
+
49
  def _load():
50
  global _llm
51
  if _llm is not None:
 
53
  from llama_cpp import Llama
54
  path = _download()
55
  print(f"[MODEL] Loading (n_gpu_layers={N_GPU})...")
56
+ _llm = Llama(
57
+ model_path=path,
58
+ n_ctx=N_CTX,
59
+ n_gpu_layers=N_GPU,
60
+ chat_format="chatml",
61
+ verbose=False,
62
+ seed=-1,
63
+ )
64
  print("[MODEL] Ready.")
65
  return _llm
66
 
67
+
68
+ # --- File readers ----------------------------------------------------------
69
  def _read_text(path, enc="utf-8"):
70
+ """Safe text reader with encoding fallback."""
71
  for e in [enc, "utf-8-sig", "latin-1", "cp1252", "utf-16"]:
72
  try:
73
  with open(path, "r", encoding=e) as f:
 
77
  with open(path, "r", encoding="utf-8", errors="replace") as f:
78
  return f.read()
79
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  def parse_file(file_path, file_name):
82
+ """Parse a single uploaded file into context-ready markdown."""
83
  suffix = Path(file_name).suffix.lower()
84
  name = Path(file_name).name
85
+
86
+ # ---- Code / text files ----
87
+ if suffix in (
88
+ ".txt", ".md", ".py", ".ts", ".tsx", ".js", ".jsx", ".mjs",
89
+ ".css", ".scss", ".less", ".html", ".htm", ".xml", ".svg",
90
+ ".yaml", ".yml", ".toml", ".ini", ".cfg", ".conf",
91
+ ".sh", ".bash", ".zsh", ".fish", ".ps1", ".bat",
92
+ ".sql", ".prisma",
93
+ ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hh",
94
+ ".java", ".kt", ".kts", ".scala", ".groovy",
95
+ ".go", ".rs", ".rb", ".php", ".pl", ".pm",
96
+ ".swift", ".r", ".lua", ".zig", ".nim", ".dart",
97
+ ".env", ".gitignore", ".editorconfig",
98
+ ".tf", ".tfvars", ".hcl", ".vue", ".svelte", ".astro",
99
+ ):
100
  content = _read_text(file_path)
101
  lang = suffix.lstrip(".")
102
+ if suffix == ".md":
103
+ lang = "markdown"
104
+ elif suffix in (".yml",):
105
+ lang = "yaml"
106
+ elif suffix in (".tf", ".tfvars"):
107
+ lang = "hcl"
108
+ elif suffix in (".htm",):
109
+ lang = "html"
110
+ return f"### `{name}`\n```{lang}\n{content}\n```\n\n---\n"
111
+
112
+ # ---- JSON ----
113
  if suffix == ".json":
114
  content = _read_text(file_path)
115
  try:
 
117
  content = json.dumps(parsed, indent=2, ensure_ascii=False)
118
  except Exception:
119
  pass
120
+ return f"### `{name}`\n```json\n{content}\n```\n\n---\n"
121
+
122
+ # ---- CSV ----
123
  if suffix == ".csv":
124
+ import csv
125
+ import io
126
  content = _read_text(file_path)
127
  rows = list(csv.reader(io.StringIO(content)))
128
  if len(rows) > 51:
129
+ rows = rows[:50] + [[f"... {len(rows) - 50} rows truncated"]]
130
+ widths = [max(len(str(c)) for c in col) for col in zip(*rows)]
131
+ sep = "|-" + "-|-".join("-" * x for x in widths) + "-|"
132
+ table = "\n".join(
133
+ "| " + " | ".join(str(c).ljust(widths[i]) for i, c in enumerate(row)) + " |"
134
+ for row in rows
135
+ )
136
+ table = table.split("\n", 1)
137
+ table.insert(1, sep)
138
+ return f"### `{name}`\n" + "\n".join(table) + "\n\n---\n"
139
+
140
+ # ---- Word documents ----
141
  if suffix == ".docx":
142
  try:
143
+ import docx
144
+ doc = docx.Document(file_path)
145
+ text = "\n\n".join(p.text for p in doc.paragraphs if p.text.strip())
146
+ return f"### `{name}`\n{text}\n\n---\n"
147
  except Exception as e:
148
+ return f"### `{name}` (DOCX error: {e})\n\n---\n"
149
+
150
+ # ---- PDF ----
151
  if suffix == ".pdf":
152
  try:
153
  import pdfplumber
154
  with pdfplumber.open(file_path) as pdf:
155
+ text = "\n\n".join(
156
+ page.extract_text() or "" for page in pdf.pages
157
+ )
158
+ return f"### `{name}`\n{text}\n\n---\n"
159
  except Exception as e:
160
+ return f"### `{name}` (PDF error: {e})\n\n---\n"
161
+
162
+ # ---- Fallback ----
163
  try:
164
+ content = _read_text(file_path)
165
+ return f"### `{name}`\n```\n{content[:10000]}\n```\n\n---\n"
166
  except Exception as e:
167
+ return f"### `{name}` (could not read: {e})\n\n---\n"
168
+
169
 
170
  def parse_all_files(files):
171
+ """Parse all uploaded files into a single context string."""
172
+ if not files:
173
+ return ""
174
+ parts = []
175
  for f in files:
176
  if isinstance(f, dict):
177
  path = f.get("path") or f.get("name")
178
+ name = f.get("orig_name") or f.get("name", "unknown")
179
+ elif hasattr(f, "name"):
180
+ path = f.name
181
+ name = getattr(f, "orig_name", Path(path).name)
182
  else:
183
+ path = str(f)
184
+ name = Path(path).name
185
  try:
186
+ parts.append(parse_file(path, name))
187
  except Exception as e:
188
+ parts.append(f"### `{name}`\nParse error: {e}\n\n---\n")
189
+ return "\n".join(parts)
190
+
191
 
192
+ # --- Generation (wrapped with @spaces.GPU for ZeroGPU) ---------------------
193
  @spaces.GPU
194
+ def respond(message, history, uploaded_files):
195
+ """Generate a streaming response from the model."""
196
+ file_context = parse_all_files(uploaded_files)
197
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
198
+
199
+ # Inject uploaded files as context
200
+ if file_context:
201
+ messages.append({
202
+ "role": "user",
203
+ "content": "[Uploaded files — read carefully]\n\n" + file_context,
204
+ })
205
+ messages.append({
206
+ "role": "assistant",
207
+ "content": "Got it! I've read through all the uploaded files.",
208
+ })
209
+
210
+ # Append conversation history (Gradio 6 "messages" format)
211
  for entry in history:
212
+ role = entry.get("role", "user")
213
+ content = entry.get("content", "")
214
+ if content:
215
+ messages.append({"role": role, "content": content})
216
+
217
+ # Append the current user message
218
+ messages.append({"role": "user", "content": message})
219
+
220
+ # Stream from the model
221
  llm = _load()
222
+ stream = llm.create_chat_completion(
223
+ messages=messages,
224
+ temperature=0.6,
225
+ top_p=0.8,
226
+ top_k=20,
227
+ max_tokens=4096,
228
+ stream=True,
229
+ )
230
+
231
  partial = ""
232
  for chunk in stream:
233
  if chunk.get("choices"):
234
+ delta = chunk["choices"][0].get("delta", {})
235
+ content = delta.get("content", "")
236
+ if content:
237
+ partial += content
238
+ yield history + [
239
+ {"role": "user", "content": message},
240
+ {"role": "assistant", "content": partial},
241
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
+ yield history + [
244
+ {"role": "user", "content": message},
245
+ {"role": "assistant", "content": partial},
246
+ ]
247
 
 
 
 
248
 
249
+ # --- Gradio UI ------------------------------------------------------------
250
+ def create_demo():
251
+ with gr.Blocks(title="codeMax Qwen 3.6 27B Coder") as demo:
252
+ gr.Markdown(
253
+ "# codeMax\n"
254
+ "**Qwen 3.6 27B · Uncensored · Code-optimized · ZeroGPU**\n"
255
+ "Drop code, docs, JSON, CSVs — chat with a 27B coding LLM."
256
+ )
257
 
258
+ with gr.Row(equal_height=True):
259
+ # ---- Left column: chat ----
260
+ with gr.Column(scale=3):
261
+ chatbot = gr.Chatbot(
262
+ label="Chat",
263
+ height=580,
264
+ avatar_images=(
265
+ None,
266
+ "https://huggingface.co/front/assets/huggingface_logo-noborder.svg",
267
+ ),
268
+ )
269
  with gr.Row():
270
  msg = gr.Textbox(
271
+ placeholder="Ask about code, share files, or chat...",
272
  scale=8,
273
  show_label=False,
274
  container=False,
275
  )
276
+ send = gr.Button(">", scale=1, variant="primary", min_width=48)
277
+ clear_btn = gr.Button("Clear", size="sm")
 
278
 
279
+ # ---- Right column: file upload ----
280
+ with gr.Column(scale=1):
281
+ gr.Markdown("### Drop Files")
282
  files = gr.File(
283
  file_count="multiple",
284
+ label="Code, docs, data...",
285
  file_types=[
286
+ ".py", ".ts", ".tsx", ".js", ".jsx", ".json",
287
+ ".md", ".txt", ".csv", ".yaml", ".yml", ".toml",
288
+ ".html", ".css", ".xml", ".sql", ".sh", ".go",
289
+ ".rs", ".rb", ".java", ".c", ".cpp", ".h",
290
+ ".docx", ".pdf", ".env", ".cfg", ".ini",
 
 
 
 
 
291
  ],
292
  )
293
+ uploaded_info = gr.Markdown("_No files uploaded._")
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
+ # ---- Event handlers ----
296
+ def update_info(uploaded):
297
  if not uploaded:
298
+ return "_No files uploaded._"
299
  names = []
300
  for f in uploaded:
301
  if isinstance(f, dict):
302
  names.append(f.get("orig_name", "?"))
303
  else:
304
  names.append(getattr(f, "orig_name", Path(str(f)).name))
305
+ return "**Uploaded:**\n" + "\n".join(f"- `{n}`" for n in names)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
+ def stream_response(message, history, current_files):
308
+ for h in respond(message, history, current_files):
309
+ yield h
310
 
311
+ msg.submit(stream_response, [msg, chatbot, files], [chatbot]).then(
312
+ lambda: "", None, [msg]
313
+ )
314
+ send.click(stream_response, [msg, chatbot, files], [chatbot]).then(
315
+ lambda: "", None, [msg]
316
+ )
317
+ clear_btn.click(lambda: [], None, chatbot, queue=False)
318
+ files.change(update_info, files, uploaded_info)
319
 
320
+ return demo
321
 
 
 
 
322
 
323
  if __name__ == "__main__":
324
  demo = create_demo()
325
  demo.queue(default_concurrency_limit=1, max_size=4)
326
  demo.launch(
327
  css="""
 
328
  footer { display: none !important; }
329
+ """
330
+ )