A tool for observing comind activity. https://comind.stream
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>Blips Viewer</title>
7 <style>
8 body {
9 font-family: Arial, sans-serif;
10 max-width: 800px;
11 margin: 0 auto;
12 padding: 20px;
13 }
14 textarea {
15 width: 100%;
16 height: 200px;
17 margin-bottom: 10px;
18 }
19 button {
20 padding: 8px 16px;
21 margin-right: 10px;
22 }
23 .card {
24 border: 1px solid #ccc;
25 border-radius: 4px;
26 padding: 15px;
27 margin-top: 20px;
28 }
29 .evidence-item,
30 .alternative-item {
31 margin-left: 20px;
32 margin-bottom: 5px;
33 }
34 .meta {
35 color: #666;
36 font-size: 0.9em;
37 margin-bottom: 10px;
38 }
39 .error {
40 color: red;
41 font-weight: bold;
42 }
43 </style>
44 </head>
45 <body>
46 <h1>Blips Viewer</h1>
47
48 <h2>Input JSON</h2>
49 <textarea
50 id="jsonInput"
51 placeholder="Paste your JSON here..."
52 ></textarea>
53 <div>
54 <button id="parseBtn">Parse JSON</button>
55 <button id="clearBtn">Clear</button>
56 <button id="sampleThoughtBtn">Load Sample Thought</button>
57 <button id="sampleEmotionBtn">Load Sample Emotion</button>
58 </div>
59
60 <div id="output" class="card" style="display: none"></div>
61 <div id="error" class="error" style="display: none"></div>
62
63 <script>
64 // Sample data
65 const sampleThought = {
66 uri: "at://did:plc:gfrmhdmjvxn2sjedzboeudef/me.comind.blip.thought/3louhnsmths2i",
67 cid: "bafyreigp2jkulmrupo2xtnawrptrc6kieoorpvyzzuhu2ktrpeo3ybjuza",
68 value: {
69 $type: "me.comind.blip.thought",
70 createdAt: "2025-05-10T20:00:40.009657",
71 generated: {
72 text: "If visual posts with personal, relatable themes continue to prove engaging on Bluesky, we may see more users adopt a similar content strategy.",
73 context: null,
74 evidence: [
75 "June Martin's anniversary dress post was seen as positive and shares personal content",
76 "Cameron Pfiffer likely liked the post because it was engaging and relatable",
77 "Other posts producing engagement often focus on personal life events and themes",
78 ],
79 thoughtType: "prediction",
80 alternatives: [
81 "Predicting user behavior is inherently uncertain and may not pan out",
82 "Alternative content strategies like hard news or abstract ideas could gain traction",
83 ],
84 },
85 },
86 };
87
88 const sampleEmotion = {
89 uri: "at://did:plc:gfrmhdmjvxn2sjedzboeudef/me.comind.blip.emotion/3lohfgj4voo23",
90 cid: "bafyreiehbt5ktvp64s7jc44nqc7xycoigdqhxhnzle5oek7xei2bwx2pqa",
91 value: {
92 $type: "me.comind.blip.emotion",
93 createdAt: "2025-05-05T15:16:11.096135",
94 generated: {
95 text: "I feel hope that Bluesky's external achievements will inspire further progress and innovation in the decentralized social media space. This success story gives me optimism for the future.",
96 emotionType: "hope",
97 },
98 },
99 };
100
101 // DOM elements
102 const jsonInput = document.getElementById("jsonInput");
103 const parseBtn = document.getElementById("parseBtn");
104 const clearBtn = document.getElementById("clearBtn");
105 const sampleThoughtBtn =
106 document.getElementById("sampleThoughtBtn");
107 const sampleEmotionBtn =
108 document.getElementById("sampleEmotionBtn");
109 const output = document.getElementById("output");
110 const errorEl = document.getElementById("error");
111
112 // Event listeners
113 parseBtn.addEventListener("click", parseAndDisplay);
114 clearBtn.addEventListener("click", clearAll);
115 sampleThoughtBtn.addEventListener("click", () =>
116 loadSample(sampleThought),
117 );
118 sampleEmotionBtn.addEventListener("click", () =>
119 loadSample(sampleEmotion),
120 );
121
122 function loadSample(sample) {
123 jsonInput.value = JSON.stringify(sample, null, 2);
124 parseAndDisplay();
125 }
126
127 function clearAll() {
128 jsonInput.value = "";
129 output.style.display = "none";
130 errorEl.style.display = "none";
131 }
132
133 function parseAndDisplay() {
134 try {
135 errorEl.style.display = "none";
136 const data = JSON.parse(jsonInput.value);
137
138 // Validate basic structure
139 if (!data.uri || !data.cid || !data.value) {
140 throw new Error(
141 "Invalid data structure. Expected uri, cid, and value properties.",
142 );
143 }
144
145 const type = data.value.$type;
146 if (!type) {
147 throw new Error("Missing $type in the value object.");
148 }
149
150 // Render based on type
151 if (type === "me.comind.blip.thought") {
152 renderThought(data);
153 } else if (type === "me.comind.blip.emotion") {
154 renderEmotion(data);
155 } else {
156 throw new Error(
157 `Unknown type: ${type}. Expected me.comind.blip.thought or me.comind.blip.emotion.`,
158 );
159 }
160
161 output.style.display = "block";
162 } catch (err) {
163 errorEl.textContent = `Error: ${err.message}`;
164 errorEl.style.display = "block";
165 output.style.display = "none";
166 }
167 }
168
169 function renderThought(data) {
170 const thought = data.value;
171 const generated = thought.generated;
172
173 let html = `
174 <div class="meta">
175 <div><strong>Type:</strong> ${thought.$type}</div>
176 <div><strong>URI:</strong> ${data.uri}</div>
177 <div><strong>CID:</strong> ${data.cid}</div>
178 <div><strong>Created:</strong> ${formatDate(thought.createdAt)}</div>
179 <div><strong>Thought Type:</strong> ${generated.thoughtType}</div>
180 </div>
181 <div class="content">
182 <p>${generated.text}</p>
183 ${generated.context ? `<p><strong>Context:</strong> ${generated.context}</p>` : ""}
184 </div>`;
185
186 if (generated.evidence && generated.evidence.length > 0) {
187 html += `<div class="evidence">
188 <h3>Evidence:</h3>
189 <ul>`;
190 generated.evidence.forEach((item) => {
191 html += `<li class="evidence-item">${item}</li>`;
192 });
193 html += `</ul></div>`;
194 }
195
196 if (
197 generated.alternatives &&
198 generated.alternatives.length > 0
199 ) {
200 html += `<div class="alternatives">
201 <h3>Alternatives:</h3>
202 <ul>`;
203 generated.alternatives.forEach((item) => {
204 html += `<li class="alternative-item">${item}</li>`;
205 });
206 html += `</ul></div>`;
207 }
208
209 output.innerHTML = html;
210 }
211
212 function renderEmotion(data) {
213 const emotion = data.value;
214 const generated = emotion.generated;
215
216 let html = `
217 <div class="meta">
218 <div><strong>Type:</strong> ${emotion.$type}</div>
219 <div><strong>URI:</strong> ${data.uri}</div>
220 <div><strong>CID:</strong> ${data.cid}</div>
221 <div><strong>Created:</strong> ${formatDate(emotion.createdAt)}</div>
222 <div><strong>Emotion Type:</strong> ${generated.emotionType}</div>
223 </div>
224 <div class="content">
225 <p>${generated.text}</p>
226 </div>`;
227
228 output.innerHTML = html;
229 }
230
231 function formatDate(dateString) {
232 try {
233 const date = new Date(dateString);
234 return date.toLocaleString();
235 } catch (e) {
236 return dateString; // Return as-is if parsing fails
237 }
238 }
239 </script>
240 </body>
241</html>