stevhliu HF Staff commited on
Commit
d955ed8
·
1 Parent(s): 9ef0bbc

Simplify chat composer and add optional repo context.

Browse files

Remove the topic selector and separate repo field, put the send button inline with the message input, and persist repo via query param or saved chats for system prompts.

Files changed (3) hide show
  1. README.md +1 -0
  2. app.py +1 -0
  3. index.html +26 -114
README.md CHANGED
@@ -27,6 +27,7 @@ Frontend query params (browser-side):
27
  | --- | --- | --- |
28
  | `api` | `http://localhost:8000/v1` | OpenAI-compatible base URL for the inference server. |
29
  | `model` | `Qwen/Qwen3-0.6B` | Model id sent in the chat completion request. |
 
30
 
31
  Example:
32
 
 
27
  | --- | --- | --- |
28
  | `api` | `http://localhost:8000/v1` | OpenAI-compatible base URL for the inference server. |
29
  | `model` | `Qwen/Qwen3-0.6B` | Model id sent in the chat completion request. |
30
+ | `repo` | _(empty)_ | Default Hugging Face repo id for new chats (e.g. `huggingface/transformers`). |
31
 
32
  Example:
33
 
app.py CHANGED
@@ -164,6 +164,7 @@ async def put_chat(chat_id: str, request: Request, x_user_id: str = Header(...))
164
  "id": chat_id,
165
  "title": body.get("title", "Untitled"),
166
  "topic": body.get("topic"),
 
167
  "updated_at": body.get("updated_at"),
168
  },
169
  )
 
164
  "id": chat_id,
165
  "title": body.get("title", "Untitled"),
166
  "topic": body.get("topic"),
167
+ "repo": body.get("repo"),
168
  "updated_at": body.get("updated_at"),
169
  },
170
  )
index.html CHANGED
@@ -146,56 +146,35 @@
146
  background: var(--bg);
147
  }
148
  .composer {
 
149
  border: 1px solid var(--border);
150
  border-radius: 22px;
151
  background: var(--composer-bg);
152
- padding: 14px 16px 10px;
153
  display: flex;
154
- flex-direction: column;
155
- gap: 8px;
156
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
157
  }
158
  .composer:focus-within { border-color: #c7c7cc; }
159
  #input {
160
- width: 100%;
 
161
  resize: none;
162
  font: inherit;
163
  color: inherit;
164
  background: transparent;
165
  border: 0;
166
  outline: none;
167
- padding: 4px 2px;
168
  max-height: 240px;
169
  min-height: 28px;
170
  line-height: 1.45;
171
  }
172
  #input::placeholder { color: var(--muted); }
173
- .toolbar {
174
- display: flex;
175
- align-items: center;
176
- gap: 6px;
177
- }
178
- .toolbar .spacer { flex: 1; }
179
-
180
- .pill {
181
- appearance: none;
182
- background: transparent;
183
- border: 0;
184
- color: var(--pill);
185
- font: inherit;
186
- font-weight: 500;
187
- display: inline-flex;
188
- align-items: center;
189
- gap: 6px;
190
- padding: 6px 10px;
191
- margin-left: -8px;
192
- border-radius: 999px;
193
- cursor: pointer;
194
- }
195
- .pill:hover { background: rgba(37,99,235,0.08); }
196
- .pill .chev { width: 14px; height: 14px; opacity: 0.85; }
197
 
198
  #send {
 
199
  background: var(--send-bg);
200
  color: var(--send-fg);
201
  width: 32px;
@@ -211,37 +190,6 @@
211
  #send:disabled { opacity: 0.45; cursor: not-allowed; }
212
  #send svg { width: 18px; height: 18px; }
213
 
214
- /* Topic dropdown menu */
215
- .menu-wrap { position: relative; }
216
- .menu {
217
- position: absolute;
218
- bottom: calc(100% + 6px);
219
- left: 0;
220
- min-width: 220px;
221
- background: var(--composer-bg);
222
- border: 1px solid var(--border);
223
- border-radius: 12px;
224
- box-shadow: 0 8px 24px rgba(0,0,0,0.12);
225
- padding: 6px;
226
- display: none;
227
- z-index: 10;
228
- }
229
- .menu.open { display: block; }
230
- .menu button {
231
- display: block;
232
- width: 100%;
233
- text-align: left;
234
- background: transparent;
235
- border: 0;
236
- color: inherit;
237
- font: inherit;
238
- padding: 8px 10px;
239
- border-radius: 8px;
240
- cursor: pointer;
241
- }
242
- .menu button:hover { background: rgba(127,127,127,0.12); }
243
- .menu button.selected { color: var(--pill); font-weight: 500; }
244
-
245
  /* ---- Tabs ---- */
