pvanand commited on
Commit
0f116bc
·
verified ·
1 Parent(s): 51c5935

Update static/graph-maker.html

Browse files
Files changed (1) hide show
  1. static/graph-maker.html +253 -88
static/graph-maker.html CHANGED
@@ -1,90 +1,136 @@
1
-
2
  <!DOCTYPE html>
3
  <html lang="en">
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Interactive Chart Maker with LLM</title>
8
- <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
9
- <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
10
- <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.min.js"></script>
11
- <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.3.4/axios.min.js"></script>
 
 
 
12
  <style>
13
- body {
14
- padding-top: 2rem;
15
- background-color: #f8f9fa;
16
- }
17
- .card {
18
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
19
- }
20
  #mermaid-output {
21
- min-height: 200px;
22
  width: 100%;
23
- margin: 1rem auto;
24
  position: relative;
25
  background-color: white;
26
  }
27
  .loading-overlay {
28
  position: absolute;
29
- top: 0;
30
- left: 0;
31
- right: 0;
32
- bottom: 0;
33
  background-color: rgba(255, 255, 255, 0.7);
34
  display: flex;
35
  justify-content: center;
36
  align-items: center;
37
  z-index: 1000;
38
  }
39
- .code-toggle {
40
- font-size: 0.9rem;
41
- padding: 0.25rem 0.5rem;
42
- margin-bottom: 0.5rem;
 
43
  }
44
- .code-toggle::after {
45
- content: " ▼";
46
- font-size: 0.8em;
47
- transition: transform 0.2s;
48
- display: inline-block;
49
- margin-left: 0.25rem;
50
  }
51
- .code-toggle.collapsed::after {
52
- transform: rotate(-90deg);
 
 
 
 
 
53
  }
54
- @media (max-width: 768px) {
55
- .input-group {
56
- flex-direction: column;
57
- }
58
- .input-group > * {
59
- margin-bottom: 0.5rem;
60
- width: 100%;
61
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
  </style>
64
  </head>
65
  <body>
66
  <div id="app" class="container">
67
- <h1 class="mb-4 text-center">Interactive Chart Maker</h1>
68
  <div class="row justify-content-center">
69
- <div class="col-lg-10 col-md-12">
70
  <div class="card mb-4">
71
  <div class="card-body">
72
  <div class="input-group mb-3">
73
- <input v-model="chartDescription" :disabled="isInputDisabled" class="form-control" placeholder="Describe the chart you want..." aria-label="Chart description" />
 
 
 
 
 
 
 
 
 
74
  <button @click="generateChart" :disabled="isInputDisabled" class="btn btn-primary">Generate Chart</button>
75
  </div>
76
- <div class="d-flex justify-content-end">
77
- <button class="btn btn-outline-secondary btn-sm code-toggle collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#codeCollapse" aria-expanded="false" aria-controls="codeCollapse">
78
- Code
79
- </button>
80
- </div>
81
- <div class="collapse" id="codeCollapse">
82
- <textarea v-model="mermaidInput" @input="debouncedRenderMermaid" :disabled="isInputDisabled" class="form-control mb-3" rows="6" placeholder="Mermaid syntax will appear here..." aria-label="Mermaid syntax"></textarea>
83
  </div>
84
- <div id="mermaid-output" class="border rounded p-3">
85
- <div v-if="loading" class="loading-overlay" aria-busy="true">
86
- <div class="spinner-border text-primary" role="status">
87
- <span class="visually-hidden">Loading...</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  </div>
89
  </div>
90
  </div>
@@ -98,17 +144,13 @@
98
  <script type="module">
99
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
100
 
101
- const defaultConfig = {
102
  startOnLoad: true,
103
  securityLevel: 'strict',
104
  flowchart: { useMaxWidth: false },
105
  gantt: { useMaxWidth: false },
106
  theme: 'default'
107
- };
108
-
109
- mermaid.initialize(defaultConfig);
110
-
111
- const { createApp, ref, watch } = Vue;
112
 
113
  function debounce(func, wait) {
114
  let timeout;
@@ -122,25 +164,23 @@
122
  };
123
  }
124
 
 
 
125
  createApp({
126
  setup() {
 
127
  const chartDescription = ref('');
128
- const mermaidInput = ref('');
129
  const error = ref('');
130
  const loading = ref(false);
131
  const isInputDisabled = ref(false);
 
 
 
 
132
 
133
  const API_ENDPOINT = 'https://pvanand-audio-chat.hf.space/llm-agent';
134
  const API_KEY = '44d5c2ac18ced6fc25c1e57dcd06fc0b31fb4ad97bf56e67540671a647465df4';
135
-
136
- const cleanMermaidSyntax = (input) => {
137
- let cleaned = input.trim();
138
- cleaned = cleaned.replace(/^```mermaid\n?|```$/gm, '');
139
- if (!cleaned.match(/^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|journey|gantt|pie|requirementDiagram)/)) {
140
- cleaned = 'graph TD\n' + cleaned;
141
- }
142
- return cleaned;
143
- };
144
 
145
  const generateChart = async () => {
146
  if (!chartDescription.value.trim()) {
@@ -150,7 +190,6 @@
150
 
151
  loading.value = true;
152
  error.value = '';
153
- mermaidInput.value = '';
154
  isInputDisabled.value = true;
155
 
156
  try {
@@ -168,7 +207,8 @@
168
  }
169
  });
170
 
171
- mermaidInput.value = cleanMermaidSyntax(response.data);
 
172
  } catch (err) {
173
  error.value = 'Error generating chart. Please try again.';
174
  console.error('API Error:', err.message);
@@ -178,38 +218,163 @@
178
  }
179
  };
180
 
181
- const renderMermaid = () => {
182
- const output = document.getElementById('mermaid-output');
183
- mermaid.render('mermaid-diagram', mermaidInput.value)
184
- .then(({ svg }) => {
185
- output.innerHTML = svg;
186
- error.value = '';
187
- })
188
- .catch(err => {
189
- error.value = 'Error rendering diagram. Please check your Mermaid syntax.';
190
- console.error('Mermaid Error:', err.message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  });
 
 
 
 
 
192
  };
193
 
194
- const debouncedRenderMermaid = debounce(renderMermaid, 300);
195
 
196
- watch(mermaidInput, (newValue) => {
197
- if (newValue.trim() !== '') {
198
- debouncedRenderMermaid();
 
199
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  });
201
 
202
  return {
203
  chartDescription,
204
- mermaidInput,
205
  error,
206
  loading,
207
  isInputDisabled,
 
 
 
208
  generateChart,
209
- debouncedRenderMermaid
 
 
 
 
 
210
  };
211
  }
212
  }).mount('#app');
