PageSourceSearch

https://jayreddinjr-jr-odysseus-hf.hf.space/static/js/rag.js

js jayreddinjr-jr-odysseus-hf.hf.space collected 2026-10-04 16:59:11 UTC 4,991 bytes, 177 lines download raw bytes

1// static/js/rag.js
2
3/**
4 * RAG (Retrieval Augmented Generation) management
5 */
6
7import uiModule from './ui.js';
8import spinnerModule from './spinner.js';
9
10let API_BASE = '';
11
12export function init(apiBase) {
13  API_BASE = apiBase;
14  _setupUploadZone();
15}
16
17function _humanSize(bytes) {
18  if (bytes < 1024) return bytes + ' B';
19  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
20  return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
21}
22
23/**
24 * Load and display RAG documents with delete buttons
25 */
26export async function loadPersonalDocs() {
27  const box = document.getElementById('docs-view');
28  if (!box) return;
29
30  box.innerHTML = '';
31  const { element: wpEl } = spinnerModule.createWhirlpool(24);
32  wpEl.title = 'Loading…';
33  box.appendChild(wpEl);
34
35  try {
36    const res = await fetch(`${API_BASE}/api/personal`, { credentials: 'same-origin' });
37    const data = await res.json();
38    const files = data.files || [];
39
40    box.innerHTML = '';
41
42    if (files.length === 0) {
43      const placeholder = document.createElement('div');
44      placeholder.textContent = 'Drop files above to add to RAG';
45      placeholder.style.cssText = 'color:var(--color-muted);font-size:12px;padding:4px 0;';
46      box.appendChild(placeholder);
47      return;
48    }
49
50    files.forEach(f => {
51      const row = document.createElement('div');
52      row.className = 'list-item';
53      row.style.cssText = 'display:flex;align-items:center;gap:4px;';
54
55      const name = document.createElement('span');
56      name.className = 'grow';
57      name.textContent = f.name.split('/').pop();
58      name.title = f.path || f.name;
59      name.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
60      row.appendChild(name);
61
62      const size = document.createElement('span');
63      size.style.cssText = 'color:var(--color-muted);font-size:11px;flex-shrink:0;';
64      size.textContent = _humanSize(f.size);
65      row.appendChild(size);
66
67      const del = document.createElement('button');
68      del.className = 'rag-file-delete';
69      del.textContent = 'x';
70      del.title = 'Remove from RAG';
71      del.style.cssText = 'background:none;border:none;color:var(--color-error);cursor:pointer;padding:2px 4px;font-size:12px;flex-shrink:0;';
72      del.addEventListener('click', (e) => {
73        e.stopPropagation();
74        _deleteFile(f.path || f.name, f.name.split('/').pop());
75      });
76      row.appendChild(del);
77
78      box.appendChild(row);
79    });
80  } catch (e) {
81    console.error(e);
82    box.innerHTML = '';
83    const error = document.createElement('div');
84    error.textContent = 'Failed to load files';
85    error.style.color = 'var(--color-error)';
86    box.appendChild(error);
87  }
88}
89
90async function _deleteFile(filepath, displayName) {
91  if (!await uiModule.styledConfirm(`Remove "${displayName}" from RAG?`, { confirmText: 'Remove', danger: true })) return;
92  try {
93    const res = await fetch(`${API_BASE}/api/personal/file?filepath=${encodeURIComponent(filepath)}`, {
94      method: 'DELETE',
95      credentials: 'same-origin'
96    });
97    if (!res.ok) throw new Error(await res.text());
98    await loadPersonalDocs();
99  } catch (e) {
100    console.error('Delete failed:', e);
101    alert('Failed to delete file: ' + e.message);
102  }
103}
104
105/**
106 * Upload files to RAG
107 */
108export async function uploadRagFiles(fileList) {
109  if (!fileList || !fileList.length) return;
110
111  const zone = document.getElementById('rag-upload-zone');
112  if (zone) zone.textContent = 'Uploading…';
113
114  const fd = new FormData();
115  for (const file of fileList) {
116    fd.append('files', file);
117  }
118
119  try {
120    const res = await fetch(`${API_BASE}/api/personal/upload`, {
121      method: 'POST',
122      credentials: 'same-origin',
123      body: fd
124    });
125
126    if (!res.ok) throw new Error(await res.text());
127
128    const data = await res.json();
129    if (zone) zone.textContent = 'Drop files here or click to upload';
130    await loadPersonalDocs();
131    return data;
132  } catch (e) {
133    console.error('Upload failed:', e);
134    if (zone) zone.textContent = 'Drop files here or click to upload';
135    alert('Upload failed: ' + e.message);
136  }
137}
138
139function _setupUploadZone() {
140  const zone = document.getElementById('rag-upload-zone');
141  const input = document.getElementById('rag-file-input');
142  if (!zone || !input) return;
143
144  zone.addEventListener('click', () => input.click());
145
146  zone.addEventListener('dragover', (e) => {
147    e.preventDefault();
148    zone.classList.add('dragover');
149  });
150
151  zone.addEventListener('dragleave', () => {
152    zone.classList.remove('dragover');
153  });
154
155  zone.addEventListener('drop', (e) => {
156    e.preventDefault();
157    zone.classList.remove('dragover');
158    if (e.dataTransfer.files.length) {
159      uploadRagFiles(e.dataTransfer.files);
160    }
161  });
162
163  input.addEventListener('change', () => {
164    if (input.files.length) {
165      uploadRagFiles(input.files);
166      input.value = '';
167    }
168  });
169}
170
171const ragModule = {
172  init,
173  loadPersonalDocs,
174  uploadRagFiles
175};
176
177export default ragModule;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.