246
  .tabs {
247
  display: flex;
@@ -548,22 +496,10 @@
548
 
549
  <form id="form">
550
  <div class="composer">
551
- <textarea id="input" rows="1" placeholder="How can I help today?" autofocus></textarea>
552
- <div class="toolbar">
553
- <div class="menu-wrap">
554
- <button type="button" class="pill" id="topic-btn" aria-haspopup="listbox" aria-expanded="false">
555
- <span id="topic-label">Continuous batching</span>
556
- <svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
557
- </button>
558
- <div class="menu" id="topic-menu" role="listbox"></div>
559
- </div>
560
-
561
- <div class="spacer"></div>
562
-
563
- <button id="send" type="submit" title="Send">
564
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
565
- </button>
566
- </div>
567
  </div>
568
  </form>
569
  </main>
@@ -574,6 +510,7 @@
574
  const _params = new URLSearchParams(location.search);
575
  const BASE_URL = _params.get("api") || "http://localhost:8000/v1";
576
  const MODEL = _params.get("model") || "Qwen/Qwen3-0.6B";
 
577
 
578
  // Persistence: anonymous per-browser user id, used as X-User-Id against /api/chats.
579
  function getUserId() {
@@ -781,9 +718,6 @@
781
  const form = document.getElementById("form");
782
  const input = document.getElementById("input");
783
  const send = document.getElementById("send");
784
- const topicBtn = document.getElementById("topic-btn");
785
- const topicLabel = document.getElementById("topic-label");
786
- const topicMenu = document.getElementById("topic-menu");
787
  const newChatBtn = document.getElementById("new-chat");
788
  const chatListEl = document.getElementById("chat-list");
789
 
@@ -796,8 +730,9 @@
796
  const chat = {
797
  id,
798
  title: "New chat",
799
- topic: TOPICS[0].id,
800
  model: MODEL,
 
801
  history: [],
802
  };
803
  chats.unshift(chat);
@@ -821,15 +756,14 @@
821
  chat.history = Array.isArray(full.history) ? full.history : [];
822
  chat.topic = full.topic || chat.topic;
823
  chat.title = full.title || chat.title;
 
824
  } else {
825
  chat.history = [];
826
  }
827
  }
828
- topicLabel.textContent = (TOPICS.find((t) => t.id === chat.topic) || TOPICS[0]).label;
829
  log.innerHTML = "";
830
  for (const m of chat.history) renderMsg(m.role, m.content);
831
  renderChatList();
832
- renderTopicMenu();
833
  input.focus();
834
  }
835
 
@@ -854,37 +788,14 @@
854
  newChat();
855
  });
856
 
857
- // ---- Topic menu ----
858
- function renderTopicMenu() {
859
- const chat = getChat();
860
- topicMenu.innerHTML = "";
861
- TOPICS.forEach((t) => {
862
- const b = document.createElement("button");
863
- b.type = "button";
864
- b.textContent = t.label;
865
- if (chat && t.id === chat.topic) b.classList.add("selected");
866
- b.addEventListener("click", () => {
867
- const c = getChat();
868
- if (c) {
869
- c.topic = t.id;
870
- topicLabel.textContent = t.label;
871
- if (Array.isArray(c.history) && c.history.length > 0) scheduleSave(c);
872
- }
873
- closeMenu();
874
- renderTopicMenu();
875
- });
876
- topicMenu.appendChild(b);
877
- });
878
  }
879
- function openMenu() { topicMenu.classList.add("open"); topicBtn.setAttribute("aria-expanded", "true"); }
880
- function closeMenu() { topicMenu.classList.remove("open"); topicBtn.setAttribute("aria-expanded", "false"); }
881
- topicBtn.addEventListener("click", (e) => {
882
- e.stopPropagation();
883
- topicMenu.classList.contains("open") ? closeMenu() : openMenu();
884
- });
885
- document.addEventListener("click", (e) => {
886
- if (!topicMenu.contains(e.target) && e.target !== topicBtn) closeMenu();
887
- });
888
 
889
  // ---- Messages ----
