devjhawar commited on
Commit
6d03b1a
·
verified ·
1 Parent(s): ee47272

Upload folder using huggingface_hub

Browse files
api/routes/query.py CHANGED
@@ -62,7 +62,6 @@ async def stream_query_policy(body: QueryRequest, request: Request):
62
  yield token
63
  except Exception as exc:
64
  logger.exception("QueryService.stream_query failed")
65
- import json
66
- yield f"data: {json.dumps({'type': 'error', 'content': str(exc)})}\n\n"
67
 
68
- return StreamingResponse(content_generator(), media_type="text/event-stream")
 
62
  yield token
63
  except Exception as exc:
64
  logger.exception("QueryService.stream_query failed")
65
+ yield f"Error: {str(exc)}"
 
66
 
67
+ return StreamingResponse(content_generator(), media_type="text/plain")
backend/src/routes/query.js CHANGED
@@ -40,37 +40,12 @@ router.post('/query/stream', authMiddleware, async (req, res) => {
40
  question, policy_id, k
41
  }, { responseType: 'stream' });
42
 
43
- res.setHeader('Content-Type', 'text/event-stream');
44
- res.setHeader('Cache-Control', 'no-cache');
45
- res.setHeader('Connection', 'keep-alive');
46
 
47
- let answerText = "";
48
- let sourcesData = null;
49
- let buffer = "";
50
-
51
  response.data.on('data', (chunk) => {
 
52
  res.write(chunk);
53
-
54
- buffer += chunk.toString();
55
- const lines = buffer.split('\n\n');
56
- buffer = lines.pop() || '';
57
-
58
- for (const line of lines) {
59
- if (line.startsWith('data: ')) {
60
- const jsonStr = line.slice(6).trim();
61
- if (jsonStr === '[DONE]') continue;
62
- try {
63
- const parsed = JSON.parse(jsonStr);
64
- if (parsed.type === 'token') {
65
- answerText += parsed.content;
66
- } else if (parsed.type === 'sources') {
67
- sourcesData = parsed.sources;
68
- }
69
- } catch (e) {
70
- // ignore partial JSON parse errors
71
- }
72
- }
73
- }
74
  });
75
 
76
  response.data.on('end', async () => {
@@ -80,8 +55,7 @@ router.post('/query/stream', authMiddleware, async (req, res) => {
80
  user_id: req.user?.id || 'anonymous',
81
  policy_id,
82
  question,
83
- answer: answerText,
84
- sources: sourcesData
85
  });
86
  } catch (dbErr) {
87
  console.error('[query/stream] DB Save Error:', dbErr.message);
 
40
  question, policy_id, k
41
  }, { responseType: 'stream' });
42
 
43
+ res.setHeader('Content-Type', 'text/plain');
 
 
44
 
45
+ let fullAnswer = "";
 
 
 
46
  response.data.on('data', (chunk) => {
47
+ fullAnswer += chunk.toString();
48
  res.write(chunk);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  });
50
 
51
  response.data.on('end', async () => {
 
55
  user_id: req.user?.id || 'anonymous',
56
  policy_id,
57
  question,
58
+ answer: fullAnswer
 
59
  });
60
  } catch (dbErr) {
61
  console.error('[query/stream] DB Save Error:', dbErr.message);
frontend/src/api.js CHANGED
@@ -61,7 +61,7 @@ export const fetchApi = async (endpoint, options = {}) => {
61
  }
62
  };
63
 
