File size: 7,983 Bytes
1f725d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/* ============================================================
   blog.js  –  Blog page logic.
   Requires: constants.js (loaded before this script)
   ============================================================ */

// ── State ─────────────────────────────────────────────────
let currentBlogData = null;
let isGenerating = false;

// ── Initialize marked and highlight.js ───────────────────
marked.setOptions({
  highlight: function (code, lang) {
    if (lang && hljs.getLanguage(lang)) {
      return hljs.highlight(code, { language: lang }).value;
    }
    return hljs.highlightAuto(code).value;
  },
  breaks: true,
});

// ── History ───────────────────────────────────────────────
async function loadHistory() {
  const res = await fetch(ROUTES.BLOGS_LIST, {
    headers: AUTH_HEADERS(),
  });
  const blogs = await res.json();
  const container = document.getElementById("history-container");
  container.innerHTML = "";

  blogs.forEach((title) => {
    const div = document.createElement("div");
    div.className = "history-item";
    div.textContent = title;
    div.onclick = () => loadBlog(title);
    container.appendChild(div);
  });
}

async function loadBlog(title) {
  if (isGenerating) return;

  const res = await fetch(ROUTES.BLOG_GET(title), {
    headers: AUTH_HEADERS(),
  });
  if (!res.ok) return;

  const data = await res.json();
  currentBlogData = {
    topic: title,
    plan: { blog_title: title },
    final: data.content,
  };

  displayBlog(data.content);
  document.getElementById("blog-title-display").textContent = title;

  // Mark as active in sidebar
  document.querySelectorAll(".history-item").forEach((item) => {
    item.classList.toggle("active", item.textContent === title);
  });

  // Enable actions
  document.getElementById("download-btn").disabled = false;
  document.getElementById("delete-btn").disabled = false;
}

// ── Display ───────────────────────────────────────────────
function displayBlog(markdown) {
  const blogContent = document.getElementById("blog-content");
  const welcome = document.getElementById("welcome-screen");
  const pipeline = document.getElementById("pipeline-status");

  welcome.style.display = "none";
  pipeline.style.display = "none";
  blogContent.style.display = "block";

  // Rewrite image paths to use the /blog/images static mount if they are internal
  const processedMd = markdown.replace(/\(\.\.\/images\//g, "(/blog/images/");

  blogContent.innerHTML = marked.parse(processedMd);
  hljs.highlightAll();
}

function startNewBlog() {
  if (isGenerating) return;

  currentBlogData = null;
  document.getElementById("welcome-screen").style.display = "flex";
  document.getElementById("blog-content").style.display = "none";
  document.getElementById("pipeline-status").style.display = "none";
  document.getElementById("blog-title-display").textContent = "Blog Overview";
  document.getElementById("topic-input").value = "";
  document.getElementById("download-btn").disabled = true;
  document.getElementById("delete-btn").disabled = true;

  document.querySelectorAll(".history-item").forEach((item) => {
    item.classList.remove("active");
  });
}

function appendStatus(msg) {
  const pipeline = document.getElementById("pipeline-status");
  pipeline.style.display = "block";
  const line = document.createElement("div");
  line.className = "status-line";
  line.innerHTML = `<span style="color: #6ee7b7;">[${new Date().toLocaleTimeString()}]</span> ${msg}`;
  pipeline.appendChild(line);
  pipeline.scrollTop = pipeline.scrollHeight;
}

// ── Generation ────────────────────────────────────────────
function startGeneration() {
  const topicInput = document.getElementById("topic-input");
  const topic = topicInput.value.trim();
  if (!topic || isGenerating) return;

  isGenerating = true;
  setLoading(true);

  document.getElementById("welcome-screen").style.display = "none";
  document.getElementById("blog-content").style.display = "none";
  const pipeline = document.getElementById("pipeline-status");
  pipeline.innerHTML = "";
  pipeline.style.display = "block";

  appendStatus(`Initializing generation for topic: "${topic}"...`);

  // WebSocket – pass user_id as a query param since WS doesn't support custom headers
  const wsUrl = new URL(ROUTES.BLOG_WS());
  wsUrl.searchParams.set("user_id", getUserId());
  const ws = new WebSocket(wsUrl.toString());

  ws.onopen = () => {
    ws.send(JSON.stringify({ topic }));
  };

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);

    if (data.status === "completed") {
      appendStatus("Done! Finalizing blog post...");
    } else if (data.final) {
      currentBlogData = data;
      displayBlog(data.final);
      loadHistory();
      setLoading(false);
      isGenerating = false;
      document.getElementById("download-btn").disabled = false;
      document.getElementById("delete-btn").disabled = false;
      if (data.plan && data.plan.blog_title) {
        document.getElementById("blog-title-display").textContent =
          data.plan.blog_title;
      }
    } else if (data.error) {
      appendStatus(`<span style="color: #f87171;">ERROR: ${data.error}</span>`);
      setLoading(false);
      isGenerating = false;
    } else {
      if (data.sections && data.sections.length > 0) {
        const lastSection = data.sections[data.sections.length - 1];
        appendStatus(
          `Generated section: ${lastSection[1].split("\n")[0].replace("## ", "")}`
        );
      } else if (data.plan) {
        appendStatus(
          `Plan created: "${data.plan.blog_title}" with ${data.plan.tasks.length} sections.`
        );
      } else if (data.queries && data.queries.length > 0) {
        appendStatus(`Researching: ${data.queries.join(", ")}`);
      }
    }
  };

  ws.onerror = () => {
    appendStatus(`<span style="color: #f87171;">Connection error.</span>`);
    setLoading(false);
    isGenerating = false;
  };

  ws.onclose = () => {
    if (isGenerating) {
      appendStatus("Connection closed.");
      setLoading(false);
      isGenerating = false;
    }
  };
}

function setLoading(loading) {
  const btn = document.getElementById("generate-btn");
  if (loading) {
    btn.disabled = true;
    btn.innerHTML = '<div class="spinner"></div>';
  } else {
    btn.disabled = false;
    btn.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polyline points="22 2 15 22 11 13 2 9 22 2"/></svg>`;
  }
}

// ── CRUD Actions ──────────────────────────────────────────
async function deleteCurrentBlog() {
  if (
    !currentBlogData ||
    !confirm("Are you sure you want to delete this blog and all related images?")
  )
    return;

  const res = await fetch(ROUTES.BLOG_DELETE, {
    method: "DELETE",
    headers: AUTH_HEADERS(),
    body: JSON.stringify({ data: currentBlogData }),
  });

  if (res.ok) {
    startNewBlog();
    loadHistory();
  } else {
    alert("Failed to delete blog.");
  }
}

function downloadCurrentBlog() {
  if (!currentBlogData) return;
  const title = currentBlogData.plan
    ? currentBlogData.plan.blog_title
    : currentBlogData.topic;
  window.location.href = ROUTES.BLOG_DOWNLOAD(title);
}

// ── Bootstrap ─────────────────────────────────────────────
loadHistory();