ashwath-vaithina-ibm commited on
Commit
a4e21c7
·
verified ·
1 Parent(s): 6c43dbf

updated CSS

Browse files
Files changed (1) hide show
  1. static/demo/index.html +796 -776
static/demo/index.html CHANGED
@@ -1,804 +1,824 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
 
3
  <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Responsible Prompting – Multi‐Turn Chat + Graph</title>
7
-
8
- <!-- Carbon CSS (for tabs and tags) -->
9
- <link
10
- rel="stylesheet"
11
- href="https://unpkg.com/carbon-components/css/carbon-components.min.css"
12
- />
13
-
14
- <style>
15
- /* ---------- Overall Layout ---------- */
16
- html,
17
- body {
18
- margin: 0;
19
- padding: 0;
20
- height: 100vh;
21
- font-family: "IBM Plex Sans", sans-serif;
22
- }
23
- body {
24
- display: flex;
25
- flex-direction: column;
26
- }
27
-
28
- /* Container for the header (title + tabs) */
29
- .header-container {
30
- background: white;
31
- padding: 1rem;
32
- border-bottom: 1px solid #ddd;
33
- }
34
-
35
- .bx--tabs__nav {
36
- display: flex;
37
- list-style: none;
38
- padding: 0;
39
- margin: 0;
40
- border-bottom: 2px solid #c6c6c6;
41
- }
42
- .bx--tabs__nav-item {
43
- margin-right: 1rem;
44
- padding: 0.5rem 0.75rem;
45
- cursor: pointer;
46
- font-size: 1rem;
47
- color: #393939;
48
- border-bottom: 2px solid transparent;
49
- }
50
- .bx--tabs__nav-item--selected {
51
- color: #0f62fe;
52
- border-bottom: 2px solid #0f62fe;
53
- }
54
-
55
- /* The “content” area below header: either chat or graph */
56
- .tab-content {
57
- flex: 1;
58
- display: flex;
59
- flex-direction: column;
60
- background: #f4f4f4;
61
- overflow: hidden;
62
- }
63
-
64
- /* When Chat is selected: show its container; Graph is hidden */
65
- #chat-content {
66
- display: flex;
67
- flex-direction: column;
68
- flex: 1;
69
- overflow: hidden;
70
- }
71
- #graph-content {
72
- display: none;
73
- flex: 1;
74
- overflow: auto;
75
- background: #fff;
76
- padding: 1rem;
77
- }
78
-
79
- /* ---------- Intro Paragraph ---------- */
80
- .intro {
81
- background: white;
82
- padding: 1rem;
83
- border-bottom: 1px solid #ddd;
84
- font-size: 0.95rem;
85
- line-height: 1.5;
86
- color: #393939;
87
- }
88
- .intro p {
89
- margin: 0.5rem 0;
90
- }
91
-
92
- /* ---------- Chat Area ---------- */
93
- .chat-container {
94
- flex: 1;
95
- overflow-y: auto;
96
- padding: 0.5rem 1rem; /* reduced top padding to avoid overlap */
97
- display: flex;
98
- flex-direction: column;
99
- }
100
-
101
- .message {
102
- max-width: 70%;
103
- margin-bottom: 0.75rem;
104
- padding: 0.5rem 0.75rem;
105
- border-radius: 0.5rem;
106
- line-height: 1.4;
107
- word-wrap: break-word;
108
- white-space: pre-wrap;
109
- }
110
- .message.user {
111
- background-color: #0f62fe;
112
- color: white;
113
- align-self: flex-end;
114
- }
115
- .message.assistant {
116
- background-color: #e0e0e0;
117
- color: #161616;
118
- align-self: flex-start;
119
- }
120
-
121
- .recs-container {
122
- display: flex;
123
- flex-wrap: wrap;
124
- margin-bottom: 0.75rem;
125
- margin-right: 0.25rem;
126
- }
127
- .recs-item {
128
- font-size: 0.85rem;
129
- padding: 0.25rem 0.5rem;
130
- border-radius: 0.25rem;
131
- margin-right: 0.25rem;
132
- margin-bottom: 0.25rem;
133
- color: white;
134
- }
135
- .recs-item.add {
136
- background-color: #24a148; /* green */
137
- }
138
- .recs-item.remove {
139
- background-color: #da1e28; /* red */
140
- }
141
-
142
- /* ---------- Input Area ---------- */
143
- .input-area {
144
- border-top: 1px solid #ddd;
145
- padding: 0.5rem 1rem;
146
- display: flex;
147
- align-items: flex-end;
148
- background: white;
149
- }
150
-
151
- #userInput {
152
- flex: 1;
153
- resize: none;
154
- font-size: 1rem;
155
- padding: 0.5rem;
156
- border: 1px solid #c6c6c6;
157
- border-radius: 0.375rem;
158
- font-family: inherit;
159
- line-height: 1.4;
160
- }
161
-
162
- #recommendation {
163
- margin-top: 0.25rem;
164
- font-size: 0.9rem;
165
- color: #393939;
166
- display: flex;
167
- flex-wrap: wrap;
168
- }
169
- #recommendation .bx--tag {
170
- margin-right: 0.25rem;
171
- margin-bottom: 0.25rem;
172
- cursor: pointer;
173
- }
174
-
175
- #sendBtn {
176
- margin-left: 0.5rem;
177
- margin-bottom: 0.25rem;
178
- }
179
-
180
- /* ---------- Graph Area ---------- */
181
- #graph {
182
- background: #efefef;
183
- position: relative;
184
- }
185
- .tooltip {
186
- position: absolute;
187
- text-align: left;
188
- padding: 0.5em;
189
- width: 20em;
190
- min-height: 5em;
191
- background: #fff;
192
- color: #000;
193
- border: 1px solid #000;
194
- border-radius: 5px;
195
- pointer-events: none;
196
- font-size: inherit;
197
- font-family: inherit;
198
- }
199
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  </head>
 
201
  <body>
202
- <!-- ===== Header: Title + Carbon Tabs ===== -->
203
- <div class="header-container">
204
- <h1 class="bx--type-expressive-heading-03">Responsible Prompting</h1>
205
- <div class="intro">
206
- <p>
207
- Please provide a prompt that would be sent to an LLM. Recommendations are performed in prompting-time, before content generation. The recommendations consider a curated dataset of values and prompt-sentences. They are based on the similarity between your input and that dataset.
208
- </p>
209
- </div>
210
- <ul class="bx--tabs__nav">
211
- <li id="tab-chat" class="bx--tabs__nav-item bx--tabs__nav-item--selected">
212
- Chat
213
- </li>
214
- <li id="tab-graph" class="bx--tabs__nav-item">
215
- Graph
216
- </li>
217
- </ul>
218
- </div>
219
-
220
- <!-- ===== Tab Content: Chat or Graph ===== -->
221
- <div class="tab-content">
222
- <!-- Chat Content -->
223
- <div id="chat-content">
224
- <!-- Intro paragraph about responsible prompting -->
225
-
226
-
227
- <!-- Chat container (messages appear here) -->
228
- <div id="chat" class="chat-container"></div>
229
-
230
- <!-- Input area -->
231
- <div class="input-area">
232
- <div style="flex: 1; display: flex; flex-direction: column;">
233
- <textarea
234
- id="userInput"
235
- rows="4"
236
- placeholder="Enter your prompt"
237
- >Act as a professional designer with 20 years of experience creating and testing UX interfaces and landing sites for a variety of IT applications. We are in need of more people and an increased budget to be able to keep up with clients' needs. What kind of evidence should I gather to support my demands to gain more resources?</textarea>
238
- <div id="recommendation"></div>
239
  </div>