64
- export const streamApi = async (endpoint, options = {}, onEvent) => {
65
  const url = `${API_URL}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
66
  const fetchOptions = {
67
  ...options,
@@ -81,28 +81,13 @@ export const streamApi = async (endpoint, options = {}, onEvent) => {
81
  const reader = response.body.getReader();
82
  const decoder = new TextDecoder();
83
  let done = false;
84
- let buffer = '';
85
 
86
  while (!done) {
87
  const { value, done: doneReading } = await reader.read();
88
  done = doneReading;
89
- if (value) {
90
- buffer += decoder.decode(value, { stream: true });
91
- const lines = buffer.split('\n\n');
92
- buffer = lines.pop() || ''; // Keep the last incomplete chunk
93
-
94
- for (const line of lines) {
95
- if (line.startsWith('data: ')) {
96
- const jsonStr = line.slice(6).trim();
97
- if (jsonStr === '[DONE]') continue;
98
- try {
99
- const parsed = JSON.parse(jsonStr);
100
- onEvent(parsed);
101
- } catch (e) {
102
- console.error('Failed to parse SSE JSON:', jsonStr, e);
103
- }
104
- }
105
- }
106
  }
107
  }
108
  };
 
61
  }
62
  };
63
 
64
+ export const streamApi = async (endpoint, options = {}, onToken) => {
65
  const url = `${API_URL}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
66
  const fetchOptions = {
67
  ...options,
 
81
  const reader = response.body.getReader();
82
  const decoder = new TextDecoder();
83
  let done = false;
 
84
 
85
  while (!done) {
86
  const { value, done: doneReading } = await reader.read();
87
  done = doneReading;
88
+ const chunkValue = decoder.decode(value);
89
+ if (chunkValue) {
90
+ onToken(chunkValue);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
  }
93
  };
frontend/src/components/Dashboard/Chatbot.jsx CHANGED
@@ -94,19 +94,8 @@ function MessageBubble({ msg, T }) {
94
  <Aperture size={18} style={{ color:T.acc }}/>
95
  </div>
96
  )}
97
- <div style={{ maxWidth:'75%', display:'flex', flexDirection:'column', gap:8 }}>
98
- <div style={{ padding:'14px 20px', fontSize:15, lineHeight:1.6, borderRadius: isUser ? '20px 4px 20px 20px' : '4px 20px 20px 20px', background: isUser ? T.msgUserBg : T.msgAiBg, color: isUser ? T.msgUserText : T.msgAiText, boxShadow: isUser ? T.msgUserShadow : 'none', fontFamily:"'DM Sans', sans-serif", fontWeight:400, transition:'background .4s, color .4s', whiteSpace:'pre-wrap' }}>
99
- {msg.text}
100
- </div>
101
- {!isUser && msg.sources && msg.sources.length > 0 && (
102
- <div style={{ display:'flex', flexDirection:'column', gap:4, marginTop:4, alignSelf:'flex-start' }}>
103
- {msg.sources.map((s, idx) => (
104
- <div key={idx} style={{ padding:'6px 10px', background:T.msgAiBg, border:`1px solid ${T.cardBorder}`, borderRadius:8, fontSize:11, fontFamily:"'JetBrains Mono', monospace", color:T.acc, lineHeight:1.3 }}>
105
- <span style={{ fontWeight:600 }}>Source:</span> {s.section !== 'Unknown' ? s.section : (s.clause_type !== 'Unknown' ? s.clause_type : 'Document')} {s.page_number ? `(Page ${s.page_number})` : ''}
106
- </div>
107
- ))}
108
- </div>
109
- )}
110
  </div>
111
  {isUser && (
112
  <div style={{ width:36, height:36, borderRadius:12, flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center', background:T.msgAiBg, border:`1px solid ${T.cardBorder}` }}>
@@ -156,18 +145,10 @@ export default function Chatbot({ file, isDark: initDark }) {
156
  await streamApi('/query/stream', {
157
  method: 'POST',
158
  body: { question: text, policy_id: file?.policy_id || 'test' }
159
- }, (event) => {
160
- setMessages(prev => prev.map(m => {
161
- if (m.id !== aiMsgId) return m;
162
- if (event.type === 'token') {
163
- return { ...m, text: m.text + event.content };
164
- } else if (event.type === 'sources') {
165
- return { ...m, sources: event.sources };
166
- } else if (event.type === 'error') {
167
- return { ...m, text: m.text + '\n\n[Error]: ' + event.content };
168
- }
169
- return m;
170
- }));
171
  });
172
  } catch (err) {
173
  setMessages(p => [...p, { id: crypto.randomUUID(), sender: 'ai', text: 'Error: ' + err.message }]);
 
94
  <Aperture size={18} style={{ color:T.acc }}/>
95
  </div>
96
  )}
97
+ <div style={{ maxWidth:'75%', padding:'14px 20px', fontSize:15, lineHeight:1.6, borderRadius: isUser ? '20px 4px 20px 20px' : '4px 20px 20px 20px', background: isUser ? T.msgUserBg : T.msgAiBg, color: isUser ? T.msgUserText : T.msgAiText, boxShadow: isUser ? T.msgUserShadow : 'none', fontFamily:"'DM Sans', sans-serif", fontWeight:400, transition:'background .4s, color .4s' }}>
98
+ {msg.text}
 
 
 
 
 
 
 
 
 
 
 
99
  </div>
100
  {isUser && (
101
  <div style={{ width:36, height:36, borderRadius:12, flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center', background:T.msgAiBg, border:`1px solid ${T.cardBorder}` }}>
 
145
  await streamApi('/query/stream', {
146
  method: 'POST',
147
  body: { question: text, policy_id: file?.policy_id || 'test' }
148
+ }, (token) => {
149
+ setMessages(prev => prev.map(m =>
150
+ m.id === aiMsgId ? { ...m, text: m.text + token } : m
151
+ ));
 
 
 
 
 
 
 
 
152
  });
153
  } catch (err) {
154
  setMessages(p => [...p, { id: crypto.randomUUID(), sender: 'ai', text: 'Error: ' + err.message }]);
frontend/src/components/Dashboard/Dboard.jsx CHANGED
@@ -475,20 +475,12 @@ export default function Dboard({ file, isDark: _initDark, userName = 'My Account
475
  ...prev,
476
  [activePolicyId]: [...(prev[activePolicyId] ?? []), { id:aiMsgId, sender:'ai', text:'' }],
477
  }));
478
- await streamApi('/query/stream', { method:'POST', body:{ question:text, policy_id:pid } }, (event) => {
479
  setChatMessagesMap(prev => ({
480
  ...prev,
481
- [activePolicyId]: prev[activePolicyId].map(m => {
482
- if (m.id !== aiMsgId) return m;
483
- if (event.type === 'token') {
484
- return { ...m, text: m.text + event.content };
485
- } else if (event.type === 'sources') {
486
- return { ...m, sources: event.sources };
487
- } else if (event.type === 'error') {
488
- return { ...m, text: m.text + '\n\n[Error]: ' + event.content };
489
- }
490
- return m;
491
- }),
492
  }));
493
  });
494
  } catch (err) {
@@ -1014,19 +1006,8 @@ export default function Dboard({ file, isDark: _initDark, userName = 'My Account
1014
  <Aperture size={15} style={{ color:T.acc }} />
1015
  </div>
1016
  )}
1017
- <div style={{ maxWidth:'80%', display:'flex', flexDirection:'column', gap:6 }}>
1018
- <div style={{ padding:'12px 16px', fontSize:13, lineHeight:1.6, borderRadius: isUser ? '18px 4px 18px 18px' : '4px 18px 18px 18px', background: isUser ? T.msgUserBg : T.msgAiBg, color: isUser ? T.msgUserText : T.msgAiText, boxShadow: isUser ? T.msgUserShadow : 'none', ...f, transition:'background .4s, color .4s', whiteSpace:'pre-wrap' }}>
1019
- {msg.text}
1020
- </div>
1021
- {!isUser && msg.sources && msg.sources.length > 0 && (
1022
- <div style={{ display:'flex', flexDirection:'column', gap:4, marginTop:2, alignSelf:'flex-start' }}>
1023
- {msg.sources.map((s, idx) => (
1024
- <div key={idx} style={{ padding:'6px 10px', background:T.badgeBg, border:`1px solid ${T.navActiveBrd}`, borderRadius:8, fontSize:10, ...mono, color:T.acc, lineHeight:1.3 }}>
1025
- <span style={{ fontWeight:600 }}>Source:</span> {s.section !== 'Unknown' ? s.section : (s.clause_type !== 'Unknown' ? s.clause_type : 'Document')} {s.page_number ? `(Page ${s.page_number})` : ''}
1026
- </div>
1027
- ))}
1028
- </div>
1029
- )}
1030
  </div>
1031
  </div>
1032
  );
 
475
  ...prev,
476
  [activePolicyId]: [...(prev[activePolicyId] ?? []), { id:aiMsgId, sender:'ai', text:'' }],
477
  }));
478
+ await streamApi('/query/stream', { method:'POST', body:{ question:text, policy_id:pid } }, (token) => {
479
  setChatMessagesMap(prev => ({
480
  ...prev,
481
+ [activePolicyId]: prev[activePolicyId].map(m =>
482
+ m.id === aiMsgId ? { ...m, text: m.text + token } : m
483
+ ),
 
 
 
 
 
 
 
 
484
  }));
485
  });
486
  } catch (err) {
 
1006
  <Aperture size={15} style={{ color:T.acc }} />
1007
  </div>
1008
  )}
1009
+ <div style={{ maxWidth:'80%', padding:'12px 16px', fontSize:13, lineHeight:1.6, borderRadius: isUser ? '18px 4px 18px 18px' : '4px 18px 18px 18px', background: isUser ? T.msgUserBg : T.msgAiBg, color: isUser ? T.msgUserText : T.msgAiText, boxShadow: isUser ? T.msgUserShadow : 'none', ...f, transition:'background .4s, color .4s' }}>
1010
+ {msg.text}
 
 
 
 
 
 
 
 
 
 
 
1011
  </div>
1012
  </div>
1013
  );