213
  </script>
214
  </body>
215
- </html>
 
 
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.0">
6
+ <title>Streamlined Interactive Chart Maker</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
9
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/d3.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js"></script>
14
  <style>
15
+ body { padding-top: 2rem; background-color: #f8f9fa; }
16
+ .card { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
 
 
 
 
 
17
  #mermaid-output {
 
18
  width: 100%;
19
+ height: 100%;
20
  position: relative;
21
  background-color: white;
22
  }
23
  .loading-overlay {
24
  position: absolute;
25
+ top: 0; left: 0; right: 0; bottom: 0;
 
 
 
26
  background-color: rgba(255, 255, 255, 0.7);
27
  display: flex;
28
  justify-content: center;
29
  align-items: center;
30
  z-index: 1000;
31
  }
32
+ .chart-output {
33
+ height: 800px;
34
+ width: 100%;
35
+ overflow: hidden;
36
+ position: relative;
37
  }
38
+ #mermaid-output svg { width: 100%; height: 100%; }
39
+ @media (max-width: 768px) {
40
+ .input-group { flex-direction: column; }
41
+ .input-group > * { margin-bottom: 0.5rem; width: 100%; }
 
 
42
  }
43
+ .fullscreen #mermaid-output {
44
+ position: fixed;
45
+ top: 0;
46
+ left: 0;
47
+ width: 100vw;
48
+ height: 100vh;
49
+ z-index: 9999;
50
  }
51
+ .recommendations {
52
+ position: absolute;
53
+ background-color: white;
54
+ border: 1px solid #ddd;
55
+ border-top: none;
56
+ max-height: 200px;
57
+ overflow-y: auto;
58
+ width: 100%;
59
+ z-index: 1000;
60
+ }
61
+ .recommendations ul {
62
+ list-style-type: none;
63
+ padding: 0;
64
+ margin: 0;
65
+ }
66
+ .recommendations li {
67
+ padding: 10px;
68
+ cursor: pointer;
69
+ }
70
+ .recommendations li:hover {
71
+ background-color: #f0f0f0;
72
+ }
73
+ .chart-controls {
74
+ position: absolute;
75
+ top: 10px;
76
+ right: 10px;
77
+ z-index: 1001;
78
+ }
79
+ .chart-controls .btn {
80
+ padding: 0.375rem 0.75rem;
81
+ }
82
+ .chart-controls .btn i {
83
+ font-size: 1.25rem;
84
  }
85
  </style>
86
  </head>
87
  <body>
88
  <div id="app" class="container">
89
+ <h1 class="mb-4 text-center">Architecture Nexus</h1>
90
  <div class="row justify-content-center">
91
+ <div class="col-12">
92
  <div class="card mb-4">
93
  <div class="card-body">
94
  <div class="input-group mb-3">