240
- <button id="sendBtn" class="bx--btn bx--btn--primary" disabled>
241
- Send
242
- </button>
243
- </div>
 
 
 
 
244
  </div>
245
 
246
- <!-- Graph Content -->
247
- <div id="graph-content">
248
- <div id="graph"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  </div>
250
- </div>
251
-
252
- <!-- Dependencies -->
253
- <script type="text/javascript" src="static/demo/js/d3.v7.min.js"></script>
254
- <script type="text/javascript" src="static/demo/js/jquery-3.7.1.min.js"></script>
255
-
256
- <script>
257
- // =============================
258
- // 1. State & Dimensions for D3
259
- // =============================
260
- let lastRecommendations = null; // stores latest /recommend response
261
-
262
- const width = 600;
263
- const height = 300;
264
- const marginTop = 30;
265
- const marginRight = 30;
266
- const marginBottom = 30;
267
- const marginLeft = 30;
268
- const nodeRadius = 3;
269
-
270
- // Create SVG once
271
- const svg = d3
272
- .select("#graph")
273
- .append("svg")
274
- .attr("viewBox", `0 0 ${width} ${height}`)
275
- .attr("style", "max-width: 100%; height: auto; font: 8px sans-serif;");
276
-
277
- const tooltip = d3
278
- .select("body")
279
- .append("div")
280
- .attr("class", "tooltip")
281
- .style("opacity", 0);
282
-
283
- function renderGraph(recommendations) {
284
- if (!recommendations) {
285
- svg.selectAll("*").remove();
286
- svg
287
- .append("text")
288
- .attr("x", width / 2)
289
- .attr("y", height / 2)
290
- .attr("text-anchor", "middle")
291
- .attr("fill", "#666")
292
- .text("No recommendation data available");
293
- return;
294
- }
295
-
296
- const rec = recommendations;
297
- const graphData = { nodes: [], edges: [] };
298
- let i = 0,
299
- j = 0;
300
-
301
- // Input sentences
302
- if (rec.input && rec.input.length > 0) {
303
- graphData.nodes.push({
304
- id: 0,
305
- x: Number(rec.input[0].x),
306
- y: Number(rec.input[0].y),
307
- text: rec.input[0].sentence,
308
- label: "S1",
309
- type: "input",
310
- });
311
- for (i = 1; i < rec.input.length; i++) {
312
- graphData.nodes.push({
313
- id: i,
314
- x: Number(rec.input[i].x),
315
- y: Number(rec.input[i].y),
316
- text: rec.input[i].sentence,
317
- label: `S${i + 1}`,
318
- type: "input",
319
- });
320
- graphData.edges.push({ source: i - 1, target: i, type: "input" });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  }
322
- }
323
-
324
- // “Add” recommendations
325
- if (rec.add && rec.add.length > 0) {
326
- for (j = 0; j < rec.add.length; j++) {
327
- graphData.nodes.push({
328
- id: i + j,
329
- x: Number(rec.add[j].x),
330
- y: Number(rec.add[j].y),
331
- text: rec.add[j].prompt,
332
- label: rec.add[j].value,
333
- type: "add",
334
- });
335
- graphData.edges.push({
336
- source: i - 1,
337
- target: i + j,
338
- type: "add",
339
- });
 
 
 
 
 
 
 
 
 
 
 
 
340
  }
341
- }
342
-
343
- // “Remove” recommendation (first only)
344
- if (rec.remove && rec.remove.length > 0) {
345
- graphData.nodes.push({
346
- id: i + j,
347
- x: Number(rec.remove[0].x),
348
- y: Number(rec.remove[0].y),
349
- text: rec.remove[0].closest_harmful_sentence,
350
- label: rec.remove[0].value,
351
- type: "remove",
352
- });
353
- }
354
-
355
- graphData.edges = graphData.edges.map((e) => ({
356
- source: graphData.nodes.find((n) => n.id === e.source),
357
- target: graphData.nodes.find((n) => n.id === e.target),
358
- type: e.type,
359
- }));
360
-
361
- const { nodes, edges } = graphData;
362
- if (nodes.length === 0) {
363
- svg.selectAll("*").remove();
364
- svg
365
- .append("text")
366
- .attr("x", width / 2)
367
- .attr("y", height / 2)
368
- .attr("text-anchor", "middle")
369
- .attr("fill", "#666")
370
- .text("No nodes to display");
371
- return;
372
- }
373
-
374
- const xDomain = d3.extent(nodes, (d) => d.x);
375
- const yDomain = d3.extent(nodes, (d) => d.y);
376
- const xPadding = 2;
377
- const yPadding = 2;
378
-
379
- const xScale = d3
380
- .scaleLinear()
381
- .domain([xDomain[0] - xPadding, xDomain[1] + xPadding])
382
- .nice()
383
- .range([marginLeft, width - marginRight]);
384
-
385
- const yScale = d3
386
- .scaleLinear()
387
- .domain([yDomain[0] - yPadding, yDomain[1] + yPadding])
388
- .nice()
389
- .range([height - marginBottom, marginTop]);
390
-
391
- svg.selectAll("*").remove();
392
-
393
- // X axis
394
- svg
395
- .append("g")
396
- .attr("transform", `translate(0, ${height - marginBottom})`)
397
- .call(d3.axisBottom(xScale).ticks(width / 80))
398
- .call((g) => g.select(".domain").remove())
399
- .call((g) =>
400
- g
401
- .append("text")
402
- .attr("x", width)
403
- .attr("y", marginBottom - 4)
404
- .attr("fill", "currentColor")
405
- .attr("text-anchor", "end")
406
- .text("Semantic dimension 1")
407
- );
408
-
409
- // Y axis
410
- svg
411
- .append("g")
412
- .attr("transform", `translate(${marginLeft}, 0)`)
413
- .call(d3.axisLeft(yScale))
414
- .call((g) => g.select(".domain").remove())
415
- .call((g) =>
416
- g
417
- .append("text")
418
- .attr("x", -marginLeft)
419
- .attr("y", 10)
420
- .attr("fill", "currentColor")
421
- .attr("text-anchor", "start")
422
- .text("Semantic dimension 2")
423
- );
424
-
425
- // Grid
426
- svg
427
- .append("g")
428
- .attr("stroke", "#cccccc")
429
- .attr("stroke-opacity", 0.5)
430
- .call((g) =>
431
- g
432
- .append("g")
433
- .selectAll("line")
434
- .data(xScale.ticks())
435
- .join("line")
436
- .attr("x1", (d) => 0.5 + xScale(d))
437
- .attr("x2", (d) => 0.5 + xScale(d))
438
- .attr("y1", marginTop)
439
- .attr("y2", height - marginBottom)
440
- )
441
- .call((g) =>
442
- g
443
- .append("g")
444
- .selectAll("line")
445
- .data(yScale.ticks())
446
- .join("line")
447
- .attr("y1", (d) => 0.5 + yScale(d))
448
- .attr("y2", (d) => 0.5 + yScale(d))
449
- .attr("x1", marginLeft)
450
- .attr("x2", width - marginRight)
451
- );
452
-
453
- // Edges
454
- svg
455
- .append("g")
456
- .selectAll("line")
457
- .data(edges)
458
- .join("line")
459
- .attr("stroke", "#666")
460
- .attr("stroke-opacity", 0.5)
461
- .attr(
462
- "x1",
463
- (d) =>
464
- xScale(d.source.x) +
465
- (d.source.x < d.target.x ? 1.3 * nodeRadius : -1.3 * nodeRadius)
466
- )
467
- .attr("y1", (d) => yScale(d.source.y))
468
- .attr(
469
- "x2",
470
- (d) =>
471
- xScale(d.target.x) +
472
- (d.source.x > d.target.x ? 1.3 * nodeRadius : -1.3 * nodeRadius)
473
- )
474
- .attr("y2", (d) => yScale(d.target.y))
475
- .style("stroke-dasharray", (d) =>
476
- d.target.type === "add" ? "3,3" : ""
477
- );
478
-
479
- // Nodes
480
- svg
481
- .append("g")
482
- .attr("stroke-width", 2.5)
483
- .attr("stroke-opacity", 0.5)
484
- .attr("fill", "none")
485
- .selectAll("circle")
486
- .data(nodes)
487
- .join("circle")
488
- .attr("stroke", (d) =>
489
- d.type === "add" ? "green" : d.type === "input" ? "#666" : "red"
490
- )
491
- .attr("cx", (d) => xScale(d.x))
492
- .attr("cy", (d) => yScale(d.y))
493
- .attr("r", nodeRadius);
494
-
495
- // Labels
496
- svg
497
- .append("g")
498
- .attr("font-family", "sans-serif")
499
- .attr("text-opacity", 0.5)
500
- .attr("font-size", 8)
501
- .selectAll("text")
502
- .data(nodes)
503
- .join("text")
504
- .attr("dy", "0.35em")
505
- .attr("x", (d) => xScale(d.x) + 5)
506
- .attr("y", (d) => yScale(d.y))
507
- .text((d) => d.label)
508
- .on("mousemove", function (event, d) {
509
- d3.select(this)
510
- .transition()
511
- .duration(50)
512
- .attr("text-opacity", 1.0)
513
- .attr("stroke", "white")
514
- .attr("stroke-width", 3)
515
- .style("paint-order", "stroke fill")
516
- .attr(
517
- "fill",
518
- d.type === "add"
519
- ? "green"
520
- : d.type === "input"
521
- ? "black"
522
- : "red"
523
- );
524
- tooltip.transition().duration(50).style("opacity", 1);
525
- tooltip
526
- .html(`<strong>${d.label}:</strong><br/>${d.text}`)
527
- .style("left", event.pageX + 10 + "px")
528
- .style("top", event.pageY + 10 + "px");
529
- })
530
- .on("mouseout", function () {
531
- d3.select(this)
532
- .transition()
533
- .duration(50)
534
- .attr("text-opacity", 0.5)
535
- .attr("stroke-width", 0)
536
- .style("paint-order", "fill")
537
- .attr("fill", "black");
538
- tooltip.transition().duration(50).style("opacity", 0);
539
- });
540
- }
541
-
542
- // Initially show “no data” message
543
- renderGraph(null);
544
-
545
- // =============================
546
- // 2. Multi‐Turn Chat + Recommendation Logic
547
- // =============================
548
- const conversation = []; // stores { role, content, recs? }
549
- let currentRecs = [];
550
- let debounceId = null;
551
-
552
- function appendUserTurn(turn) {
553
- const bubble = $("<div>")
554
- .addClass("message user")
555
- .text(turn.content);
556
- $("#chat").append(bubble);
557
-
558
- if (turn.recs && turn.recs.length > 0) {
559
- const container = $("<div>").addClass("recs-container");
560
- turn.recs.forEach((r) => {
561
- const item = $("<div>")
562
- .addClass("recs-item")
563
- .addClass(r.type === "add" ? "add" : "remove")
564
- .text(r.value);
565
- container.append(item);
566
- });
567
- container.css("align-self", "flex-end");
568
- $("#chat").append(container);
569
- }
570
- $("#chat").scrollTop($("#chat")[0].scrollHeight);
571
- }
572
-
573
- function appendAssistantTurn(text) {
574
- const bubble = $("<div>")
575
- .addClass("message assistant")
576
- .text(text);
577
- $("#chat").append(bubble);
578
- $("#chat").scrollTop($("#chat")[0].scrollHeight);
579
- }
580
-
581
- $("#userInput").on("input", function () {
582
- const txt = $(this).val();
583
- const trimmed = txt.trim();
584
-
585
- if (trimmed.length > 0) {
586
- $("#sendBtn").removeAttr("disabled");
587
- } else {
588
- $("#sendBtn").attr("disabled", true);
589
- $("#recommendation").empty();
590
- }
591
-
592
- clearTimeout(debounceId);
593
-
594
- if (txt.length > 0 && /[.?!]$/.test(txt)) {
595
- debounceId = setTimeout(() => {
596
- $("#recommendation").html(
597
- 'Checking recommendations… <div class="bx--tag bx--tag--gray bx--tag--deletable">…</div>'
598
- );
599
- $.getJSON("/recommend?prompt=" + encodeURIComponent(txt), (data) => {
600
- $("#recommendation").empty();
601
- lastRecommendations = data;
602
 
603
- if (data.remove && data.remove.length > 0) {
604
- const rec = data.remove[0];
605
- const sentence = rec.sentence.replaceAll("'", "\\'");
606
- const valueEscaped = rec.value.replaceAll("'", "\\'");
607
- const $tag = $(`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
  <div class="bx--tag bx--tag--red bx--tag--deletable">
609
  ✕ ${valueEscaped}
610
  </div>
611
  `);
612
 
613
- $tag.hover(
614
- () => {
615
- const cur = $("#userInput").val();
616
- $("#userInput").data("prevText", cur);
617
- $("#userInput").val(cur.replace(rec.sentence, ""));
618
- },
619
- () => {
620
- const prev = $("#userInput").data("prevText") || txt;
621
- $("#userInput").val(prev);
622
- }
623
- );
624
-
625
- $tag.click(() => {
626
- const updated = $("#userInput")
627
- .val()
628
- .replace(rec.sentence, "")
629
- .replace(/ {2,}/g, " ")
630
- .trim();
631
- $("#userInput").val(updated);
632
- currentRecs.push({ type: "remove", value: rec.value });
633
- $("#recommendation").empty();
634
- $("#userInput").trigger("input");
635
- });
636
-
637
- $("#recommendation").append($tag);
638
- }
639
-
640
- if (data.add && data.add.length > 0) {
641
- data.add.forEach((rec) => {
642
- if (!$("#userInput").val().includes(rec.prompt)) {
643
- const promptEscaped = rec.prompt.replaceAll("'", "\\'");
644
- const valueEscaped = rec.value.replaceAll("'", "\\'");
645
- const $tag = $(`
646
  <div class="bx--tag bx--tag--green">
647
  + ${valueEscaped}
648
  </div>
649
  `);
650
 
651
- $tag.hover(
652
- () => {
653
- const cur = $("#userInput").val();
654
- $("#userInput").data("prevAdd", cur);
655
- $("#userInput").val((cur + " " + rec.prompt).trim());
656
- },
657
- () => {
658
- const prev = $("#userInput").data("prevAdd") || txt;
659
- $("#userInput").val(prev);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
660
  }
661
- );
662
-
663
- $tag.click(() => {
664
- const base =
665
- $("#userInput").data("prevAdd") ||
666
- $("#userInput").val().trim();
667
- $("#userInput").val((base + " " + rec.prompt).trim());
668
- currentRecs.push({ type: "add", value: rec.value });
669
- $("#recommendation").empty();
670
- $("#userInput").trigger("input");
671
- });
672
-
673
- $("#recommendation").append($tag);
 
 
 
 
674
  }
675
- });
676
  }
 
677
 
678
- if (
679
- (!data.add || data.add.length === 0) &&
680
- (!data.remove || data.remove.length === 0)
681
- ) {
682
- $("#recommendation").text("No recommendations found.");
683
- }
684
- });
685
- }, 500);
686
- } else {
687
- $("#recommendation").empty();
688
- lastRecommendations = null;
689
- }
690
- });
691
-
692
- $("#sendBtn").on("click", function () {
693
- const rawText = $("#userInput").val();
694
- const userText = rawText.trim();
695
- if (!userText) return;
696
-
697
- const thisTurn = {
698
- role: "user",
699
- content: userText,
700
- recs: currentRecs.slice(),
701
- };
702
- conversation.push(thisTurn);
703
- appendUserTurn(thisTurn);
704
-
705
- $("#userInput").val("");
706
- $("#recommendation").empty();
707
- $("#sendBtn").attr("disabled", true);
708
- currentRecs = [];
709
-
710
- const typingBubble = $("<div>")
711
- .addClass("message assistant")
712
- .attr("id", "typing")
713
- .text("Assistant is typing…");
714
- $("#chat").append(typingBubble);
715
- $("#chat").scrollTop($("#chat")[0].scrollHeight);
716
-
717
- let fullPrompt = "";
718
- conversation.forEach((turn) => {
719
- if (turn.role === "user") {
720
- fullPrompt += `User: ${turn.content}\n`;
721
- } else {
722
- fullPrompt += `Assistant: ${turn.content}\n`;
723
- }
724
- });
725
- fullPrompt += "Assistant: ";
726
-
727
- $.ajax({
728
- url: "/demo_inference?prompt=" + encodeURIComponent(fullPrompt),
729
- dataType: "json",
730
- success: function (data) {
731
- $("#typing").remove();
732
- const generated = data.content;
733
- const modelId = data.model_id;
734
- const temp = data.temperature;
735
- const maxTokens = data.max_new_tokens;
736
-
737
- let toType = `Model: ${modelId} · Temperature: ${temp} · Max tokens: ${maxTokens}\n\n`;
738
- toType += generated;
739
-
740
- const assistantBubble = $("<div>")
741
- .addClass("message assistant")
742
- .attr("id", "assistantBubble")
743
- .text("");
744
- $("#chat").append(assistantBubble);
745
- $("#chat").scrollTop($("#chat")[0].scrollHeight);
746
-
747
- const chars = toType.split("");
748
- let idx = 0;
749
- function typeNext() {
750
- if (idx < chars.length) {
751
- const cur = $("#assistantBubble").text();
752
- $("#assistantBubble").text(cur + chars[idx]);
753
- idx++;
754
- $("#chat").scrollTop($("#chat")[0].scrollHeight);
755
- setTimeout(typeNext, 10);
756
- } else {
757
- $("#assistantBubble").removeAttr("id");
758
- conversation.push({
759
- role: "assistant",
760
- content: generated,
761
- });
762
- }
763
- }
764
- typeNext();
765
- },
766
- error: function (xhr) {
767
- $("#typing").remove();
768
- const err =
769
- xhr.responseJSON?.error?.message ||
770
- "Unknown error from inference.";
771
- appendAssistantTurn(`Error: ${err}`);
772
- },
773
- });
774
- });
775
-
776
- $("#userInput").on("keydown", function (e) {
777
- if (e.key === "Enter" && !e.shiftKey) {
778
- e.preventDefault();
779
- if (!$("#sendBtn").attr("disabled")) {
780
- $("#sendBtn").click();
781
- }
782
- }
783
- });
784
-
785
- // =============================
786
- // 3. Tab Switching (Chat <-> Graph)
787
- // =============================
788
- $("#tab-chat").on("click", function () {
789
- $(this).addClass("bx--tabs__nav-item--selected");
790
- $("#tab-graph").removeClass("bx--tabs__nav-item--selected");
791
- $("#chat-content").show();
792
- $("#graph-content").hide();
793
- });
794
-
795
- $("#tab-graph").on("click", function () {
796
- $(this).addClass("bx--tabs__nav-item--selected");
797
- $("#tab-chat").removeClass("bx--tabs__nav-item--selected");
798
- $("#chat-content").hide();
799
- $("#graph-content").show();
800
- renderGraph(lastRecommendations);
801
- });
802
- </script>
803
  </body>
 