rag_engine/services/query_service.py CHANGED
@@ -101,41 +101,9 @@ class QueryService:
101
 
102
  prompt = build_query_prompt(question, context, policy_id)
103
 
104
- import json
105
- import time
106
  # Step 5 — LLM Stream
107
  for token in self._llm.stream(prompt, system=SYSTEM_PROMPT):
108
- # Pace the stream so it renders smoothly in the UI rather than huge blocks
109
- chunk_size = 2
110
- for i in range(0, len(token), chunk_size):
111
- piece = token[i:i+chunk_size]
112
- yield f"data: {json.dumps({'type': 'token', 'content': piece})}\n\n"
113
- time.sleep(0.015)
114
-
115
- # Step 6 — Yield sources at the end
116
- seen_sources = set()
117
- unique_sources = []
118
- for chunk in reranked[:5]:
119
- sec = chunk.get("metadata", {}).get("section_name", "Unknown")
120
- page = chunk.get("metadata", {}).get("page_number")
121
- key = (sec, page)
122
- if key not in seen_sources:
123
- seen_sources.add(key)
124
- unique_sources.append({
125
- "section": sec,
126
- "clause_type": chunk.get("metadata", {}).get("clause_type", "Unknown"),
127
- "page_number": page,
128
- "highlight_text": chunk.get("content", ""),
129
- "relevance_score": round(chunk.get("rerank_score", chunk.get("score", 0)), 4),
130
- "snippet": (
131
- chunk.get("content", "")[:200] + "..."
132
- if len(chunk.get("content", "")) > 200
133
- else chunk.get("content", "")
134
- ),
135
- })
136
-
137
- yield f"data: {json.dumps({'type': 'sources', 'sources': unique_sources})}\n\n"
138
- yield "data: [DONE]\n\n"
139
 
140
  # ------------------------------------------------------------------ #
141
  # multi-policy query
 
101
 
102
  prompt = build_query_prompt(question, context, policy_id)
103
 
 
 
104
  # Step 5 — LLM Stream
105
  for token in self._llm.stream(prompt, system=SYSTEM_PROMPT):
106
+ yield token
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  # ------------------------------------------------------------------ #
109
  # multi-policy query