Fafnirk commited on
Commit
ec837f4
·
1 Parent(s): c1df21f
Files changed (2) hide show
  1. static/script.js +106 -111
  2. templates/index.html +4 -3
static/script.js CHANGED
@@ -9,6 +9,44 @@ const deleteProjectBtn = document.getElementById("deleteProject");
9
 
10
  let currentAbortController = null;
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  // ---------- Chat message helpers ----------
13
  function makeUserNode(text) {
14
  const node = document.createElement("div");
@@ -33,6 +71,8 @@ function appendAndScroll(node) {
33
  async function refreshProjects() {
34
  const res = await fetch("/projects");
35
  const data = await res.json();
 
 
36
  projectSelect.innerHTML = "";
37
  data.forEach(p => {
38
  const opt = document.createElement("option");
@@ -40,121 +80,88 @@ async function refreshProjects() {
40
  opt.textContent = p;
41
  projectSelect.appendChild(opt);
42
  });
 
 
 
 
 
 
 
 
43
  }
44
 
45
  addProjectBtn.addEventListener("click", async () => {
46
- const name = prompt("New project name:");
47
  if (!name) return;
48
- await fetch("/add_project", {
49
- method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- body: JSON.stringify({ project: name })
52
- });
53
  await refreshProjects();
54
- projectSelect.value = name;
55
  });
56
 
57
  deleteProjectBtn.addEventListener("click", async () => {
58
- const name = projectSelect.value;
59
- if (!name) return;
60
- if (!confirm(`Delete project "${name}"? This cannot be undone.`)) return;
61
- await fetch("/delete_project", {
62
- method: "POST",
63
- headers: { "Content-Type": "application/json" },
64
- body: JSON.stringify({ project: name })
65
- });
66
  await refreshProjects();
67
  });
68
 
69
- // ---------- Chat ----------
70
  sendBtn.addEventListener("click", async () => {
71
- const prompt = promptInput.value.trim();
72
- const project = projectSelect.value || "default";
73
- if (!prompt) return;
 
 
74
 
 
75
  promptInput.value = "";
76
- appendAndScroll(makeUserNode(prompt));
77
  const assistantNode = makeAssistantNode();
78
  appendAndScroll(assistantNode);
79
 
80
- let fullText = "";
81
  currentAbortController = new AbortController();
82
 
83
- let finalPrompt = prompt;
84
-
85
- // --- Web search integration ---
86
- const useWeb = document.getElementById("useWeb").checked;
87
- if (useWeb) {
88
- try {
89
- const res = await fetch("/search_web", {
90
  method: "POST",
91
  headers: { "Content-Type": "application/json" },
92
- body: JSON.stringify({ query: prompt })
93
  });
94
- const data = await res.json();
95
- if (data.results && data.results.length > 0) {
96
- const webText = data.results.slice(0, 5).join("\n- ");
97
- finalPrompt = `${prompt}\n\nWeb search results:\n- ${webText}`;
98
- }
99
- } catch (err) {
100
- console.error("Web search failed:", err);
101
  }
102
- }
103
- // --- End Web search ---
104
-
105
- fetch("/stream", {
106
- method: "POST",
107
- headers: { "Content-Type": "application/json" },
108
- body: JSON.stringify({ message: finalPrompt, project }),
109
- signal: currentAbortController.signal
110
- })
111
- .then(response => {
112
- if (!response.ok) {
113
- assistantNode.textContent = `Error: ${response.status}`;
114
- return;
 
115
  }
116
- const reader = response.body.getReader();
117
- const decoder = new TextDecoder("utf-8");
118
- function readLoop() {
119
- reader.read().then(({ done, value }) => {
120
- if (done) {
121
- hljs.highlightAll();
122
- return;
123
- }
124
- const chunkText = decoder.decode(value, { stream: true });
125
- const parts = chunkText.split("\n");
126
- for (const line of parts) {
127
- if (line.startsWith("data: ")) {
128
- const raw = line.slice(6);
129
-
130
- // 1. Skip empty keep-alive data or the [DONE] signal
131
- if (raw.trim() === "" || raw === "[DONE]") continue;
132
-
133
- if (raw.startsWith("ERROR:")) {
134
- assistantNode.textContent = raw;
135
- continue;
136
- }
137
-
138
- // 2. Decode the token (handle the escaped newlines from app.py)
139
- const formatted = raw.replace(/\\n/g, "\n");
140
- fullText += formatted;
141
-
142
- // 3. Update the UI
143
- try {
144
- // Use marked to parse the markdown as it arrives
145
- assistantNode.innerHTML = `<strong>Assistant:</strong><br>` + marked.parse(fullText);
146
- if (typeof hljs !== 'undefined') hljs.highlightAll();
147
- } catch (e) {
148
- assistantNode.textContent = fullText;
149
- }
150
- chatDiv.scrollTop = chatDiv.scrollHeight;
151
- }
152
- }
153
- readLoop();
154
- });
155
  }
156
- readLoop();
157
- });
 
 
 
 
 
 
 
158
  });
159
 
160
  async function uploadFile(project) {
@@ -163,39 +170,27 @@ async function uploadFile(project) {
163
  alert("Please select a file first.");
164
  return;
165
  }
166
-
167
  const formData = new FormData();
168
  formData.append("file", fileInput.files[0]);
169
 
170
  try {
171
- const res = await fetch(`/upload_file/${project}`, {
172
- method: "POST",
173
- body: formData
174
- });
175
  const data = await res.json();
176
  if (data.status === "ok") {
177
- alert(`Successfully uploaded: ${data.filename}`);
178
- fileInput.value = ""; // Clear the input
179
  } else {
180
- alert(`Upload failed: ${data.error}`);
181
  }
182
  } catch (err) {
183
- console.error("Upload error:", err);
184
  alert("Error uploading file.");
185
  }
186
  }
187
 
188
- // ---------- Controls ----------
189
- stopBtn.addEventListener("click", () => {
190
- if (currentAbortController) {
191
- currentAbortController.abort();
192
- currentAbortController = null;
193
- }
194
- });
195
-
196
- clearBtn.addEventListener("click", () => {
197
- chatDiv.innerHTML = "";
198
  });
199
 
200
- // ---------- Init ----------
201
- refreshProjects();
 
9
 
10
  let currentAbortController = null;
11
 
12
+ // ---------- NEW: Project History Loading ----------
13
+
14
+ async function loadHistory(project) {
15
+ chatDiv.innerHTML = '<div class="message assistant"><em>Loading history...</em></div>';
16
+
17
+ try {
18
+ const res = await fetch(`/history/${project}`);
19
+ const data = await res.json();
20
+
21
+ chatDiv.innerHTML = ""; // Clear loading indicator
22
+
23
+ if (data.history && data.history.length > 0) {
24
+ data.history.forEach(msg => {
25
+ if (msg.role === "user") {
26
+ appendAndScroll(makeUserNode(msg.content));
27
+ } else {
28
+ // Assistant messages need markdown parsing
29
+ const node = makeAssistantNode();
30
+ node.innerHTML = `<strong>Assistant:</strong><br>${marked.parse(msg.content)}`;
31
+ appendAndScroll(node);
32
+ }
33
+ });
34
+ // Re-highlight all code blocks after loading
35
+ if (typeof hljs !== 'undefined') hljs.highlightAll();
36
+ } else {
37
+ chatDiv.innerHTML = '<div class="message assistant"><em>New project started. No history found.</em></div>';
38
+ }
39
+ } catch (err) {
40
+ console.error("Error loading history:", err);
41
+ chatDiv.innerHTML = '<div class="message assistant"><em>Error loading history for this project.</em></div>';
42
+ }
43
+ }
44
+
45
+ // Listen for dropdown changes
46
+ projectSelect.addEventListener("change", () => {
47
+ loadHistory(projectSelect.value);
48
+ });
49
+
50
  // ---------- Chat message helpers ----------
51
  function makeUserNode(text) {
52
  const node = document.createElement("div");
 
71
  async function refreshProjects() {
72
  const res = await fetch("/projects");
73
  const data = await res.json();
74
+ const currentVal = projectSelect.value;
75
+
76
  projectSelect.innerHTML = "";
77
  data.forEach(p => {
78
  const opt = document.createElement("option");
 
80
  opt.textContent = p;
81
  projectSelect.appendChild(opt);
82
  });
83
+
84
+ // Keep selection if it still exists, otherwise load the first project
85
+ if (data.includes(currentVal)) {
86
+ projectSelect.value = currentVal;
87
+ } else if (data.length > 0) {
88
+ projectSelect.value = data[0];
89
+ loadHistory(data[0]); // Load history for the initial project
90
+ }
91
  }
92
 
93
  addProjectBtn.addEventListener("click", async () => {
94
+ const name = prompt("Project Name:");
95
  if (!name) return;
96
+ await fetch(`/add_project/${name}`, { method: "POST" });
 
 
 
 
97
  await refreshProjects();
 
98
  });
99
 
100
  deleteProjectBtn.addEventListener("click", async () => {
101
+ const p = projectSelect.value;
102
+ if (!p || !confirm(`Delete project ${p}?`)) return;
103
+ await fetch(`/delete_project/${p}`, { method: "DELETE" });
 
 
 
 
 
104
  await refreshProjects();
105
  });
106
 
107
+ // ---------- Core Chat Logic ----------
108
  sendBtn.addEventListener("click", async () => {
109
+ const text = promptInput.value.trim();
110
+ if (!text) return;
111
+
112
+ const project = projectSelect.value;
113
+ const useWeb = document.getElementById("useWeb").checked;
114
 
115
+ appendAndScroll(makeUserNode(text));
116
  promptInput.value = "";
117
+
118
  const assistantNode = makeAssistantNode();
119
  appendAndScroll(assistantNode);
120
 
 
121
  currentAbortController = new AbortController();
122
 
123
+ try {
124
+ let search_results = [];
125
+ if (useWeb) {
126
+ assistantNode.innerHTML = `<strong>Assistant:</strong><br><em>Searching web...</em>`;
127
+ const sResp = await fetch("/search_web", {
 
 
128
  method: "POST",
129
  headers: { "Content-Type": "application/json" },
130
+ body: JSON.stringify({ query: text })
131
  });
132
+ const sData = await sResp.json();
133
+ search_results = sData.results || [];
 
 
 
 
 
134
  }
135
+
136
+ const res = await fetch("/chat", {
137
+ method: "POST",
138
+ headers: { "Content-Type": "application/json" },
139
+ body: JSON.stringify({ project, message: text, search_results }),
140
+ signal: currentAbortController.signal
141
+ });
142
+
143
+ const data = await res.json();
144
+ if (data.response) {
145
+ assistantNode.innerHTML = `<strong>Assistant:</strong><br>${marked.parse(data.response)}`;
146
+ if (typeof hljs !== 'undefined') hljs.highlightAll();
147
+ } else if (data.error) {
148
+ assistantNode.textContent = "Error: " + data.error;
149
  }
150
+ } catch (err) {
151
+ if (err.name === 'AbortError') {
152
+ assistantNode.innerHTML += "<br><em>[Stopped]</em>";
153
+ } else {
154
+ assistantNode.textContent = "Error: " + err.message;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
156
+ } finally {
157
+ currentAbortController = null;
158
+ chatDiv.scrollTop = chatDiv.scrollHeight;
159
+ }
160
+ });
161
+
162
+ // Clear UI only (doesn't delete database)
163
+ clearBtn.addEventListener("click", () => {
164
+ chatDiv.innerHTML = "";
165
  });
166
 
167
  async function uploadFile(project) {
 
170
  alert("Please select a file first.");
171
  return;
172
  }
 
173
  const formData = new FormData();
174
  formData.append("file", fileInput.files[0]);
175
 
176
  try {
177
+ const res = await fetch(`/upload_file/${project}`, { method: "POST", body: formData });
 
 
 
178
  const data = await res.json();
179
  if (data.status === "ok") {
180
+ alert(`Uploaded: ${data.filename}`);
181
+ fileInput.value = "";
182
  } else {
183
+ alert(`Failed: ${data.error}`);
184
  }
185
  } catch (err) {
 
186
  alert("Error uploading file.");
187
  }
188
  }
189
 
190
+ // Ctrl+Enter support
191
+ promptInput.addEventListener("keydown", (e) => {
192
+ if (e.ctrlKey && e.key === "Enter") sendBtn.click();
 
 
 
 
 
 
 
193
  });
194
 
195
+ // Initial Init
196
+ refreshProjects();
templates/index.html CHANGED
@@ -33,12 +33,13 @@
33
  </div>
34
  </div>
35
 
36
- <script src="{{ url_for('static', filename='script.js') }}"></script>
37
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
38
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
39
  <script>
 
40
  marked.setOptions({ gfm: true, breaks: true });
41
- hljs.configure({ ignoreUnescapedHTML: true, languages: [] });
42
  </script>
 
 
43
  </body>
44
- </html>
 
33
  </div>
34
  </div>
35
 
 
36
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
37
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
38
  <script>
39
+ // Initialize Markdown and Code Highlighting
40
  marked.setOptions({ gfm: true, breaks: true });
 
41
  </script>
42
+
43
+ <script src="{{ url_for('static', filename='script.js') }}"></script>
44
  </body>
45
+ </html>