95
+ <input
96
+ v-model="chartDescription"
97
+ :disabled="isInputDisabled"
98
+ class="form-control"
99
+ placeholder="Describe the chart you want..."
100
+ aria-label="Chart description"
101
+ @focus="onInputFocus"
102
+ @input="onInputChange"
103
+ @blur="onInputBlur"
104
+ />
105
  <button @click="generateChart" :disabled="isInputDisabled" class="btn btn-primary">Generate Chart</button>
106
  </div>
107
+ <div v-if="showRecommendations" class="recommendations">
108
+ <ul>
109
+ <li v-for="recommendation in recommendations" @click="selectRecommendation(recommendation)">
110
+ {{ recommendation }}
111
+ </li>
112
+ </ul>
 
113
  </div>
114
+ <div class="chart-output" :class="{ 'fullscreen': isFullscreen }">
115
+ <div class="chart-controls btn-group">
116
+ <button @click="toggleFullscreen" class="btn btn-secondary" :title="isFullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'">
117
+ <i class="bi" :class="isFullscreen ? 'bi-fullscreen-exit' : 'bi-arrows-fullscreen'"></i>
118
+ </button>
119
+ <div class="btn-group">
120
+ <button type="button" class="btn btn-secondary " data-bs-toggle="dropdown" aria-expanded="false" title="Download">
121
+ <i class="bi bi-download"></i>
122
+ </button>
123
+ <ul class="dropdown-menu">
124
+ <li><a class="dropdown-item" href="#" @click="downloadChart('png')">Download as PNG</a></li>
125
+ <li><a class="dropdown-item" href="#" @click="downloadChart('svg')">Download as SVG</a></li>
126
+ </ul>
127
+ </div>
128
+ </div>
129
+ <div id="mermaid-output" class="border rounded">
130
+ <div v-if="loading" class="loading-overlay" aria-busy="true">
131
+ <div class="spinner-border text-primary" role="status">
132
+ <span class="visually-hidden">Loading...</span>
133
+ </div>
134
  </div>
135
  </div>
136
  </div>
 
144
  <script type="module">
145
  import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
146
 
147
+ mermaid.initialize({
148
  startOnLoad: true,
149
  securityLevel: 'strict',
150
  flowchart: { useMaxWidth: false },
151
  gantt: { useMaxWidth: false },
152
  theme: 'default'
153
+ });
 
 
 
 
154
 
155
  function debounce(func, wait) {
156
  let timeout;
 
164
  };
165
  }
166
 