804
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+
4
  <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>Responsible Prompting – Multi‐Turn Chat + Graph</title>
8
+
9
+ <!-- Carbon CSS (for tabs and tags) -->
10
+ <link rel="stylesheet" href="https://unpkg.com/carbon-components/css/carbon-components.min.css" />
11
+
12
+ <style>
13
+ /* ---------- Overall Layout ---------- */
14
+ html,
15
+ body {
16
+ margin: 0;
17
+ padding: 0;
18
+ height: 100vh;
19
+ font-family: "IBM Plex Sans", sans-serif;
20
+ }
21
+
22
+ body {
23
+ display: flex;
24
+ flex-direction: column;
25
+ }
26
+
27
+ /* Container for the header (title + tabs) */
28
+ .header-container {
29
+ background: white;
30
+ padding: 1rem;
31
+ border-bottom: 1px solid #ddd;
32
+ }
33
+
34
+ .bx--tabs__nav {
35
+ position: relative;
36
+ display: flex;
37
+ list-style: none;
38
+ padding: 0;
39
+ margin: 0;
40
+ border-bottom: 2px solid #c6c6c6;
41
+ }
42
+
43
+ .bx--tabs__nav-item {
44
+ text-align: center;
45
+ justify-content: center;
46
+ display: block;
47
+ margin-right: 1rem;
48
+ padding: 0.5rem 0.75rem;
49
+ cursor: pointer;
50
+ font-size: 1rem;
51
+ color: #393939;
52
+ border-bottom: 2px solid transparent;
53
+ }
54
+
55
+ .bx--tabs__nav-item--selected {
56
+ font-weight: bold;
57
+ border-bottom: 2px solid #0f62fe !important;
58
+ }
59
+
60
+ /* The “content” area below header: either chat or graph */
61
+ .tab-content {
62
+ flex: 1;
63
+ display: flex;
64
+ flex-direction: column;
65
+ overflow: hidden;
66
+ padding: 5px;
67
+ }
68
+
69
+ /* When Chat is selected: show its container; Graph is hidden */
70
+ #chat-content {
71
+ display: flex;
72
+ flex-direction: column;
73
+ flex: 1;
74
+ overflow: hidden;
75
+ }
76
+
77
+ #graph-content {
78
+ display: none;
79
+ flex: 1;
80
+ overflow: auto;
81
+ padding: 1rem;
82
+ }
83
+
84
+ /* ---------- Intro Paragraph ---------- */
85
+ .intro {
86
+ background: white;
87
+ padding: 1rem;
88
+ border-bottom: 1px solid #ddd;
89
+ font-size: 0.95rem;
90
+ line-height: 1.5;
91
+ color: #393939;
92
+ }
93
+
94
+ .intro p {
95
+ margin: 0.5rem 0;
96
+ }
97
+
98
+ /* ---------- Chat Area ---------- */
99
+ .chat-container {
100
+ flex: 1;
101
+ overflow-y: auto;
102
+ padding: 0.5rem 1rem;
103
+ /* reduced top padding to avoid overlap */
104
+ display: flex;
105
+ flex-direction: column;
106
+ }
107
+
108
+ .message {
109
+ max-width: 70%;
110
+ margin-bottom: 0.75rem;
111
+ padding: 0.5rem 0.75rem;
112
+ border-radius: 0.5rem;
113
+ line-height: 1.4;
114
+ word-wrap: break-word;
115
+ white-space: pre-wrap;
116
+ }
117
+
118
+ .message.user {
119
+ background-color: #0f62fe;
120
+ color: white;
121
+ align-self: flex-end;
122
+ }
123
+
124
+ .message.assistant {
125
+ background-color: #e0e0e0;
126
+ color: #161616;
127
+ align-self: flex-start;
128
+ }
129
+
130
+ .recs-container {
131
+ display: flex;
132
+ flex-wrap: wrap;
133
+ margin-bottom: 0.75rem;
134
+ margin-right: 0.25rem;
135
+ }
136
+
137
+ .recs-item {
138
+ font-size: 0.85rem;
139
+ padding: 0.25rem 0.5rem;
140
+ border-radius: 0.25rem;
141
+ margin-right: 0.25rem;
142
+ margin-bottom: 0.25rem;
143
+ color: white;
144
+ }
145
+
146
+ .recs-item.add {
147
+ background-color: #24a148;
148
+ /* green */
149
+ }
150
+
151
+ .recs-item.remove {
152
+ background-color: #da1e28;
153
+ /* red */
154
+ }
155
+
156
+ /* ---------- Input Area ---------- */
157
+ .input-area {
158
+ border-top: 1px solid #ddd;
159
+ padding: 0.5rem 1rem;
160
+ display: flex;
161
+ align-items: flex-end;
162
+ background: white;
163
+ align-items: center;
164
+ }
165
+
166
+ #userInput {
167
+ flex: 1;
168
+ resize: none;
169
+ font-size: 1rem;
170
+ padding: 0.5rem;
171
+ border: 1px solid #c6c6c6;
172
+ border-radius: 0.375rem;
173
+ font-family: inherit;
174
+ line-height: 1.4;
175
+ }
176
+
177
+ #recommendation {
178
+ margin-top: 0.25rem;
179
+ font-size: 0.9rem;
180
+ color: #393939;
181
+ display: flex;
182
+ flex-wrap: wrap;
183
+ height: 30px;
184
+ }
185
+
186
+ #recommendation .bx--tag {
187
+ margin-right: 0.25rem;
188
+ margin-bottom: 0.25rem;
189
+ cursor: pointer;
190
+ }
191
+
192
+ #sendBtn {
193
+ margin-left: 0.5rem;
194
+ margin-bottom: 0.25rem;
195
+ background-color: red;
196
+ }
197
+
198
+ /* ---------- Graph Area ---------- */
199
+ #graph {
200
+ background: #efefef;
201
+ position: relative;
202
+ }
203
+
204
+ .tooltip {
205
+ position: absolute;
206
+ text-align: left;
207
+ padding: 0.5em;
208
+ width: 20em;
209
+ min-height: 5em;
210
+ background: #fff;
211
+ color: #000;
212
+ border: 1px solid #000;
213
+ border-radius: 5px;
214
+ pointer-events: none;
215
+ font-size: inherit;
216
+ font-family: inherit;
217
+ }
218
+ </style>
219
  </head>
