ashwath-vaithina-ibm commited on
Commit
201b50b
·
verified ·
1 Parent(s): 630cf5e

Update static/demo/index.html

Browse files
Files changed (1) hide show
  1. static/demo/index.html +402 -157
static/demo/index.html CHANGED
@@ -6,30 +6,52 @@
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
  <title>Responsible Prompting – Multi‐Turn Chat + Graph</title>
8
 
9
- <script type="text/javascript" src="static/demo/js/d3.v7.min.js"></script>
10
- <script type="text/javascript" src="static/demo/js/jquery-3.7.1.min.js"></script>
 
 
11
  <!-- Carbon CSS (for tabs and tags) -->
12
- <link rel="stylesheet" href="https://unpkg.com/carbon-components/css/carbon-components.min.css" />
 
 
 
 
 
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
 
24
  body {
25
  display: flex;
26
  flex-direction: column;
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
 
29
  .header-container {
30
- background: white;
31
- padding: 1rem;
32
- border-bottom: 1px solid #ddd;
 
33
  }
34
 
35
  .bx--tabs__nav {
@@ -37,7 +59,7 @@
37
  display: flex;
38
  list-style: none;
39
  padding: 0;
40
- margin: 0;
41
  border-bottom: 2px solid #c6c6c6;
42
  }
43
 
@@ -45,34 +67,43 @@
45
  text-align: center;
46
  justify-content: center;
47
  display: block;
48
- margin-right: 1rem;
49
  padding: 0.5rem 0.75rem;
50
  cursor: pointer;
51
  font-size: 1rem;
52
  color: #393939;
53
  border-bottom: 2px solid transparent;
 
 
 
 
 
 
54
  }
55
 
56
  .bx--tabs__nav-item--selected {
57
- font-weight: bold;
58
  border-bottom: 2px solid #0f62fe !important;
 
59
  }
60
 
61
- /* The “content” area below header: either chat or graph */
62
  .tab-content {
63
  flex: 1;
64
  display: flex;
65
  flex-direction: column;
66
- overflow: hidden;
67
- padding: 5px;
68
  }
69
 
70
- /* When Chat is selected: show its container; Graph is hidden */
71
  #chat-content {
72
  display: flex;
73
  flex-direction: column;
74
  flex: 1;
75
  overflow: hidden;
 
 
 
 
76
  }
77
 
78
  #graph-content {
@@ -80,125 +111,185 @@
80
  flex: 1;
81
  overflow: auto;
82
  padding: 1rem;
 
 
 
83
  }
84
 
85
- /* ---------- Intro Paragraph ---------- */
86
- .intro {
87
- background: white;
88
- padding: 1rem;
89
- border-bottom: 1px solid #ddd;
90
- font-size: 0.95rem;
91
- line-height: 1.5;
92
- color: #393939;
93
- }
94
-
95
- .intro p {
96
- margin: 0.5rem 0;
97
- }
98
-
99
- /* ---------- Chat Area ---------- */
100
  .chat-container {
101
  flex: 1;
102
  overflow-y: auto;
103
- padding: 0.5rem 1rem;
104
- /* reduced top padding to avoid overlap */
105
  display: flex;
106
  flex-direction: column;
 
107
  }
108
 
 
109
  .message {
110
  max-width: 70%;
111
- margin-bottom: 0.75rem;
112
- padding: 0.5rem 0.75rem;
113
- border-radius: 0.5rem;
114
- line-height: 1.4;
115
  word-wrap: break-word;
116
  white-space: pre-wrap;
 
 
117
  }
118
 
119
  .message.user {
120
- background-color: #0f62fe;
121
- color: white;
122
  align-self: flex-end;
 
123
  }
124
 
125
  .message.assistant {
126
- background-color: #e0e0e0;
127
- color: #161616;
 
 
 
 
 
 
 
 
 
 
128
  align-self: flex-start;
129
  }
130
 
 
131
  .recs-container {
 
132
  display: flex;
133
- flex-wrap: wrap;
134
- margin-bottom: 0.75rem;
135
- margin-right: 0.25rem;
 
 
 
 
136
  }
137
 
138
  .recs-item {
139
  font-size: 0.85rem;
140
  padding: 0.25rem 0.5rem;
141
  border-radius: 0.25rem;
142
- margin-right: 0.25rem;
143
- margin-bottom: 0.25rem;
144
- color: white;
145
  }
146
 
147
  .recs-item.add {
148
  background-color: #24a148;
149
- /* green */
150
  }
151
 
152
  .recs-item.remove {
153
  background-color: #da1e28;
154
- /* red */
155
  }
156
 
157
- /* ---------- Input Area ---------- */
158
  .input-area {
159
- border-top: 1px solid #ddd;
160
- padding: 0.5rem 1rem;
161
  display: flex;
162
- align-items: flex-end;
163
- background: white;
164
- align-items: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  }
166
 
 
167
  #userInput {
168
  flex: 1;
169
- resize: none;
 
 
 
 
 
 
170
  font-size: 1rem;
171
- padding: 0.5rem;
172
- border: 1px solid #c6c6c6;
173
- border-radius: 0.375rem;
174
  font-family: inherit;
175
- line-height: 1.4;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
 
 
178
  #recommendation {
179
- margin-top: 0.25rem;
180
  font-size: 0.9rem;
181
  color: #393939;
182
  display: flex;