890
  function renderMsg(role, text) {
@@ -927,7 +838,7 @@
927
  const botEl = renderMsg("bot", "");
928
 
929
  const messages = [
930
- { role: "system", content: TOPIC_PROMPTS[chat.topic] },
931
  ...chat.history,
932
  ];
933
 
@@ -1338,7 +1249,8 @@
1338
  chats = idx.map((e) => ({
1339
  id: e.id,
1340
  title: e.title || "Untitled",
1341
- topic: e.topic || TOPICS[0].id,
 
1342
  history: null, // lazy-load on switch
1343
  }));
1344
  renderChatList();
 
146
  background: var(--bg);
147
  }
148
  .composer {
149
+ --composer-pad-x: 2px;
150
  border: 1px solid var(--border);
151
  border-radius: 22px;
152
  background: var(--composer-bg);
153
+ padding: 14px 16px;
154
  display: flex;
155
+ align-items: flex-end;
156
+ gap: 10px;
157
  box-shadow: 0 1px 2px rgba(0,0,0,0.04);
158
  }
159
  .composer:focus-within { border-color: #c7c7cc; }
160
  #input {
161
+ flex: 1;
162
+ min-width: 0;
163
  resize: none;
164
  font: inherit;
165
  color: inherit;
166
  background: transparent;
167
  border: 0;
168
  outline: none;
169
+ padding: 4px var(--composer-pad-x);
170
  max-height: 240px;
171
  min-height: 28px;
172
  line-height: 1.45;
173
  }
174
  #input::placeholder { color: var(--muted); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
  #send {
177
+ flex-shrink: 0;
178
  background: var(--send-bg);
179
  color: var(--send-fg);
180
  width: 32px;
 
190
  #send:disabled { opacity: 0.45; cursor: not-allowed; }
191
  #send svg { width: 18px; height: 18px; }
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  /* ---- Tabs ---- */
194
  .tabs {
195
  display: flex;
 
496
 
497
  <form id="form">
498
  <div class="composer">
499
+ <textarea id="input" rows="1" placeholder="Pass in a repository (https://github.com/huggingface/transformers)" autofocus></textarea>
500
+ <button id="send" type="submit" title="Send">
501
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
502
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
503
  </div>
504
  </form>
505
  </main>
 
510
  const _params = new URLSearchParams(location.search);
511
  const BASE_URL = _params.get("api") || "http://localhost:8000/v1";
512
  const MODEL = _params.get("model") || "Qwen/Qwen3-0.6B";
513
+ const DEFAULT_REPO = _params.get("repo") || "";
514
 
515
  // Persistence: anonymous per-browser user id, used as X-User-Id against /api/chats.
516
  function getUserId() {
 
718
  const form = document.getElementById("form");
719
  const input = document.getElementById("input");
720
  const send = document.getElementById("send");
 
 
 
721
  const newChatBtn = document.getElementById("new-chat");
722
  const chatListEl = document.getElementById("chat-list");
723
 
 
730
  const chat = {
731
  id,
732
  title: "New chat",
733
+ topic: "general",
734
  model: MODEL,
735
+ repo: DEFAULT_REPO,
736
  history: [],
737
  };
738
  chats.unshift(chat);
 
756
  chat.history = Array.isArray(full.history) ? full.history : [];
757
  chat.topic = full.topic || chat.topic;
758
  chat.title = full.title || chat.title;
759
+ chat.repo = full.repo ?? chat.repo ?? "";
760
  } else {
761
  chat.history = [];
762
  }
763
  }
 
764
  log.innerHTML = "";
765
  for (const m of chat.history) renderMsg(m.role, m.content);
766
  renderChatList();
 
767
  input.focus();
768
  }
769
 
 
788
  newChat();
789
  });
790
 
791
+ function systemContent(chat) {
792
+ let content = TOPIC_PROMPTS[chat.topic] || TOPIC_PROMPTS.general;
793
+ const repo = (chat.repo || "").trim();
794
+ if (repo) {
795
+ content += `\n\nThe user is working in the Hugging Face repository \`${repo}\`. Ground answers in that repo when relevant (paths, APIs, conventions).`;
796
+ }
797
+ return content;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
798
  }
 
 
 
 
 
 
 
 
 
799
 
800
  // ---- Messages ----
801
  function renderMsg(role, text) {
 
838
  const botEl = renderMsg("bot", "");
839
 
840
  const messages = [
841
+ { role: "system", content: systemContent(chat) },
842
  ...chat.history,
843
  ];
844
 
 
1249
  chats = idx.map((e) => ({
1250
  id: e.id,
1251
  title: e.title || "Untitled",
1252
+ topic: e.topic || "general",
1253
+ repo: e.repo || DEFAULT_REPO,
1254
  history: null, // lazy-load on switch
1255
  }));
1256
  renderChatList();