220
+
221
  <body>
222
+ <!-- ===== Header: Title + Carbon Tabs ===== -->
223
+ <div class="header-container">
224
+ <h1 class="bx--type-expressive-heading-03">Responsible Prompting</h1>
225
+ <div class="intro">
226
+ <p>
227
+ Please provide a prompt that would be sent to an LLM. Recommendations are performed in prompting-time,
228
+ before content generation. The recommendations consider a curated dataset of values and
229
+ prompt-sentences. They are based on the similarity between your input and that dataset.
230
+ </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  </div>
232
+ <ol class="bx--tabs__nav">
233
+ <li id="tab-chat" class="bx--tabs__nav-item bx--tabs__nav-item--selected">
234
+ Chat
235
+ </li>
236
+ <li id="tab-graph" class="bx--tabs__nav-item">
237
+ Graph
238
+ </li>
239
+ </ol>
240
  </div>
241
 
242
+ <!-- ===== Tab Content: Chat or Graph ===== -->
243
+ <div class="tab-content">
244
+ <!-- Chat Content -->
245
+ <div id="chat-content">
246
+ <!-- Intro paragraph about responsible prompting -->
247
+
248
+
249
+ <!-- Chat container (messages appear here) -->
250
+ <div id="chat" class="chat-container"></div>
251
+
252
+ <!-- Input area -->
253
+ <div class="input-area">
254
+ <div style="flex: 1; display: flex; flex-direction: column;">
255
+ <textarea id="userInput" rows="4"
256
+ placeholder="Enter your prompt">Act as a professional designer with 20 years of experience creating and testing UX interfaces and landing sites for a variety of IT applications. We are in need of more people and an increased budget to be able to keep up with clients' needs. What kind of evidence should I gather to support my demands to gain more resources?</textarea>
257
+ <div id="recommendation"></div>
258
+ </div>
259
+ <button id="sendBtn" class="bx--btn bx--btn--primary" disabled>
260
+ Send
261
+ </button>
262
+ </div>
263
+ </div>
264
+
265
+ <!-- Graph Content -->
266
+ <div id="graph-content">
267
+ <div id="graph"></div>
268
+ </div>
269
  </div>
