Spaces:
Running
Running
Update index.html
Browse files- index.html +149 -26
index.html
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8" />
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
-
<title>
|
7 |
<style>
|
8 |
/* --- Reset and Global Styles --- */
|
9 |
* {
|
@@ -55,22 +55,47 @@
|
|
55 |
}
|
56 |
|
57 |
.input-section h1 {
|
58 |
-
margin-bottom:
|
59 |
font-size: 20px;
|
60 |
font-weight: 500;
|
61 |
}
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
#jsonInput {
|
64 |
width: 100%;
|
65 |
-
|
66 |
-
padding: 10px;
|
67 |
border: 1px solid #ccc;
|
68 |
border-radius: 2px;
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
font-family: monospace;
|
70 |
font-size: 12px;
|
71 |
resize: vertical;
|
72 |
}
|
73 |
|
|
|
74 |
#parseBtn {
|
75 |
margin-top: 8px;
|
76 |
padding: 8px 16px;
|
@@ -80,12 +105,19 @@
|
|
80 |
border-radius: 2px;
|
81 |
cursor: pointer;
|
82 |
font-size: 13px;
|
|
|
83 |
}
|
84 |
|
|
|
85 |
#parseBtn:hover {
|
86 |
background: #555;
|
87 |
}
|
88 |
|
|
|
|
|
|
|
|
|
|
|
89 |
.error {
|
90 |
color: #d73a49;
|
91 |
margin-top: 8px;
|
@@ -238,12 +270,31 @@
|
|
238 |
<div class="app-container">
|
239 |
<div class="sidebar">
|
240 |
<div class="input-section">
|
241 |
-
<h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
242 |
<textarea
|
243 |
id="jsonInput"
|
244 |
-
placeholder="Paste
|
245 |
></textarea>
|
246 |
-
<button id="parseBtn">Parse</button>
|
|
|
247 |
<div id="error" class="error"></div>
|
248 |
</div>
|
249 |
<div class="header-info" id="headerInfo">
|
@@ -259,29 +310,110 @@
|
|
259 |
</div>
|
260 |
|
261 |
<script>
|
262 |
-
|
263 |
-
|
264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
265 |
|
266 |
-
function
|
267 |
-
const
|
268 |
-
|
269 |
-
|
270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
// Reset view on each parse attempt
|
273 |
errorDiv.style.display = "none";
|
274 |
headerInfo.style.display = "none";
|
275 |
mainContent.style.display = "none";
|
276 |
|
277 |
-
if (!
|
278 |
errorDiv.textContent = "Input cannot be empty.";
|
279 |
errorDiv.style.display = "block";
|
280 |
return;
|
281 |
}
|
282 |
|
283 |
try {
|
284 |
-
const data = JSON.parse(
|
285 |
// Show the components on successful parse
|
286 |
headerInfo.style.display = "block";
|
287 |
mainContent.style.display = "flex"; // Use flex as it's a flex container
|
@@ -331,7 +463,6 @@
|
|
331 |
}
|
332 |
|
333 |
function renderChat(chatParts, experience) {
|
334 |
-
const chatArea = document.getElementById("chatArea");
|
335 |
chatArea.innerHTML = "";
|
336 |
|
337 |
chatParts.forEach((part) => {
|
@@ -407,14 +538,6 @@
|
|
407 |
content = content.replace(/\n/g, "<br>");
|
408 |
return content;
|
409 |
}
|
410 |
-
|
411 |
-
document
|
412 |
-
.getElementById("jsonInput")
|
413 |
-
.addEventListener("keydown", function (e) {
|
414 |
-
if (e.ctrlKey && e.key === "Enter") {
|
415 |
-
parseConversation();
|
416 |
-
}
|
417 |
-
});
|
418 |
</script>
|
419 |
</body>
|
420 |
</html>
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8" />
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>SOC Visualizer</title>
|
7 |
<style>
|
8 |
/* --- Reset and Global Styles --- */
|
9 |
* {
|
|
|
55 |
}
|
56 |
|
57 |
.input-section h1 {
|
58 |
+
margin-bottom: 16px;
|
59 |
font-size: 20px;
|
60 |
font-weight: 500;
|
61 |
}
|
62 |
|
63 |
+
.input-section h2 {
|
64 |
+
font-size: 14px;
|
65 |
+
font-weight: 500;
|
66 |
+
margin-top: 16px;
|
67 |
+
margin-bottom: 8px;
|
68 |
+
color: #555;
|
69 |
+
}
|
70 |
+
|
71 |
+
.input-group {
|
72 |
+
margin-bottom: 12px;
|
73 |
+
}
|
74 |
+
|
75 |
+
.input-group label {
|
76 |
+
display: block;
|
77 |
+
font-size: 13px;
|
78 |
+
margin-bottom: 4px;
|
79 |
+
}
|
80 |
+
|
81 |
+
.input-group select,
|
82 |
#jsonInput {
|
83 |
width: 100%;
|
84 |
+
padding: 8px;
|
|
|
85 |
border: 1px solid #ccc;
|
86 |
border-radius: 2px;
|
87 |
+
font-size: 13px;
|
88 |
+
background-color: #fff;
|
89 |
+
}
|
90 |
+
|
91 |
+
#jsonInput {
|
92 |
+
height: 120px;
|
93 |
font-family: monospace;
|
94 |
font-size: 12px;
|
95 |
resize: vertical;
|
96 |
}
|
97 |
|
98 |
+
#loadBtn,
|
99 |
#parseBtn {
|
100 |
margin-top: 8px;
|
101 |
padding: 8px 16px;
|
|
|
105 |
border-radius: 2px;
|
106 |
cursor: pointer;
|
107 |
font-size: 13px;
|
108 |
+
width: 100%;
|
109 |
}
|
110 |
|
111 |
+
#loadBtn:hover,
|
112 |
#parseBtn:hover {
|
113 |
background: #555;
|
114 |
}
|
115 |
|
116 |
+
#loadBtn:disabled {
|
117 |
+
background: #ccc;
|
118 |
+
cursor: not-allowed;
|
119 |
+
}
|
120 |
+
|
121 |
.error {
|
122 |
color: #d73a49;
|
123 |
margin-top: 8px;
|
|
|
270 |
<div class="app-container">
|
271 |
<div class="sidebar">
|
272 |
<div class="input-section">
|
273 |
+
<h1>SOC Visualizer</h1>
|
274 |
+
|
275 |
+
<div class="input-group">
|
276 |
+
<label for="fileSelector">1. Select a file</label>
|
277 |
+
<select id="fileSelector">
|
278 |
+
<option value="">-- Choose a file --</option>
|
279 |
+
</select>
|
280 |
+
</div>
|
281 |
+
|
282 |
+
<div class="input-group">
|
283 |
+
<label for="lineSelector">2. Select a conversation</label>
|
284 |
+
<select id="lineSelector" disabled></select>
|
285 |
+
</div>
|
286 |
+
|
287 |
+
<button id="loadBtn" disabled>Load Conversation</button>
|
288 |
+
|
289 |
+
<hr style="margin: 24px 0" />
|
290 |
+
|
291 |
+
<h2>Or Paste Manually</h2>
|
292 |
<textarea
|
293 |
id="jsonInput"
|
294 |
+
placeholder="Paste a single JSON conversation object here..."
|
295 |
></textarea>
|
296 |
+
<button id="parseBtn">Parse Manual Input</button>
|
297 |
+
|
298 |
<div id="error" class="error"></div>
|
299 |
</div>
|
300 |
<div class="header-info" id="headerInfo">
|
|
|
310 |
</div>
|
311 |
|
312 |
<script>
|
313 |
+
// --- VIRTUAL FILE SYSTEM ---
|
314 |
+
// In a real application, you would fetch these files.
|
315 |
+
// For this tool, simply add your JSONL file content here.
|
316 |
+
// Each key is a filename, and the value is the file's content as a string.
|
317 |
+
// Each line in the string must be a complete, valid JSON object.
|
318 |
+
const virtualFiles = {
|
319 |
+
"soc_sample_1.jsonl": `{ "experience": { "persona1": { "id": "p1", "name": "Alex", "age": 28, "traits": ["tech-savvy", "impatient", "witty"], "background": "Software developer who loves sci-fi movies.", "chatting_style": "Uses short sentences and a lot of tech jargon." }, "persona2": { "id": "p2", "name": "Ben", "age": 30, "traits": ["patient", "thoughtful", "curious"], "background": "Librarian who enjoys classic literature.", "chatting_style": "Writes in full paragraphs and asks many questions." }, "relationship": "Friends", "situation": "Discussing a new movie they both saw.", "topic": "Movie review" }, "chat_parts": [ { "sender": "p1", "messages": ["Did you see 'Galaxy Runners'? The CGI was insane."] }, { "sender": "p2", "messages": ["I did! I found the plot to be a bit derivative of older films, though. What did you think of the story itself?"] }, { "sender": "p1", "messages": ["Story? lol who cares. The FTL jumps looked amazing.", "And that scene with the alien armada... <gif>mind_blown.gif</gif>"] }, { "sender": "p2", "messages": ["Haha, fair enough. The visual spectacle was certainly its strong suit. I just wish they had spent more time developing the main character's motivations."] } ] }
|
320 |
+
{ "experience": { "persona1": { "id": "p1", "name": "Chloe", "age": 22, "traits": ["energetic", "loves food", "uses emojis"], "background": "Culinary student exploring new cafes.", "chatting_style": "Very casual, lots of emojis and slang." }, "persona2": { "id": "p2", "name": "David", "age": 24, "traits": ["calm", "methodical", "minimalist"], "background": "Architecture student who prefers quiet places.", "chatting_style": "Concise and to the point." }, "relationship": "Siblings", "situation": "Making plans for the weekend.", "topic": "Weekend plans" }, "chat_parts": [ { "sender": "p1", "messages": ["heyyyy!! what r u up to this weekend? π€©"] }, { "sender": "p2", "messages": ["Not much. Probably finishing up my model for studio."] }, { "sender": "p1", "messages": ["omg nooo you have to come with me to this new brunch place downtown! π₯π§π₯", "the pics look amazing!! <image>brunch_spread.jpg</image>"] }, { "sender": "p2", "messages": ["Hah, looks intense. Maybe. What time?"] }, { "sender": "p1", "messages": ["11am! be there or be square! π"] } ] }`,
|
321 |
+
"soc_sample_2.jsonl": `{ "experience": { "persona1": { "id": "p1", "name": "Sarah", "age": 45, "traits": ["organized", "caring", "formal"], "background": "Project manager and mother of two.", "chatting_style": "Proper grammar and punctuation, plans everything." }, "persona2": { "id": "p2", "name": "Tom", "age": 46, "traits": ["forgetful", "easy-going", "humorous"], "background": "Graphic designer who works from home.", "chatting_style": "Casual, uses ellipses, often cracks jokes." }, "relationship": "Married Couple", "situation": "Coordinating grocery shopping.", "topic": "Groceries" }, "chat_parts": [ { "sender": "p1", "messages": ["Hi honey, I'm heading to the store after work. I've shared the updated grocery list with you. Please take a look."] }, { "sender": "p2", "messages": ["Hey! ok will check... pretty sure we still have milk tho?"] }, { "sender": "p1", "messages": ["The kids finished it this morning. Please double-check the list for quantities. I don't want to forget the baking soda for Maya's science project again."] }, { "sender": "p2", "messages": ["oops right! science fair volcano... classic.", "got it. list looks good. grab me some of those fancy chips? π", "the sea salt and vinegar ones"] } ] }`,
|
322 |
+
};
|
323 |
+
// --- END VIRTUAL FILE SYSTEM ---
|
324 |
+
|
325 |
+
// --- DOM Elements ---
|
326 |
+
const fileSelector = document.getElementById("fileSelector");
|
327 |
+
const lineSelector = document.getElementById("lineSelector");
|
328 |
+
const loadBtn = document.getElementById("loadBtn");
|
329 |
+
const parseBtn = document.getElementById("parseBtn");
|
330 |
+
const jsonInput = document.getElementById("jsonInput");
|
331 |
+
const errorDiv = document.getElementById("error");
|
332 |
+
const headerInfo = document.getElementById("headerInfo");
|
333 |
+
const mainContent = document.getElementById("mainContent");
|
334 |
+
const chatArea = document.getElementById("chatArea");
|
335 |
+
|
336 |
+
// --- Event Listeners ---
|
337 |
+
document.addEventListener("DOMContentLoaded", initialize);
|
338 |
+
fileSelector.addEventListener("change", handleFileSelection);
|
339 |
+
loadBtn.addEventListener("click", loadSelectedConversation);
|
340 |
+
parseBtn.addEventListener("click", parseManualInput);
|
341 |
+
jsonInput.addEventListener("keydown", function (e) {
|
342 |
+
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
343 |
+
parseManualInput();
|
344 |
+
}
|
345 |
+
});
|
346 |
+
|
347 |
+
// --- Functions ---
|
348 |
+
function initialize() {
|
349 |
+
// Populate the file selector on page load
|
350 |
+
Object.keys(virtualFiles).forEach((filename) => {
|
351 |
+
const option = document.createElement("option");
|
352 |
+
option.value = filename;
|
353 |
+
option.textContent = filename;
|
354 |
+
fileSelector.appendChild(option);
|
355 |
+
});
|
356 |
+
}
|
357 |
|
358 |
+
function handleFileSelection() {
|
359 |
+
const selectedFile = fileSelector.value;
|
360 |
+
lineSelector.innerHTML = ""; // Clear previous options
|
361 |
+
if (selectedFile) {
|
362 |
+
const content = virtualFiles[selectedFile];
|
363 |
+
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
364 |
+
|
365 |
+
lines.forEach((line, index) => {
|
366 |
+
const option = document.createElement("option");
|
367 |
+
option.value = index;
|
368 |
+
// Try to parse to get a topic for a better label
|
369 |
+
try {
|
370 |
+
const data = JSON.parse(line);
|
371 |
+
option.textContent = `Conversation ${index + 1}: ${data.experience.topic}`;
|
372 |
+
} catch (e) {
|
373 |
+
option.textContent = `Conversation ${index + 1}`;
|
374 |
+
}
|
375 |
+
lineSelector.appendChild(option);
|
376 |
+
});
|
377 |
+
|
378 |
+
lineSelector.disabled = false;
|
379 |
+
loadBtn.disabled = false;
|
380 |
+
} else {
|
381 |
+
lineSelector.disabled = true;
|
382 |
+
loadBtn.disabled = true;
|
383 |
+
}
|
384 |
+
}
|
385 |
|
386 |
+
function loadSelectedConversation() {
|
387 |
+
const fileName = fileSelector.value;
|
388 |
+
const lineIndex = lineSelector.value;
|
389 |
+
|
390 |
+
if (fileName && lineIndex !== null) {
|
391 |
+
const content = virtualFiles[fileName];
|
392 |
+
const lines = content.split("\n");
|
393 |
+
const jsonString = lines[lineIndex];
|
394 |
+
processAndRender(jsonString);
|
395 |
+
}
|
396 |
+
}
|
397 |
+
|
398 |
+
function parseManualInput() {
|
399 |
+
const jsonString = jsonInput.value.trim();
|
400 |
+
processAndRender(jsonString);
|
401 |
+
}
|
402 |
+
|
403 |
+
function processAndRender(jsonString) {
|
404 |
// Reset view on each parse attempt
|
405 |
errorDiv.style.display = "none";
|
406 |
headerInfo.style.display = "none";
|
407 |
mainContent.style.display = "none";
|
408 |
|
409 |
+
if (!jsonString) {
|
410 |
errorDiv.textContent = "Input cannot be empty.";
|
411 |
errorDiv.style.display = "block";
|
412 |
return;
|
413 |
}
|
414 |
|
415 |
try {
|
416 |
+
const data = JSON.parse(jsonString);
|
417 |
// Show the components on successful parse
|
418 |
headerInfo.style.display = "block";
|
419 |
mainContent.style.display = "flex"; // Use flex as it's a flex container
|
|
|
463 |
}
|
464 |
|
465 |
function renderChat(chatParts, experience) {
|
|
|
466 |
chatArea.innerHTML = "";
|
467 |
|
468 |
chatParts.forEach((part) => {
|
|
|
538 |
content = content.replace(/\n/g, "<br>");
|
539 |
return content;
|
540 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
541 |
</script>
|
542 |
</body>
|
543 |
</html>
|