167
+ const { createApp, ref } = Vue;
168
+
169
  createApp({
170
  setup() {
171
+ const chartReady = ref(false);
172
  const chartDescription = ref('');
 
173
  const error = ref('');
174
  const loading = ref(false);
175
  const isInputDisabled = ref(false);
176
+ const isFullscreen = ref(false);
177
+ const recommendations = ref([]);
178
+ const showRecommendations = ref(false);
179
+ const isFirstFocus = ref(true);
180
 
181
  const API_ENDPOINT = 'https://pvanand-audio-chat.hf.space/llm-agent';
182
  const API_KEY = '44d5c2ac18ced6fc25c1e57dcd06fc0b31fb4ad97bf56e67540671a647465df4';
183
+ const RECOMMENDATIONS_ENDPOINT = 'https://pvanand-generate-subtopics.hf.space/get_recommendations_chart';
 
 
 
 
 
 
 
 
184
 
185
  const generateChart = async () => {
186
  if (!chartDescription.value.trim()) {
 
190
 
191
  loading.value = true;
192
  error.value = '';
 
193
  isInputDisabled.value = true;
194
 
195
  try {
 
207
  }
208
  });
209
 
210
+ const cleanedInput = response.data.trim().replace(/^```mermaid\n?|```$/gm, '');
211
+ renderMermaid(cleanedInput);
212
  } catch (err) {
213
  error.value = 'Error generating chart. Please try again.';
214
  console.error('API Error:', err.message);
 
218
  }
219
  };
220
 
221
+ const renderMermaid = async (input) => {
222
+ try {
223
+ const { svg } = await mermaid.render('mermaid-diagram', input);
224
+ const container = document.getElementById('mermaid-output');
225
+ container.innerHTML = svg;
226
+ const svgElement = container.querySelector('svg');
227
+
228
+ // Set viewBox attribute to ensure the entire SVG is visible
229
+ const bbox = svgElement.getBBox();
230
+ svgElement.setAttribute('viewBox', `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`);
231
+
232
+ // Set width and height to 100% to fill the container
233
+ svgElement.setAttribute('width', '100%');
234
+ svgElement.setAttribute('height', '100%');
235
+
236
+ error.value = '';
237
+ addZoomFunctionality();
238
+ chartReady.value = true;
239
+ } catch (err) {
240
+ error.value = 'Error rendering diagram. Please check your Mermaid syntax.';
241
+ console.error('Mermaid Error:', err.message);
242
+ }
243
+ };
244
+
245
+ const addZoomFunctionality = () => {
246
+ const svg = d3.select("#mermaid-output svg");
247
+ svg.html("<g>" + svg.html() + "</g>");
248
+ const inner = svg.select("g");
249
+ const zoom = d3.zoom().on("zoom", (event) => {
250
+ inner.attr("transform", event.transform);
251
+ });
252
+ svg.call(zoom);
253
+ };
254
+
255
+ const toggleFullscreen = () => {
256
+ const element = document.getElementById('mermaid-output');
257
+ if (!document.fullscreenElement) {
258
+ if (element.requestFullscreen) {
259
+ element.requestFullscreen();
260
+ } else if (element.webkitRequestFullscreen) { /* Safari */
261
+ element.webkitRequestFullscreen();
262
+ } else if (element.msRequestFullscreen) { /* IE11 */
263
+ element.msRequestFullscreen();
264
+ }
265
+ isFullscreen.value = true;
266
+ } else {
267
+ if (document.exitFullscreen) {
268
+ document.exitFullscreen();
269
+ } else if (document.webkitExitFullscreen) { /* Safari */
270
+ document.webkitExitFullscreen();
271
+ } else if (document.msExitFullscreen) { /* IE11 */
272
+ document.msExitFullscreen();
273
+ }
274
+ isFullscreen.value = false;
275
+ }
276
+ };
277
+
278
+ const getRecommendations = async () => {
279
+ try {
280
+ const response = await axios.post(RECOMMENDATIONS_ENDPOINT, {
281
+ user_input: chartDescription.value,
282
+ num_recommendations: 5
283
  });
284
+ recommendations.value = response.data.recommendations;
285
+ showRecommendations.value = true;
286
+ } catch (err) {
287
+ console.error('Error fetching recommendations:', err);
288
+ }
289
  };
290
 
291
+ const debouncedGetRecommendations = debounce(getRecommendations, 300);
292
 
293
+ const onInputFocus = () => {
294
+ if (isFirstFocus.value) {
295
+ debouncedGetRecommendations();
296
+ isFirstFocus.value = false;
297
  }
298
+ };
299
+
300
+ const onInputChange = (event) => {
301
+ const inputType = event.inputType;
302
+ if (inputType === 'insertText' || inputType === 'deleteContentBackward' || inputType === 'deleteContentForward') {
303
+ debouncedGetRecommendations();
304
+ }
305
+ };
306
+
307
+ const onInputBlur = () => {
308
+ setTimeout(() => {
309
+ showRecommendations.value = false;
310
+ }, 200);
311
+ };
312
+
313
+ const selectRecommendation = (recommendation) => {
314
+ chartDescription.value = recommendation;
315
+ showRecommendations.value = false;
316
+ };
317
+
318
+ const downloadChart = async (format) => {
319
+ if (!chartReady.value) {
320
+ console.error('Chart is not ready for download');
321
+ return;
322
+ }
323
+
324
+ const element = document.getElementById('mermaid-output');
325
+ const filename = 'chart';
326
+
327
+ switch (format) {
328
+ case 'png':
329
+ {
330
+ const canvas = await html2canvas(element);
331
+ const pngData = canvas.toDataURL('image/png');
332
+ downloadURI(pngData, `${filename}.png`);
333
+ }
334
+ break;
335
+ case 'svg':
336
+ {
337
+ const svgData = new XMLSerializer().serializeToString(element.querySelector('svg'));
338
+ const svgBlob = new Blob([svgData], {type: 'image/svg+xml;charset=utf-8'});
339
+ const svgUrl = URL.createObjectURL(svgBlob);
340
+ downloadURI(svgUrl, `${filename}.svg`);
341
+ URL.revokeObjectURL(svgUrl);
342
+ }
343
+ break;
344
+ }
345
+ };
346
+
347
+ const downloadURI = (uri, name) => {
348
+ const link = document.createElement('a');
349
+ link.download = name;
350
+ link.href = uri;
351
+ document.body.appendChild(link);
352
+ link.click();
353
+ document.body.removeChild(link);
354
+ };
355
+
356
+ document.addEventListener('fullscreenchange', () => {
357
+ isFullscreen.value = !!document.fullscreenElement;
358
  });
359
 
360
  return {
361
  chartDescription,
 
362
  error,
363
  loading,
364
  isInputDisabled,
365
+ isFullscreen,
366
+ recommendations,
367
+ showRecommendations,
368
  generateChart,
369
+ toggleFullscreen,
370
+ onInputFocus,
371
+ onInputChange,
372
+ onInputBlur,
373
+ selectRecommendation,
374
+ downloadChart
375
  };
376
  }
377
  }).mount('#app');
378
  </script>
379
  </body>
380
+ </html>