270
+
271
+ <!-- Dependencies -->
272
+ <script type="text/javascript" src="./js/d3.v7.min.js"></script>
273
+ <script type="text/javascript" src="./js/jquery-3.7.1.min.js"></script>
274
+
275
+ <script>
276
+ // =============================
277
+ // 1. State & Dimensions for D3
278
+ // =============================
279
+ let lastRecommendations = null; // stores latest /recommend response
280
+
281
+ const width = 600;
282
+ const height = 300;
283
+ const marginTop = 30;
284
+ const marginRight = 30;
285
+ const marginBottom = 30;
286
+ const marginLeft = 30;
287
+ const nodeRadius = 3;
288
+
289
+ // Create SVG once
290
+ const svg = d3
291
+ .select("#graph")
292
+ .append("svg")
293
+ .attr("viewBox", `0 0 ${width} ${height}`)
294
+ .attr("style", "max-width: 100%; height: auto; font: 8px sans-serif;");
295
+
296
+ const tooltip = d3
297
+ .select("body")
298
+ .append("div")
299
+ .attr("class", "tooltip")
300
+ .style("opacity", 0);
301
+
302
+ function renderGraph(recommendations) {
303
+ if (!recommendations) {
304
+ svg.selectAll("*").remove();
305
+ svg
306
+ .append("text")
307
+ .attr("x", width / 2)
308
+ .attr("y", height / 2)
309
+ .attr("text-anchor", "middle")
310
+ .attr("fill", "#666")
311
+ .text("No recommendation data available");
312
+ return;
313
+ }
314
+
315
+ const rec = recommendations;
316
+ const graphData = { nodes: [], edges: [] };
317
+ let i = 0,
318
+ j = 0;
319
+
320
+ // Input sentences
321
+ if (rec.input && rec.input.length > 0) {
322
+ graphData.nodes.push({
323
+ id: 0,
324
+ x: Number(rec.input[0].x),
325
+ y: Number(rec.input[0].y),
326
+ text: rec.input[0].sentence,
327
+ label: "S1",
328
+ type: "input",
329
+ });
330
+ for (i = 1; i < rec.input.length; i++) {
331
+ graphData.nodes.push({
332
+ id: i,
333
+ x: Number(rec.input[i].x),
334
+ y: Number(rec.input[i].y),
335
+ text: rec.input[i].sentence,
336
+ label: `S${i + 1}`,
337
+ type: "input",
338
+ });
339
+ graphData.edges.push({ source: i - 1, target: i, type: "input" });
340
+ }
341
+ }
342
+
343
+ // “Add” recommendations
344
+ if (rec.add && rec.add.length > 0) {
345
+ for (j = 0; j < rec.add.length; j++) {
346
+ graphData.nodes.push({
347
+ id: i + j,
348
+ x: Number(rec.add[j].x),
349
+ y: Number(rec.add[j].y),
350
+ text: rec.add[j].prompt,
351
+ label: rec.add[j].value,
352
+ type: "add",
353
+ });
354
+ graphData.edges.push({
355
+ source: i - 1,
356
+ target: i + j,
357
+ type: "add",
358
+ });
359
+ }
360
+ }
361
+
362
+ // “Remove” recommendation (first only)
363
+ if (rec.remove && rec.remove.length > 0) {
364
+ graphData.nodes.push({
365
+ id: i + j,
366
+ x: Number(rec.remove[0].x),
367
+ y: Number(rec.remove[0].y),
368
+ text: rec.remove[0].closest_harmful_sentence,
369
+ label: rec.remove[0].value,
370
+ type: "remove",
371
+ });
372
+ }
373
+
374
+ graphData.edges = graphData.edges.map((e) => ({
375
+ source: graphData.nodes.find((n) => n.id === e.source),
376
+ target: graphData.nodes.find((n) => n.id === e.target),
377
+ type: e.type,
378
+ }));
379
+
380
+ const { nodes, edges } = graphData;
381
+ if (nodes.length === 0) {
382
+ svg.selectAll("*").remove();
383
+ svg
384
+ .append("text")
385
+ .attr("x", width / 2)
386
+ .attr("y", height / 2)
387
+ .attr("text-anchor", "middle")
388
+ .attr("fill", "#666")
389
+ .text("No nodes to display");
390
+ return;
391
+ }
392
+
393
+ const xDomain = d3.extent(nodes, (d) => d.x);
394
+ const yDomain = d3.extent(nodes, (d) => d.y);
395
+ const xPadding = 2;
396
+ const yPadding = 2;
397
+
398
+ const xScale = d3
399
+ .scaleLinear()
400
+ .domain([xDomain[0] - xPadding, xDomain[1] + xPadding])
401
+ .nice()
402
+ .range([marginLeft, width - marginRight]);
403
+
404
+ const yScale = d3
405
+ .scaleLinear()
406
+ .domain([yDomain[0] - yPadding, yDomain[1] + yPadding])
407
+ .nice()
408
+ .range([height - marginBottom, marginTop]);
409
+
410
+ svg.selectAll("*").remove();
411
+
412
+ // X axis
413
+ svg
414
+ .append("g")
415
+ .attr("transform", `translate(0, ${height - marginBottom})`)
416
+ .call(d3.axisBottom(xScale).ticks(width / 80))
417
+ .call((g) => g.select(".domain").remove())
418
+ .call((g) =>
419
+ g
420
+ .append("text")
421
+ .attr("x", width)
422
+ .attr("y", marginBottom - 4)
423
+ .attr("fill", "currentColor")
424
+ .attr("text-anchor", "end")
425
+ .text("Semantic dimension 1")
426
+ );
427
+
428
+ // Y axis
429
+ svg
430
+ .append("g")
431
+ .attr("transform", `translate(${marginLeft}, 0)`)
432
+ .call(d3.axisLeft(yScale))
433
+ .call((g) => g.select(".domain").remove())
434
+ .call((g) =>
435
+ g
436
+ .append("text")
437
+ .attr("x", -marginLeft)
438
+ .attr("y", 10)
439
+ .attr("fill", "currentColor")
440
+ .attr("text-anchor", "start")
441
+ .text("Semantic dimension 2")
442
+ );
443
+
444
+ // Grid
445
+ svg
446
+ .append("g")
447
+ .attr("stroke", "#cccccc")
448
+ .attr("stroke-opacity", 0.5)
449
+ .call((g) =>
450
+ g
451
+ .append("g")
452
+ .selectAll("line")
453
+ .data(xScale.ticks())
454
+ .join("line")
455
+ .attr("x1", (d) => 0.5 + xScale(d))
456
+ .attr("x2", (d) => 0.5 + xScale(d))
457
+ .attr("y1", marginTop)
458
+ .attr("y2", height - marginBottom)
459
+ )
460
+ .call((g) =>
461
+ g
462
+ .append("g")
463
+ .selectAll("line")
464
+ .data(yScale.ticks())
465
+ .join("line")
466
+ .attr("y1", (d) => 0.5 + yScale(d))
467
+ .attr("y2", (d) => 0.5 + yScale(d))
468
+ .attr("x1", marginLeft)
469
+ .attr("x2", width - marginRight)
470
+ );
471
+
472
+ // Edges
473
+ svg
474
+ .append("g")
475
+ .selectAll("line")
476
+ .data(edges)
477
+ .join("line")
478
+ .attr("stroke", "#666")
479
+ .attr("stroke-opacity", 0.5)
480
+ .attr(
481
+ "x1",
482
+ (d) =>
483
+ xScale(d.source.x) +
484
+ (d.source.x < d.target.x ? 1.3 * nodeRadius : -1.3 * nodeRadius)
485
+ )
486
+ .attr("y1", (d) => yScale(d.source.y))
487
+ .attr(
488
+ "x2",
489
+ (d) =>
490
+ xScale(d.target.x) +
491
+ (d.source.x > d.target.x ? 1.3 * nodeRadius : -1.3 * nodeRadius)
492
+ )
493
+ .attr("y2", (d) => yScale(d.target.y))
494
+ .style("stroke-dasharray", (d) =>
495
+ d.target.type === "add" ? "3,3" : ""
496
+ );
497
+
498
+ // Nodes
499
+ svg
500
+ .append("g")
501
+ .attr("stroke-width", 2.5)
502
+ .attr("stroke-opacity", 0.5)
503
+ .attr("fill", "none")
504
+ .selectAll("circle")
505
+ .data(nodes)
506
+ .join("circle")
507
+ .attr("stroke", (d) =>
508
+ d.type === "add" ? "green" : d.type === "input" ? "#666" : "red"
509
+ )
510
+ .attr("cx", (d) => xScale(d.x))
511
+ .attr("cy", (d) => yScale(d.y))
512
+ .attr("r", nodeRadius);
513
+
514
+ // Labels
515
+ svg
516
+ .append("g")
517
+ .attr("font-family", "sans-serif")
518
+ .attr("text-opacity", 0.5)
519
+ .attr("font-size", 8)
520
+ .selectAll("text")
521
+ .data(nodes)
522
+ .join("text")
523
+ .attr("dy", "0.35em")
524
+ .attr("x", (d) => xScale(d.x) + 5)
525
+ .attr("y", (d) => yScale(d.y))
526
+ .text((d) => d.label)
527
+ .on("mousemove", function (event, d) {
528
+ d3.select(this)
529
+ .transition()
530
+ .duration(50)
531
+ .attr("text-opacity", 1.0)
532
+ .attr("stroke", "white")
533
+ .attr("stroke-width", 3)
534
+ .style("paint-order", "stroke fill")
535
+ .attr(
536
+ "fill",
537
+ d.type === "add"
538
+ ? "green"
539
+ : d.type === "input"
540
+ ? "black"
541
+ : "red"
542
+ );
543
+ tooltip.transition().duration(50).style("opacity", 1);
544
+ tooltip
545
+ .html(`<strong>${d.label}:</strong><br/>${d.text}`)
546
+ .style("left", event.pageX + 10 + "px")
547
+ .style("top", event.pageY + 10 + "px");
548
+ })
549
+ .on("mouseout", function () {
550
+ d3.select(this)
551
+ .transition()
552
+ .duration(50)
553
+ .attr("text-opacity", 0.5)
554
+ .attr("stroke-width", 0)
555
+ .style("paint-order", "fill")
556
+ .attr("fill", "black");
557
+ tooltip.transition().duration(50).style("opacity", 0);
558
+ });
559
  }