183
- flex-wrap: wrap;
184
- height: 30px;
185
- }
186
-
187
- #recommendation .bx--tag {
188
- margin-right: 0.25rem;
189
- margin-bottom: 0.25rem;
190
- cursor: pointer;
191
  }
192
 
193
- #sendBtn {
194
- margin-left: 0.5rem;
195
- margin-bottom: 0.25rem;
196
  }
197
 
198
- /* ---------- Graph Area ---------- */
199
  #graph {
200
  background: #efefef;
201
  position: relative;
 
 
202
  }
203
 
204
  .tooltip {
@@ -207,13 +298,15 @@
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>
@@ -221,50 +314,95 @@
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
  <div class="tab-content">
 
243
  <div id="chat-content">
244
  <div id="chat" class="chat-container"></div>
245
 
246
  <!-- Input area -->
247
  <div class="input-area">
248
- <div style="flex: 1; display: flex; flex-direction: column;">
249
- <textarea id="userInput" rows="4"
250
- 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>
 
 
 
251
  <div id="recommendation"></div>
 
 
 
 
 
252
  </div>
253
- <button id="sendBtn" class="bx--btn bx--btn--primary" disabled>
254
- Send
255
- </button>
256
  </div>
 
257
  </div>
258
 
 
259
  <div id="graph-content">
260
  <div id="graph"></div>
261
- </div>
262
  </div>
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
 
265
  <script>
266
- let lastRecommendations = null; // stores latest /recommend response
 
 
 
267
 
 
 
268
  const width = 600;
269
  const height = 300;
270
  const marginTop = 30;
@@ -274,11 +412,10 @@
274
  const nodeRadius = 3;
275
 
276
  // Create SVG once
277
- const svg = d3
278
  .select("#graph")
279
  .append("svg")
280
- .attr("viewBox", `0 0 ${width} ${height}`)
281
- .attr("style", "max-width: 100%; height: auto; font: 8px sans-serif;");
282
 
283
  const tooltip = d3
284
  .select("body")
@@ -286,24 +423,23 @@
286
  .attr("class", "tooltip")
287
  .style("opacity", 0);
288
 
289
- function renderGraph(recommendations) {
 
 
 
 
 
290
  if (!recommendations) {
291
- svg.selectAll("*").remove();
292
- svg
293
- .append("text")
294
- .attr("x", width / 2)
295
- .attr("y", height / 2)
296
- .attr("text-anchor", "middle")
297
- .attr("fill", "#666")
298
- .text("No recommendation data available");
299
  return;
300
  }
301
 
302
  const rec = recommendations;
303
- const graphData = { nodes: [], edges: [] };
304
  let i = 0,
305
  j = 0;
306
 
 
 
307
  // Input sentences
308
  if (rec.input && rec.input.length > 0) {
309
  graphData.nodes.push({
@@ -364,7 +500,28 @@
364
  type: e.type,
365
  }));
366
 
367
- const { nodes, edges } = graphData;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  if (nodes.length === 0) {
369
  svg.selectAll("*").remove();
370
  svg
@@ -546,10 +703,10 @@
546
  }
547
 
548
  // Initially show “no data” message
549
- renderGraph(null);
550
 
551
  const conversation = []; // stores { role, content, recs? }
552
- let currentRecs = [];
553
  let debounceId = null;
554
 
555
  function appendUserTurn(turn) {
@@ -560,11 +717,16 @@
560
 
561
  if (turn.recs && turn.recs.length > 0) {
562
  const container = $("<div>").addClass("recs-container");
563
- turn.recs.forEach((r) => {
 
564
  const item = $("<div>")
565
  .addClass("recs-item")
566
  .addClass(r.type === "add" ? "add" : "remove")
567
- .text(r.value);
 
 
 
 
568
  container.append(item);
569
  });
570
  container.css("align-self", "flex-end");
@@ -573,6 +735,59 @@
573
  $("#chat").scrollTop($("#chat")[0].scrollHeight);
574
  }
575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  function appendAssistantTurn(text) {
577
  const bubble = $("<div>")
578
  .addClass("message assistant")
@@ -581,11 +796,10 @@
581
  $("#chat").scrollTop($("#chat")[0].scrollHeight);
582
  }
583
 
584
- $("#userInput").on("input", function () {
585
- const txt = $(this).val();
586
- const trimmed = txt.trim();
587
 
588
- if (trimmed.length > 0) {
589
  $("#sendBtn").removeAttr("disabled");
590
  } else {
591
  $("#sendBtn").attr("disabled", true);
@@ -594,47 +808,57 @@
594
 
595
  clearTimeout(debounceId);
596
 
 
 
597
  if (txt.length > 0 && /[.?!]$/.test(txt)) {
598
  debounceId = setTimeout(() => {
599
- $("#recommendation").html(
600
- 'Checking recommendations…'
601
- );
602
  $.getJSON("/recommend?prompt=" + encodeURIComponent(txt), (data) => {
 
603
  $("#recommendation").empty();
604
  lastRecommendations = data;
 
605
 
606
  if (data.remove && data.remove.length > 0) {
607
  const rec = data.remove[0];
608
  const sentence = rec.sentence.replaceAll("'", "\\'");
609
  const valueEscaped = rec.value.replaceAll("'", "\\'");
610
  const $tag = $(`
611
- <div class="bx--tag bx--tag--red bx--tag--deletable">
612
- ✕ ${valueEscaped}
613
- </div>
614
- `);
615
-
616
  $tag.hover(
617
  () => {
618
- const cur = $("#userInput").val();
619
- $("#userInput").data("prevText", cur);
620
- $("#userInput").val(cur.replace(rec.sentence, ""));
 
 
 
 
 
621
  },
622
  () => {
623
- const prev = $("#userInput").data("prevText") || txt;
624
- $("#userInput").val(prev);
625
  }
626
  );
627
 
628
  $tag.click(() => {
629
- const updated = $("#userInput")
630
- .val()
631
  .replace(rec.sentence, "")
632
  .replace(/ {2,}/g, " ")
633
  .trim();
634
- $("#userInput").val(updated);
635
- currentRecs.push({ type: "remove", value: rec.value });
 
 
 
636
  $("#recommendation").empty();
637
- $("#userInput").trigger("input");
638
  });
639
 
640
  $("#recommendation").append($tag);
@@ -642,35 +866,40 @@
642
 
643
  if (data.add && data.add.length > 0) {
644
  data.add.forEach((rec) => {
645
- if (!$("#userInput").val().includes(rec.prompt)) {
646
  const promptEscaped = rec.prompt.replaceAll("'", "\\'");
647
  const valueEscaped = rec.value.replaceAll("'", "\\'");
648
  const $tag = $(`
649
- <div class="bx--tag bx--tag--green">
650
- + ${valueEscaped}
651
- </div>
652
- `);
653
-
 
654
  $tag.hover(
655
  () => {
656
- const cur = $("#userInput").val();
657
- $("#userInput").data("prevAdd", cur);
658
- $("#userInput").val((cur + " " + rec.prompt).trim());
 
659
  },
660
  () => {
661
- const prev = $("#userInput").data("prevAdd") || txt;
662
- $("#userInput").val(prev);
663
  }
664
  );
665
 
666
  $tag.click(() => {
667
  const base =
668
- $("#userInput").data("prevAdd") ||
669
- $("#userInput").val().trim();
670
- $("#userInput").val((base + " " + rec.prompt).trim());
671
- currentRecs.push({ type: "add", value: rec.value });
 
 
 
672
  $("#recommendation").empty();
673
- $("#userInput").trigger("input");
674
  });
675
 
676
  $("#recommendation").append($tag);
@@ -686,14 +915,19 @@
686
  }
687
  });
688
  }, 500);
 
 
 
689
  } else {
690
  $("#recommendation").empty();
691
  lastRecommendations = null;
692
  }
693
  });
694
 
 
 
695
  $("#sendBtn").on("click", function () {
696
- const rawText = $("#userInput").val();
697
  const userText = rawText.trim();
698
  if (!userText) return;
699
 
@@ -705,11 +939,12 @@
705
  conversation.push(thisTurn);
706
  appendUserTurn(thisTurn);
707
 
708
- $("#userInput").val("");
709
  $("#recommendation").empty();
710
  $("#sendBtn").attr("disabled", true);
711
  currentRecs = [];
712
 
 
713
  const typingBubble = $("<div>")
714
  .addClass("message assistant")
715
  .attr("id", "typing")
@@ -717,6 +952,7 @@
717
  $("#chat").append(typingBubble);
718
  $("#chat").scrollTop($("#chat")[0].scrollHeight);
719
 
 
720
  let fullPrompt = "";
721
  conversation.forEach((turn) => {
722
  if (turn.role === "user") {
@@ -728,18 +964,21 @@
728
  fullPrompt += "Assistant: ";
729
 
730
  $.ajax({
731
- url: "/demo_inference?prompt=" + encodeURIComponent(fullPrompt),
732
  dataType: "json",
733
  success: function (data) {
734
  $("#typing").remove();
735
- const generated = data.content;
736
  const modelId = data.model_id;
737
  const temp = data.temperature;
738
  const maxTokens = data.max_new_tokens;
739
 
740
- let toType = `Model: ${modelId} · Temperature: ${temp} · Max tokens: ${maxTokens}\n\n`;
741
- toType += generated;
 
 
742
 
 
743
  const assistantBubble = $("<div>")
744
  .addClass("message assistant")
745
  .attr("id", "assistantBubble")
@@ -747,16 +986,19 @@
747
  $("#chat").append(assistantBubble);
748
  $("#chat").scrollTop($("#chat")[0].scrollHeight);
749
 
750
- const chars = toType.split("");
751
  let idx = 0;
 
 
752
  function typeNext() {
753
  if (idx < chars.length) {
754
  const cur = $("#assistantBubble").text();
755
  $("#assistantBubble").text(cur + chars[idx]);
756
  idx++;
757
- $("#chat").scrollTop($("#chat")[0].scrollHeight);
758
- setTimeout(typeNext, 10);
759
  } else {
 
760
  $("#assistantBubble").removeAttr("id");
761
  conversation.push({
762
  role: "assistant",
@@ -765,6 +1007,7 @@
765
  }
766
  }
767
  typeNext();
 
768
  },
769
  error: function (xhr) {
770
  $("#typing").remove();
@@ -776,6 +1019,7 @@
776
  });
777
  });
778
 
 
779
  $("#userInput").on("keydown", function (e) {
780
  if (e.key === "Enter" && !e.shiftKey) {
781
  e.preventDefault();
@@ -785,6 +1029,7 @@
785
  }
786
  });
787
 
 
788
  $("#tab-chat").on("click", function () {
789
  $(this).addClass("bx--tabs__nav-item--selected");
790
  $("#tab-graph").removeClass("bx--tabs__nav-item--selected");
@@ -797,7 +1042,7 @@
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>
 
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
  <title>Responsible Prompting – Multi‐Turn Chat + Graph</title>
8
 
9
+ <!-- IBM Plex Sans -->
10
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&display=swap"
11
+ rel="stylesheet" />
12
+
13
  <!-- Carbon CSS (for tabs and tags) -->
14
+ <!-- <link rel="stylesheet" href="https://unpkg.com/carbon-components/css/carbon-components.min.css" /> -->
15
+
16
+ <link rel="stylesheet" href="./../styles/carbon-components.min.css" />
17
+
18
+ <script type="text/javascript" src="./js/d3.v7.min.js"></script>
19
+ <script type="text/javascript" src="./js/jquery-3.7.1.min.js"></script>
20
 
21
  <style>
22
+ /* ================== Global ================== */
23
  html,
24
  body {
25
  margin: 0;
26
  padding: 0;
27
  height: 100vh;
28
+ /* background-color: #f4f4f4; */
29
  font-family: "IBM Plex Sans", sans-serif;
30
+ color: #161616;
31
  }
32
 
33
  body {
34
  display: flex;
35
  flex-direction: column;
36
+ overflow: hidden;
37
+ /* center within a max width */
38
+ max-width: 1000px;
39
+ margin: 0 auto;
40
+ padding: 1rem 2rem;
41
+ box-sizing: border-box;
42
+ }
43
+
44
+ a {
45
+ color: #0f62fe;
46
+ text-decoration: none;
47
  }
48
 
49
+ /* ================== Header & Tabs ================== */
50
  .header-container {
51
+ /* background: #ffffff; */
52
+ padding: 1rem 0;
53
+ border-bottom: 1px solid #dddddd;
54
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.05);
55
  }
56
 
57
  .bx--tabs__nav {
 
59
  display: flex;
60
  list-style: none;
61
  padding: 0;
62
+ margin-top: 1em;
63
  border-bottom: 2px solid #c6c6c6;
64
  }
65
 
 
67
  text-align: center;
68
  justify-content: center;
69
  display: block;
 
70
  padding: 0.5rem 0.75rem;
71
  cursor: pointer;
72
  font-size: 1rem;
73
  color: #393939;
74
  border-bottom: 2px solid transparent;
75
+ font-weight: 500;
76
+ transition: color 0.2s;
77
+ }
78
+
79
+ .bx--tabs__nav-item:hover {
80
+ color: #0f62fe;
81
  }
82
 
83
  .bx--tabs__nav-item--selected {
84
+ font-weight: 600;
85
  border-bottom: 2px solid #0f62fe !important;
86
+ color: #161616;
87
  }
88
 
89
+ /* ================== Main Content Container ================== */
90
  .tab-content {
91
  flex: 1;
92
  display: flex;
93
  flex-direction: column;
94
+ min-height: 0;
 
95
  }
96
 
97
+ /* ================== Chat vs. Graph ================== */
98
  #chat-content {
99
  display: flex;
100
  flex-direction: column;
101
  flex: 1;
102
  overflow: hidden;
103
+ background: #ffffff;
104
+ border-radius: 8px;
105
+ box-sizing: border-box;
106
+ min-height: 0;
107
  }
108
 
109
  #graph-content {
 
111
  flex: 1;
112
  overflow: auto;
113
  padding: 1rem;
114
+ background: #ffffff;
115
+ border-radius: 8px;
116
+ box-sizing: border-box;
117
  }
118
 
119
+ /* ================== Chat Container ================== */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  .chat-container {
121
  flex: 1;
122
  overflow-y: auto;
123
+ padding: 0.5rem;
 
124
  display: flex;
125
  flex-direction: column;
126
+ gap: 0.25rem;
127
  }
128
 
129
+ /* ================== Message Bubbles ================== */
130
  .message {
131
  max-width: 70%;
132
+ padding: 0.75rem 1rem;
133
+ border-radius: 0.75rem;
134
+ line-height: 1.5;
 
135
  word-wrap: break-word;
136
  white-space: pre-wrap;
137
+ position: relative;
138
+ margin-bottom: 0.5rem;
139
  }
140
 
141
  .message.user {
142
+ background-color: #f2f4f8;
 
143
  align-self: flex-end;
144
+ box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
145
  }
146
 
147
  .message.assistant {
148
+ background-color: white;
149
+ align-self: flex-start;
150
+ padding-left: 2.5rem;
151
+ }
152
+
153
+ /* ================== Model Info Bar ================== */
154
+ .model-info {
155
+ font-size: 0.85rem;
156
+ color: #525252;
157
+ padding: 0.25rem 0.75rem;
158
+ border-radius: 0.5rem;
159
+ background-color: white;
160
  align-self: flex-start;
161
  }
162
 
163
+ /* ================== Recommendations Tags ================== */
164
  .recs-container {
165
+ max-width: 70%;
166
  display: flex;
167
+ flex-wrap: nowrap;
168
+ gap: 0.5rem;
169
+ margin-bottom: 1rem;
170
+ overflow-x: auto;
171
+ scrollbar-width: none;
172
+ white-space: nowrap;
173
+ flex-shrink: 0;
174
  }
175
 
176
  .recs-item {
177
  font-size: 0.85rem;
178
  padding: 0.25rem 0.5rem;
179
  border-radius: 0.25rem;
180
+ color: #ffffff;
181
+ flex-shrink: 0;
 
182
  }
183
 
184
  .recs-item.add {
185
  background-color: #24a148;
 
186
  }
187
 
188
  .recs-item.remove {
189
  background-color: #da1e28;
 
190
  }
191
 
192
+ /* ================== Input Area ================== */
193
  .input-area {
194
+ border-top: 1px solid #dddddd;
195
+ padding: 1rem;
196
  display: flex;
197
+ flex-direction: column;
198
+ background: #ffffff;
199
+ border-bottom-left-radius: 8px;
200
+ border-bottom-right-radius: 8px;
201
+ box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.05);
202
+ gap: 0.5rem;
203
+ box-sizing: border-box;
204
+ transition: border-color 0.2s, box-shadow 0.2s;
205
+ border: 1px solid #c6c6c6;
206
+ border-radius: 0.5rem;
207
+ }
208
+
209
+ .input-area:focus-within {
210
+ outline: none;
211
+ border-color: #0f62fe;
212
+ box-shadow: 0px 0px 0px 2px rgba(15, 98, 254, 0.2);
213
  }
214
 
215
+ /* Contenteditable box wrapper */
216
  #userInput {
217
  flex: 1;
218
+ display: flex;
219
+ flex-direction: column;
220
+ }
221
+
222
+ /* The editable area itself */
223
+ #userInput>div[contenteditable] {
224
+ min-height: 2.5rem;
225
  font-size: 1rem;
226
+ line-height: 1.5;
 
 
227
  font-family: inherit;
228
+ resize: none;
229
+ background-color: #ffffff;
230
+ }
231
+
232
+ [contenteditable]:focus {
233
+ outline: 0px solid transparent;
234
+ }
235
+
236
+ div[contenteditable] {
237
+ height: 10vh;
238
+ overflow: scroll;
239
+ }
240
+
241
+
242
+ /* Send button wrapper */
243
+ .btn {
244
+ border: none;
245
+ border-radius: 50%;
246
+ display: flex;
247
+ align-items: center;
248
+ justify-content: center;
249
+ cursor: pointer;
250
+ transition: background-color 0.2s;
251
+ background-color: white;
252
+ }
253
+
254
+ .btn:disabled {
255
+ cursor: not-allowed;
256
+ }
257
+
258
+ .icon {
259
+ width: 1.25rem;
260
+ height: 1.25rem;
261
+ }
262
+
263
+ #modelSelect {
264
+ border: 1px solid #c6c6c6;
265
+ border-radius: 5px;
266
+ padding: 0.25rem 0.25rem;
267
  }
268
 
269
+ /* ================== Recommendation Tag Container ================== */
270
  #recommendation {
271
+ flex: 1;
272
  font-size: 0.9rem;
273
  color: #393939;
274
  display: flex;
275
+ gap: 0.5rem;
276
+ min-height: 1.5rem;
277
+ overflow-x: scroll;
278
+ scrollbar-width: none;
279
+ white-space: nowrap;
280
+ align-items: center;
 
 
281
  }
282
 
283
+ .rec-tags-inputarea {
284
+ flex: 0 0 auto;
 
285
  }
286
 
287
+ /* ================== Graph Area ================== */
288
  #graph {
289
  background: #efefef;
290
  position: relative;
291
+ min-height: 300px;
292
+ border-radius: 8px;
293
  }
294
 
295
  .tooltip {
 
298
  padding: 0.5em;
299
  width: 20em;
300
  min-height: 5em;
301
+ background: #ffffff;
302
+ color: #000000;
303
+ border: 1px solid #000000;
304
  border-radius: 5px;
305
  pointer-events: none;
306
  font-size: inherit;
307
  font-family: inherit;
308
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
309
+ z-index: 10;
310
  }
311
  </style>
312
  </head>
 
314
  <body>
315
  <!-- ===== Header: Title + Carbon Tabs ===== -->
316
  <div class="header-container">
317
+ <div style="display: flex; flex-direction: row;">
318
+ <h4 style="width: 40%; display: flex; padding-left: 1rem; font-size: xx-large; font-weight: 300;">Responsible Prompting</h4>
319
+ <div style="width: 60%; padding-left: 1rem;" class="intro">
320
+ <p>
321
+ 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.
322
+ </p>
323
+ </div>
324
  </div>
325
+ <!-- <ol class="bx--tabs__nav">
326
  <li id="tab-chat" class="bx--tabs__nav-item bx--tabs__nav-item--selected">
327
  Chat
328
  </li>
329
  <li id="tab-graph" class="bx--tabs__nav-item">
330
  Graph
331
  </li>
332
+ </ol> -->
333
  </div>
334
+ <!-- <button class="btn bx--btn bx--btn--ghost" style="position: absolute; top: 0; right: 0; margin-top: 0.5rem; margin-right: 0.5rem;">
335
+ <img class="icon" src="./imgs/settings.svg"/>
336
+ </button> -->
337
 
338
  <div class="tab-content">
339
+ <!-- === Chat View === -->
340
  <div id="chat-content">
341
  <div id="chat" class="chat-container"></div>
342
 
343
  <!-- Input area -->
344
  <div class="input-area">
345
+ <div id="userInput">
346
+ <div id="userInputDiv" contenteditable="true" 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?
347
+ </div>
348
+ </div>
349
+
350
+ <div style="display: flex; justify-content: space-between; gap: 1rem;">
351
  <div id="recommendation"></div>
352
+ <select id="modelSelect"></select>
353
+ <!-- Send button -->
354
+ <button id="sendBtn" class="btn" disabled>
355
+ <img src="./imgs/send.svg" alt="Send" class="icon"/>
356
+ </button>
357
  </div>
 
 
 
358
  </div>
359
+ <!-- Under the input row: recommendation tags -->
360
  </div>
361
 
362
+ <!-- === Graph View ===
363
  <div id="graph-content">
364
  <div id="graph"></div>
365
+ </div> -->
366
  </div>
367
 
368
+ <script>
369
+ const models = [
370
+ { id: 'mistralai/Mistral-7B-Instruct-v0.3', name: 'Mistral 7B Instruct v0.3' },
371
+ { id: 'meta-llama/Llama-4-Scout-17B-16E-Instruct', name: 'Llama 4 Scout' },
372
+ ];
373
+ function createModelSelect() {
374
+ const modelSelect = document.getElementById('modelSelect');
375
+
376
+ models.forEach(model => {
377
+ const option = document.createElement('option');
378
+ option.value = model.id;
379
+ option.textContent = model.name;
380
+ modelSelect.appendChild(option);
381
+ });
382
+ }
383
+
384
+ // Call the function when the DOM is fully loaded
385
+ document.addEventListener('DOMContentLoaded', createModelSelect);
386
+
387
+
388
+ var modelId = models[0].id;
389
+ const modelSelect = document.getElementById('modelSelect');
390
+
391
+ modelSelect.addEventListener('change', function() {
392
+ const selectedModel = models.find(model => model.id === this.value);
393
+ modelId = selectedModel.id;
394
+ });
395
+ </script>
396
 
397
+ <!-- To show the bottom of text in -->
398
  <script>
399
+ var objDiv = document.getElementById("userInputDiv");
400
+ objDiv.scrollTop = objDiv.scrollHeight;
401
+ $("#userInputDiv").trigger("input");
402
+ </script>
403
 
404
+ <script>
405
+ let lastRecommendations = [];
406
  const width = 600;
407
  const height = 300;
408
  const marginTop = 30;
 
412
  const nodeRadius = 3;
413
 
414
  // Create SVG once
415
+ var svg = d3
416
  .select("#graph")
417
  .append("svg")
418
+ .attr('id', 'svgMain');
 
419
 
420
  const tooltip = d3
421
  .select("body")
 
423
  .attr("class", "tooltip")
424
  .style("opacity", 0);
425
 
426
+ var graphData = { nodes: [], edges: [] };
427
+ function generateAndRenderGraph(recommendations, svgId) {
428
+ svg = d3
429
+ .select(svgId)
430
+ .attr("viewBox", `0 0 ${width} ${height}`)
431
+ .attr("style", "max-width: 100%; height: auto; font: 8px sans-serif;");
432
  if (!recommendations) {
433
+ clearGraph();
 
 
 
 
 
 
 
434
  return;
435
  }
436
 
437
  const rec = recommendations;
 
438
  let i = 0,
439
  j = 0;
440
 
441
+ graphData = { nodes: [], edges: [] };
442
+
443
  // Input sentences
444
  if (rec.input && rec.input.length > 0) {
445
  graphData.nodes.push({
 
500
  type: e.type,
501
  }));
502
 
503
+ renderGraph(graphData, svgId)
504
+ }
505
+
506
+ function clearGraph() {
507
+ svg.selectAll("*").remove();
508
+ svg
509
+ .append("text")
510
+ .attr("x", width / 2)
511
+ .attr("y", height / 2)
512
+ .attr("text-anchor", "middle")
513
+ .attr("fill", "#666")
514
+ .text("No recommendation data available");
515
+ }
516
+
517
+ function renderGraph(graphData, svgId) {
518
+ svg = d3
519
+ .select(svgId)
520
+ .attr("viewBox", `0 0 ${width} ${height}`)
521
+ .attr("style", "max-width: 100%; height: auto; font: 8px sans-serif;");
522
+
523
+ let { nodes, edges } = graphData;
524
+
525
  if (nodes.length === 0) {
526
  svg.selectAll("*").remove();
527
  svg
 
703
  }
704
 
705
  // Initially show “no data” message
706
+ generateAndRenderGraph(null, "#svgMain");
707
 
708
  const conversation = []; // stores { role, content, recs? }
709
+ let currentRecs = []; // stores { recommendation, graphData }
710
  let debounceId = null;
711
 
712
  function appendUserTurn(turn) {
 
717
 
718
  if (turn.recs && turn.recs.length > 0) {
719
  const container = $("<div>").addClass("recs-container");
720
+ turn.recs.forEach((rec, index) => {
721
+ let r = rec.recommendation;
722
  const item = $("<div>")
723
  .addClass("recs-item")
724
  .addClass(r.type === "add" ? "add" : "remove")
725
+ .attr('title', r.sentence)
726
+ .text((r.type === "add" ? "+ " : "x ") + r.value);
727
+ let itemId = `rec-${conversation.length}-${index}`;
728
+ item.attr('id', itemId);
729
+ item.click(() => toggleGraph(rec.graphData, itemId));
730
  container.append(item);
731
  });
732
  container.css("align-self", "flex-end");
 
735
  $("#chat").scrollTop($("#chat")[0].scrollHeight);
736
  }
737
 
738
+ let popupOpen = false;
739
+ function toggleGraph(data, itemId) {
740
+ const existing = document.getElementById('popup');
741
+ if (existing) {
742
+ existing.remove();
743
+ popupOpen = false;
744
+ return;
745
+ }
746
+ const tag = document.getElementById(itemId);
747
+ const rect = tag.getBoundingClientRect();
748
+ const popup = document.createElement('div');
749
+ popup.id = 'popup';
750
+ // reuse your .tooltip styles but allow pointer events
751
+ popup.className = 'tooltip';
752
+ popup.style.pointerEvents = 'auto';
753
+ popup.style.width = '600px';
754
+ popup.style.height = '315px';
755
+ popup.style.zIndex = 2;
756
+ popup.style.left = `${rect.left + window.scrollX}px`;
757
+ popup.style.top = `${rect.top + window.scrollY + 25}px`;
758
+
759
+ // When the popup is open when the chat is being scrolled,
760
+ // it leads to a lot of weird behaviour
761
+ // so, for now, the popup is force closed when the chat is scrolling
762
+ document.getElementById("chat").addEventListener("scroll", () => {
763
+ popup.remove();
764
+ popupOpen = false;
765
+ return;
766
+ })
767
+
768
+ const container = document.createElement('svg');
769
+ container.style.width = '100%';
770
+ container.style.height = '100%';
771
+
772
+ container.innerHTML = "<svg id='popup-graph'></svg>"
773
+ popup.appendChild(container);
774
+ document.body.appendChild(popup);
775
+
776
+ renderGraph(data, '#popup-graph');
777
+ // close on outside click
778
+ const onClickOutside = (e) => {
779
+ if (!popup.contains(e.target) && e.target !== tag) {
780
+ popup.remove();
781
+ document.removeEventListener('click', onClickOutside);
782
+ popupOpen = false;
783
+ }
784
+ };
785
+ setTimeout(() => {
786
+ document.addEventListener('click', onClickOutside);
787
+ }, 0);
788
+ popupOpen = true;
789
+ }
790
+
791
  function appendAssistantTurn(text) {
792
  const bubble = $("<div>")
793
  .addClass("message assistant")
 
796
  $("#chat").scrollTop($("#chat")[0].scrollHeight);
797
  }
798
 
799
+ $("#userInputDiv").on("input", function () {
800
+ const txt = $(this).text().trim() || "";
 
801
 
802
+ if (txt.length > 0) {
803
  $("#sendBtn").removeAttr("disabled");
804
  } else {
805
  $("#sendBtn").attr("disabled", true);
 
808
 
809
  clearTimeout(debounceId);
810
 
811
+ let stopRecos = false;
812
+
813
  if (txt.length > 0 && /[.?!]$/.test(txt)) {
814
  debounceId = setTimeout(() => {
815
+ $("#recommendation").html("Checking recommendations…");
 
 
816
  $.getJSON("/recommend?prompt=" + encodeURIComponent(txt), (data) => {
817
+ if (stopRecos) return;
818
  $("#recommendation").empty();
819
  lastRecommendations = data;
820
+ generateAndRenderGraph(lastRecommendations, "#svgMain");
821
 
822
  if (data.remove && data.remove.length > 0) {
823
  const rec = data.remove[0];
824
  const sentence = rec.sentence.replaceAll("'", "\\'");
825
  const valueEscaped = rec.value.replaceAll("'", "\\'");
826
  const $tag = $(`
827
+ <div class="bx--tag bx--tag--red bx--tag--deletable rec-tags-inputarea">
828
+ ✕ ${valueEscaped}
829
+ </div>
830
+ `);
831
+ // Removal recommendations
832
  $tag.hover(
833
  () => {
834
+ const cur = $("#userInputDiv").html();
835
+ $("#userInputDiv").data("prevHtml", cur);
836
+ $("#userInputDiv").html(
837
+ $("#userInputDiv").html().replace(
838
+ rec.sentence.trim(),
839
+ " <span style='color: red; text-decoration: line-through'>" + rec.sentence.trim() + "</span>"
840
+ )
841
+ )
842
  },
843
  () => {
844
+ const prev = $("#userInputDiv").data("prevHtml") || txt;
845
+ $("#userInputDiv").html(prev);
846
  }
847
  );
848
 
849
  $tag.click(() => {
850
+ const updated = $("#userInputDiv")
851
+ .text()
852
  .replace(rec.sentence, "")
853
  .replace(/ {2,}/g, " ")
854
  .trim();
855
+ $("#userInputDiv").text(updated);
856
+ currentRecs.push({
857
+ 'recommendation': { type: "remove", value: rec.value, sentence: rec.sentence },
858
+ 'graphData': graphData,
859
+ });
860
  $("#recommendation").empty();
861
+ $("#userInputDiv").trigger("input");
862
  });
863
 
864
  $("#recommendation").append($tag);
 
866
 
867
  if (data.add && data.add.length > 0) {
868
  data.add.forEach((rec) => {
869
+ if (!$("#userInputDiv").text().includes(rec.prompt)) {
870
  const promptEscaped = rec.prompt.replaceAll("'", "\\'");
871
  const valueEscaped = rec.value.replaceAll("'", "\\'");
872
  const $tag = $(`
873
+ <div class="bx--tag bx--tag--green rec-tags-inputarea">
874
+ + ${valueEscaped}
875
+ </div>
876
+ `);
877
+
878
+ // Addition recommendations
879
  $tag.hover(
880
  () => {
881
+ const cur = $("#userInputDiv").html();
882
+ $("#userInputDiv").data("prevHtml", cur);
883
+ $("#userInputDiv").html($("#userInputDiv").html() + " <span style='color: green'>" + rec.prompt.trim() + "</span>")
884
+ $("#userInputDiv").scrollTop($("#userInputDiv")[0].scrollHeight);
885
  },
886
  () => {
887
+ const prev = $("#userInputDiv").data("prevHtml") || txt;
888
+ $("#userInputDiv").html(prev);
889
  }
890
  );
891
 
892
  $tag.click(() => {
893
  const base =
894
+ $("#userInputDiv").data("prevHtml") ||
895
+ $("#userInputDiv").html().trim();
896
+ $("#userInputDiv").html((base + " " + rec.prompt).trim());
897
+ currentRecs.push({
898
+ 'recommendation': { type: "add", value: rec.value, sentence: rec.prompt },
899
+ 'graphData': graphData,
900
+ });
901
  $("#recommendation").empty();
902
+ $("#userInputDiv").trigger("input");
903
  });
904
 
905
  $("#recommendation").append($tag);
 
915
  }
916
  });
917
  }, 500);
918
+ $("#sendBtn").on("click", function () {
919
+ stopRecos = true;
920
+ })
921
  } else {
922
  $("#recommendation").empty();
923
  lastRecommendations = null;
924
  }
925
  });
926
 
927
+ $("#userInputDiv").trigger('input');
928
+
929
  $("#sendBtn").on("click", function () {
930
+ const rawText = $("#userInputDiv").text() || "";
931
  const userText = rawText.trim();
932
  if (!userText) return;
933
 
 
939
  conversation.push(thisTurn);
940
  appendUserTurn(thisTurn);
941
 
942
+ $("#userInputDiv").text("");
943
  $("#recommendation").empty();
944
  $("#sendBtn").attr("disabled", true);
945
  currentRecs = [];
946
 
947
+ // "Typing..." placeholder
948
  const typingBubble = $("<div>")
949
  .addClass("message assistant")
950
  .attr("id", "typing")
 
952
  $("#chat").append(typingBubble);
953
  $("#chat").scrollTop($("#chat")[0].scrollHeight);
954
 
955
+ // Build full prompt
956
  let fullPrompt = "";
957
  conversation.forEach((turn) => {
958
  if (turn.role === "user") {
 
964
  fullPrompt += "Assistant: ";
965
 
966
  $.ajax({
967
+ url: "/demo_inference?prompt=" + encodeURIComponent(fullPrompt) + "&model_id=" + encodeURIComponent(modelId),
968
  dataType: "json",
969
  success: function (data) {
970
  $("#typing").remove();
971
+ const generated = data.content.trim();
972
  const modelId = data.model_id;
973
  const temp = data.temperature;
974
  const maxTokens = data.max_new_tokens;
975
 
976
+ const modelHeader = $("<div style='display: flex; flex-direction: row;'>")
977
+ .addClass("model-info")
978
+ .html(`<img src="./imgs/granite.svg" class='icon'/> <div style='display:flex; align-items: center;margin-left: 0.5rem'>${modelId}</div>`);
979
+ $("#chat").append(modelHeader);
980
 
981
+ // Now type the assistant's generated text:
982
  const assistantBubble = $("<div>")
983
  .addClass("message assistant")
984
  .attr("id", "assistantBubble")
 
986
  $("#chat").append(assistantBubble);
987
  $("#chat").scrollTop($("#chat")[0].scrollHeight);
988
 
989
+ const chars = generated.split("");
990
  let idx = 0;
991
+ const chatEl = $("#chat")[0];
992
+ const wasAtBottom = chatEl.scrollHeight - chatEl.scrollTop <= chatEl.clientHeight + 5;
993
  function typeNext() {
994
  if (idx < chars.length) {
995
  const cur = $("#assistantBubble").text();
996
  $("#assistantBubble").text(cur + chars[idx]);
997
  idx++;
998
+ if(wasAtBottom) $("#chat").scrollTop($("#chat")[0].scrollHeight);
999
+ setTimeout(typeNext, 0);
1000
  } else {
1001
+ if(wasAtBottom) $("#chat").scrollTop($("#chat")[0].scrollHeight);
1002
  $("#assistantBubble").removeAttr("id");
1003
  conversation.push({
1004
  role: "assistant",
 
1007
  }
1008
  }
1009
  typeNext();
1010
+ // $("#assistantBubble").text(generated)
1011
  },
1012
  error: function (xhr) {
1013
  $("#typing").remove();
 
1019
  });
1020
  });
1021
 
1022
+ // Pressing Enter (without Shift) also sends
1023
  $("#userInput").on("keydown", function (e) {
1024
  if (e.key === "Enter" && !e.shiftKey) {
1025
  e.preventDefault();
 
1029
  }
1030
  });
1031
 
1032
+ // Tab switching
1033
  $("#tab-chat").on("click", function () {
1034
  $(this).addClass("bx--tabs__nav-item--selected");
1035
  $("#tab-graph").removeClass("bx--tabs__nav-item--selected");
 
1042
  $("#tab-chat").removeClass("bx--tabs__nav-item--selected");
1043
  $("#chat-content").hide();
1044
  $("#graph-content").show();
1045
+ generateAndRenderGraph(lastRecommendations, "#svgMain");
1046
  });
1047
  </script>
1048
  </body>