Starchik commited on
Commit
d8c1801
·
verified ·
1 Parent(s): f815ea5

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +228 -91
  2. requirements.txt +2 -0
app.py CHANGED
@@ -1,98 +1,235 @@
1
- from huggingface_hub import InferenceClient
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import gradio as gr
 
3
 
4
- client = InferenceClient(
5
- "mistralai/Mistral-7B-Instruct-v0.1"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  )
7
 
8
- def format_prompt(message, history):
9
- # Updated system prompt
10
- prompt = "You are an expert web developer specializing in creating clean, efficient, and modern web applications.\nYour task is to write complete, self-contained HTML files that include all necessary CSS and JavaScript.\nFocus on:\n- Writing clear, maintainable code\n- Following best practices\n- Creating responsive designs\n- Adding appropriate styling and interactivity\nReturn only the complete HTML code without any additional explanation."
11
- for user_prompt, bot_response in history:
12
- prompt += f"[INST] {user_prompt} [/INST]"
13
- prompt += f" {bot_response}</s> "
14
- prompt += f"[INST] {message} [/INST]"
15
- return prompt
16
-
17
- def generate(
18
- prompt, history, system_prompt, temperature=0.9, max_new_tokens=256, top_p=0.95, repetition_penalty=1.0,
19
- ):
20
- temperature = float(temperature)
21
- if temperature < 1e-2:
22
- temperature = 1e-2
23
- top_p = float(top_p)
24
-
25
- generate_kwargs = dict(
26
- temperature=temperature,
27
- max_new_tokens=max_new_tokens,
28
- top_p=top_p,
29
- repetition_penalty=repetition_penalty,
30
- do_sample=True,
31
- seed=42,
32
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- formatted_prompt = format_prompt(f"{system_prompt}, {prompt}", history)
35
- stream = client.text_generation(formatted_prompt, **generate_kwargs, stream=True, details=True, return_full_text=True)
36
- output = ""
37
-
38
- for response in stream:
39
- output += response.token.text
40
- yield output
41
- return output
42
-
43
-
44
- additional_inputs=[
45
- gr.Textbox(
46
- label="System Prompt",
47
- max_lines=1,
48
- interactive=True,
49
- ),
50
- gr.Slider(
51
- label="Temperature",
52
- value=0.9,
53
- minimum=0.0,
54
- maximum=1.0,
55
- step=0.05,
56
- interactive=True,
57
- info="Higher values produce more diverse outputs",
58
- ),
59
- gr.Slider(
60
- label="Max new tokens",
61
- value=2048,
62
- minimum=128,
63
- maximum=8192,
64
- step=64,
65
- interactive=True,
66
- info="The maximum numbers of new tokens",
67
- ),
68
- gr.Slider(
69
- label="Top-p (nucleus sampling)",
70
- value=0.90,
71
- minimum=0.0,
72
- maximum=1,
73
- step=0.05,
74
- interactive=True,
75
- info="Higher values sample more low-probability tokens",
76
- ),
77
- gr.Slider(
78
- label="Repetition penalty",
79
- value=1.2,
80
- minimum=1.0,
81
- maximum=2.0,
82
- step=0.05,
83
- interactive=True,
84
- info="Penalize repeated tokens",
85
  )
86
- ]
87
 
88
- examples=[#["говори на русском", None, None, None, None, None, ],
89
- ]
90
-
91
- gr.ChatInterface(
92
- fn=generate,
93
- chatbot=gr.Chatbot(show_label=False, show_share_button=True, show_copy_button=True, likeable=True, layout="panel"),
94
- additional_inputs=additional_inputs,
95
- title="Chatterbox",
96
- examples=examples,
97
- concurrency_limit=20,
98
- ).launch(show_api=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Chatterbox — HF Space chat app that writes code as downloadable files
3
+ and previews HTML live, similar to Claude's "artifacts".
4
+
5
+ Why the old version broke:
6
+ - `mistralai/Mistral-7B-Instruct-v0.1` was dropped from HF's free
7
+ serverless inference tier.
8
+ - `client.text_generation(...)` with a hand-built `[INST]...[/INST]`
9
+ prompt is the old (pre chat-template) way of calling models.
10
+ `InferenceClient.chat_completion()` is the current, stable API and
11
+ works across providers HF routes to (`provider="auto"`).
12
+
13
+ Requirements (requirements.txt):
14
+ gradio>=4.44
15
+ huggingface_hub>=0.26
16
+ """
17
+
18
+ import os
19
+ import re
20
+ import tempfile
21
+ from pathlib import Path
22
+
23
  import gradio as gr
24
+ from huggingface_hub import InferenceClient
25
 
26
+ # ---------------------------------------------------------------------------
27
+ # Config
28
+ # ---------------------------------------------------------------------------
29
+
30
+ # Reads a token from the Space's secrets if you add one (needed for higher
31
+ # rate limits / gated models). Works without it too, just more rate-limited.
32
+ HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN")
33
+
34
+ # Models known to work through HF's free/routed serverless chat-completion
35
+ # as of 2026. If one of these goes away, just remove it from the list —
36
+ # HF periodically retires models from the free tier.
37
+ MODEL_CHOICES = [
38
+ "meta-llama/Llama-3.1-8B-Instruct",
39
+ "Qwen/Qwen2.5-Coder-32B-Instruct",
40
+ "mistralai/Mistral-7B-Instruct-v0.3",
41
+ "HuggingFaceH4/zephyr-7b-beta",
42
+ ]
43
+
44
+ DEFAULT_SYSTEM_PROMPT = (
45
+ "You are an expert software engineer, similar to Claude's coding "
46
+ "assistant. When the user asks you to build or write something, answer "
47
+ "with a short explanation and then one or more fenced code blocks.\n\n"
48
+ "Every code block MUST start with a language tag, and its very first "
49
+ "line must be a comment giving a filename, like this:\n\n"
50
+ "```html\n<!-- filename: index.html -->\n<!doctype html>...\n```\n"
51
+ "```python\n# filename: app.py\nprint('hi')\n```\n\n"
52
+ "Always write complete, self-contained, runnable files (inline CSS/JS "
53
+ "for HTML pages). Never truncate code for brevity."
54
  )
55
 
56
+ CODE_BLOCK_RE = re.compile(r"```([a-zA-Z0-9_+-]*)\n(.*?)```", re.DOTALL)
57
+ FILENAME_RE = re.compile(r"^\s*(?:#|//|<!--)\s*filename:\s*([^\s>]+)", re.IGNORECASE)
58
+
59
+ EXT_BY_LANG = {
60
+ "html": "html", "css": "css", "javascript": "js", "js": "js",
61
+ "python": "py", "py": "py", "json": "json", "bash": "sh", "sh": "sh",
62
+ "yaml": "yml", "yml": "yml", "typescript": "ts", "ts": "ts", "": "txt",
63
+ }
64
+
65
+ OUT_DIR = Path(tempfile.gettempdir()) / "chatterbox_outputs"
66
+ OUT_DIR.mkdir(exist_ok=True)
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # Helpers
71
+ # ---------------------------------------------------------------------------
72
+
73
+ def extract_files(text: str, tag: str):
74
+ """Pull fenced code blocks out of a model reply, write each to disk.
75
+
76
+ Returns (list_of_written_file_paths, html_source_or_None).
77
+ """
78
+ files = []
79
+ html_source = None
80
+ for i, m in enumerate(CODE_BLOCK_RE.finditer(text)):
81
+ lang = (m.group(1) or "").strip().lower()
82
+ body = m.group(2)
83
+ first_line, _, rest = body.partition("\n")
84
+ fname_match = FILENAME_RE.match(first_line)
85
+ if fname_match:
86
+ filename = fname_match.group(1)
87
+ body = rest
88
+ else:
89
+ ext = EXT_BY_LANG.get(lang, lang or "txt")
90
+ filename = f"snippet_{i + 1}.{ext}"
91
+
92
+ safe_name = re.sub(r"[^\w.\-]", "_", filename)
93
+ path = OUT_DIR / f"{tag}_{safe_name}"
94
+ path.write_text(body, encoding="utf-8")
95
+ files.append(str(path))
96
+
97
+ if lang in ("html", "") and safe_name.endswith("html") and html_source is None:
98
+ html_source = body
99
+ elif lang == "html" and html_source is None:
100
+ html_source = body
101
+
102
+ return files, html_source
103
+
104
+
105
+ def build_client(model: str) -> InferenceClient:
106
+ kwargs = {"provider": "auto"}
107
+ if HF_TOKEN:
108
+ kwargs["token"] = HF_TOKEN
109
+ return InferenceClient(model=model, **kwargs)
110
+
111
 
112
+ def make_preview_html(html_source: str | None) -> str:
113
+ if not html_source:
114
+ return "<div style='opacity:.6;padding:1rem;'>Нет HTML в последнем ответе — превью появится, когда модель вернёт html-блок.</div>"
115
+ escaped = html_source.replace('"', "&quot;")
116
+ return (
117
+ f'<iframe style="width:100%;height:520px;border:1px solid #4443;'
118
+ f'border-radius:10px;background:#fff;" srcdoc="{escaped}"></iframe>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  )
 
120
 
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # Core chat function (generator, streams tokens)
124
+ # ---------------------------------------------------------------------------
125
+
126
+ def respond(message, chat_history, model, system_prompt, temperature, max_tokens, top_p):
127
+ chat_history = chat_history or []
128
+
129
+ if not message or not message.strip():
130
+ yield chat_history, "", gr.update(), gr.update()
131
+ return
132
+
133
+ system_prompt = system_prompt.strip() or DEFAULT_SYSTEM_PROMPT
134
+
135
+ messages = [{"role": "system", "content": system_prompt}]
136
+ messages.extend(chat_history)
137
+ messages.append({"role": "user", "content": message})
138
+
139
+ chat_history = chat_history + [
140
+ {"role": "user", "content": message},
141
+ {"role": "assistant", "content": ""},
142
+ ]
143
+
144
+ try:
145
+ client = build_client(model)
146
+ stream = client.chat_completion(
147
+ messages=messages,
148
+ max_tokens=int(max_tokens),
149
+ temperature=float(max(temperature, 1e-2)),
150
+ top_p=float(top_p),
151
+ stream=True,
152
+ )
153
+
154
+ partial = ""
155
+ for chunk in stream:
156
+ delta = chunk.choices[0].delta.content or ""
157
+ if not delta:
158
+ continue
159
+ partial += delta
160
+ chat_history[-1]["content"] = partial
161
+ yield chat_history, "", gr.update(), gr.update()
162
+
163
+ tag = f"turn{len(chat_history)}"
164
+ files, html_source = extract_files(partial, tag)
165
+ yield (
166
+ chat_history,
167
+ "",
168
+ gr.update(value=files if files else None, visible=bool(files)),
169
+ gr.update(value=make_preview_html(html_source)),
170
+ )
171
+
172
+ except Exception as e:
173
+ chat_history[-1]["content"] = (
174
+ f"⚠️ Ошибка запроса к модели `{model}`: {e}\n\n"
175
+ "Если модель недоступна на бесплатном инференсе — попробуйте "
176
+ "другую из выпадающего списка сверху."
177
+ )
178
+ yield chat_history, "", gr.update(), gr.update()
179
+
180
+
181
+ def clear_all():
182
+ return [], None, make_preview_html(None)
183
+
184
+
185
+ # ---------------------------------------------------------------------------
186
+ # UI
187
+ # ---------------------------------------------------------------------------
188
+
189
+ with gr.Blocks(title="Chatterbox", theme=gr.themes.Soft()) as demo:
190
+ gr.Markdown("## 🤖 Chatterbox — чат + генерация файлов кода")
191
+
192
+ with gr.Row():
193
+ with gr.Column(scale=3):
194
+ chatbot = gr.Chatbot(
195
+ type="messages",
196
+ show_label=False,
197
+ show_copy_button=True,
198
+ height=520,
199
+ )
200
+ msg = gr.Textbox(
201
+ placeholder="Опишите, что построить, например: «сделай лендинг с формой подписки»",
202
+ show_label=False,
203
+ lines=2,
204
+ )
205
+ with gr.Row():
206
+ send_btn = gr.Button("Отправить", variant="primary")
207
+ clear_btn = gr.Button("Очистить")
208
+
209
+ with gr.Column(scale=2):
210
+ with gr.Tab("📄 Файлы"):
211
+ files_out = gr.File(label="Сгенерированные файлы", file_count="multiple")
212
+ with gr.Tab("👁 Превью (HTML)"):
213
+ preview = gr.HTML(value=make_preview_html(None))
214
+
215
+ with gr.Accordion("Настройки модели", open=False):
216
+ model_dd = gr.Dropdown(
217
+ MODEL_CHOICES, value=MODEL_CHOICES[0], label="Модель",
218
+ allow_custom_value=True,
219
+ )
220
+ system_box = gr.Textbox(
221
+ label="System prompt", value=DEFAULT_SYSTEM_PROMPT, lines=6,
222
+ )
223
+ temperature = gr.Slider(0.0, 1.5, value=0.7, step=0.05, label="Temperature")
224
+ max_tokens = gr.Slider(128, 8192, value=2048, step=64, label="Max new tokens")
225
+ top_p = gr.Slider(0.0, 1.0, value=0.9, step=0.05, label="Top-p")
226
+
227
+ inputs = [msg, chatbot, model_dd, system_box, temperature, max_tokens, top_p]
228
+ outputs = [chatbot, msg, files_out, preview]
229
+
230
+ msg.submit(respond, inputs, outputs)
231
+ send_btn.click(respond, inputs, outputs)
232
+ clear_btn.click(clear_all, None, [chatbot, files_out, preview])
233
+
234
+ if __name__ == "__main__":
235
+ demo.launch(show_api=False)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=4.44
2
+ huggingface_hub>=0.26