560
+
561
+ // Initially show “no data” message
562
+ renderGraph(null);
563
+
564
+ // =============================
565
+ // 2. Multi‐Turn Chat + Recommendation Logic
566
+ // =============================
567
+ const conversation = []; // stores { role, content, recs? }
568
+ let currentRecs = [];
569
+ let debounceId = null;
570
+
571
+ function appendUserTurn(turn) {
572
+ const bubble = $("<div>")
573
+ .addClass("message user")
574
+ .text(turn.content);
575
+ $("#chat").append(bubble);
576
+
577
+ if (turn.recs && turn.recs.length > 0) {
578
+ const container = $("<div>").addClass("recs-container");
579
+ turn.recs.forEach((r) => {
580
+ const item = $("<div>")
581
+ .addClass("recs-item")
582
+ .addClass(r.type === "add" ? "add" : "remove")
583
+ .text(r.value);
584
+ container.append(item);
585
+ });
586
+ container.css("align-self", "flex-end");
587
+ $("#chat").append(container);
588
+ }
589
+ $("#chat").scrollTop($("#chat")[0].scrollHeight);
590
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
 
592
+ function appendAssistantTurn(text) {
593
+ const bubble = $("<div>")
594
+ .addClass("message assistant")
595
+ .text(text);
596
+ $("#chat").append(bubble);
597
+ $("#chat").scrollTop($("#chat")[0].scrollHeight);
598
+ }
599
+
600
+ $("#userInput").on("input", function () {
601
+ const txt = $(this).val();
602
+ const trimmed = txt.trim();
603
+
604
+ if (trimmed.length > 0) {
605
+ $("#sendBtn").removeAttr("disabled");
606
+ } else {
607
+ $("#sendBtn").attr("disabled", true);
608
+ $("#recommendation").empty();
609
+ }
610
+
611
+ clearTimeout(debounceId);
612
+
613
+ if (txt.length > 0 && /[.?!]$/.test(txt)) {
614
+ debounceId = setTimeout(() => {
615
+ $("#recommendation").html(
616
+ 'Checking recommendations…'
617
+ );
618
+ $.getJSON("/recommend?prompt=" + encodeURIComponent(txt), (data) => {
619
+ $("#recommendation").empty();
620
+ lastRecommendations = data;
621
+
622
+ if (data.remove && data.remove.length > 0) {
623
+ const rec = data.remove[0];
624
+ const sentence = rec.sentence.replaceAll("'", "\\'");
625
+ const valueEscaped = rec.value.replaceAll("'", "\\'");
626
+ const $tag = $(`
627
  <div class="bx--tag bx--tag--red bx--tag--deletable">
628
  ✕ ${valueEscaped}
629
  </div>
630
  `);
631
 
632
+ $tag.hover(
633
+ () => {
634
+ const cur = $("#userInput").val();
635
+ $("#userInput").data("prevText", cur);
636
+ $("#userInput").val(cur.replace(rec.sentence, ""));
637
+ },
638
+ () => {
639
+ const prev = $("#userInput").data("prevText") || txt;
640
+ $("#userInput").val(prev);
641
+ }
642
+ );
643
+
644
+ $tag.click(() => {
645
+ const updated = $("#userInput")
646
+ .val()
647
+ .replace(rec.sentence, "")
648
+ .replace(/ {2,}/g, " ")
649
+ .trim();
650
+ $("#userInput").val(updated);
651
+ currentRecs.push({ type: "remove", value: rec.value });
652
+ $("#recommendation").empty();
653
+ $("#userInput").trigger("input");
654
+ });
655
+
656
+ $("#recommendation").append($tag);
657
+ }
658
+
659
+ if (data.add && data.add.length > 0) {
660
+ data.add.forEach((rec) => {
661
+ if (!$("#userInput").val().includes(rec.prompt)) {
662
+ const promptEscaped = rec.prompt.replaceAll("'", "\\'");
663
+ const valueEscaped = rec.value.replaceAll("'", "\\'");
664
+ const $tag = $(`
665
  <div class="bx--tag bx--tag--green">
666
  + ${valueEscaped}
667
  </div>
668
  `);
669
 
670
+ $tag.hover(
671
+ () => {
672
+ const cur = $("#userInput").val();
673
+ $("#userInput").data("prevAdd", cur);
674
+ $("#userInput").val((cur + " " + rec.prompt).trim());
675
+ },
676
+ () => {
677
+ const prev = $("#userInput").data("prevAdd") || txt;
678
+ $("#userInput").val(prev);
679
+ }
680
+ );
681
+
682
+ $tag.click(() => {
683
+ const base =
684
+ $("#userInput").data("prevAdd") ||
685
+ $("#userInput").val().trim();
686
+ $("#userInput").val((base + " " + rec.prompt).trim());
687
+ currentRecs.push({ type: "add", value: rec.value });
688
+ $("#recommendation").empty();
689
+ $("#userInput").trigger("input");
690
+ });
691
+
692
+ $("#recommendation").append($tag);
693
+ }
694
+ });
695
+ }
696
+
697
+ if (
698
+ (!data.add || data.add.length === 0) &&
699
+ (!data.remove || data.remove.length === 0)
700
+ ) {
701
+ $("#recommendation").text("No recommendations found.");
702
+ }
703
+ });
704
+ }, 500);
705
+ } else {
706
+ $("#recommendation").empty();
707
+ lastRecommendations = null;
708
+ }
709
+ });
710
+
711
+ $("#sendBtn").on("click", function () {
712
+ const rawText = $("#userInput").val();
713
+ const userText = rawText.trim();
714
+ if (!userText) return;
715
+
716
+ const thisTurn = {
717
+ role: "user",
718
+ content: userText,
719
+ recs: currentRecs.slice(),
720
+ };
721
+ conversation.push(thisTurn);
722
+ appendUserTurn(thisTurn);
723
+
724
+ $("#userInput").val("");
725
+ $("#recommendation").empty();
726
+ $("#sendBtn").attr("disabled", true);
727
+ currentRecs = [];
728
+
729
+ const typingBubble = $("<div>")
730
+ .addClass("message assistant")
731
+ .attr("id", "typing")
732
+ .text("Requesting Content…");
733
+ $("#chat").append(typingBubble);
734
+ $("#chat").scrollTop($("#chat")[0].scrollHeight);
735
+
736
+ let fullPrompt = "";
737
+ conversation.forEach((turn) => {
738
+ if (turn.role === "user") {
739
+ fullPrompt += `User: ${turn.content}\n`;
740
+ } else {
741
+ fullPrompt += `Assistant: ${turn.content}\n`;
742
+ }
743
+ });
744
+ fullPrompt += "Assistant: ";
745
+
746
+ $.ajax({
747
+ url: "/demo_inference?prompt=" + encodeURIComponent(fullPrompt),
748
+ dataType: "json",
749
+ success: function (data) {
750
+ $("#typing").remove();
751
+ const generated = data.content;
752
+ const modelId = data.model_id;
753
+ const temp = data.temperature;
754
+ const maxTokens = data.max_new_tokens;
755
+
756
+ let toType = `Model: ${modelId} · Temperature: ${temp} · Max tokens: ${maxTokens}\n\n`;
757
+ toType += generated;
758
+
759
+ const assistantBubble = $("<div>")
760
+ .addClass("message assistant")
761
+ .attr("id", "assistantBubble")
762
+ .text("");
763
+ $("#chat").append(assistantBubble);
764
+ $("#chat").scrollTop($("#chat")[0].scrollHeight);
765
+
766
+ const chars = toType.split("");
767
+ let idx = 0;
768
+ function typeNext() {
769
+ if (idx < chars.length) {
770
+ const cur = $("#assistantBubble").text();
771
+ $("#assistantBubble").text(cur + chars[idx]);
772
+ idx++;
773
+ $("#chat").scrollTop($("#chat")[0].scrollHeight);
774
+ setTimeout(typeNext, 10);
775
+ } else {
776
+ $("#assistantBubble").removeAttr("id");
777
+ conversation.push({
778
+ role: "assistant",
779
+ content: generated,
780
+ });
781
+ }
782
  }
783
+ typeNext();
784
+ },
785
+ error: function (xhr) {
786
+ $("#typing").remove();
787
+ const err =
788
+ xhr.responseJSON?.error?.message ||
789
+ "Unknown error from inference.";
790
+ appendAssistantTurn(`Error: ${err}`);
791
+ },
792
+ });
793
+ });
794
+
795
+ $("#userInput").on("keydown", function (e) {
796
+ if (e.key === "Enter" && !e.shiftKey) {
797
+ e.preventDefault();
798
+ if (!$("#sendBtn").attr("disabled")) {
799
+ $("#sendBtn").click();
800
  }
 
801
  }
802
+ });
803
 
804
+ // =============================
805
+ // 3. Tab Switching (Chat <-> Graph)
806
+ // =============================
807
+ $("#tab-chat").on("click", function () {
808
+ $(this).addClass("bx--tabs__nav-item--selected");
809
+ $("#tab-graph").removeClass("bx--tabs__nav-item--selected");
810
+ $("#chat-content").show();
811
+ $("#graph-content").hide();
812
+ });
813
+
814
+ $("#tab-graph").on("click", function () {
815
+ $(this).addClass("bx--tabs__nav-item--selected");
816
+ $("#tab-chat").removeClass("bx--tabs__nav-item--selected");
817
+ $("#chat-content").hide();
818
+ $("#graph-content").show();
819
+ renderGraph(lastRecommendations);
820
+ });
821
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822
  </body>
823
+
824
  </html>