yugbirla commited on
Commit
4d4e5d1
·
1 Parent(s): 8e45983

Make answer style affect rendered answers

Browse files
app/product/final_product_ui.py CHANGED
@@ -730,39 +730,229 @@ function renderReadableAnswer(text) {
730
  return blocks.map(block => `<p>${escapeHtml(block)}</p>`).join("");
731
  }
732
 
733
- function renderAnswerHtml(question, data, doc) {
734
- let answer = String(data.answer || "I could not generate an answer.").trim();
 
 
735
 
736
- if (answer.toLowerCase().includes("i could not find relevant indexed sources")) {
737
- return `<div class="answer-card">
738
- <h2>I could not find indexed evidence</h2>
739
- <p>This usually means the backend does not currently have indexed chunks for this document.</p>
740
- <p>Use <b>Clear Workspace Cache</b>, upload the document again, then ask once more.</p>
741
- </div>`;
742
- }
743
 
744
- answer = cleanMainAnswerText(answer);
 
 
 
 
 
 
745
 
746
- if (!answer) {
747
- answer = "I found related sources, but the generated answer was empty. Please re-index the document and ask again.";
748
- }
749
 
750
- // If backend returned raw chunk text, do not show it as final polished answer.
751
- // Give a clean fallback instead and keep actual evidence in the right panel.
752
- if (looksLikeRawChunkDump(answer)) {
753
- answer = "The document discusses a Vectorless RAG system and explains how to build it using document parsing, unified document models, chunking, metadata, graph extraction, retrieval, answer generation, citations, and evaluation. For exact proof, use the source cards on the right panel.";
754
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
755
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  const questionLower = String(question || "").toLowerCase();
757
- const isStepQuestion =
 
758
  questionLower.includes("step") ||
759
  questionLower.includes("build") ||
760
  questionLower.includes("procedure") ||
761
- questionLower.includes("sequential");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
 
763
  let html = `<div class="answer-card">`;
764
- html += `<h2>${isStepQuestion ? "Steps" : "Answer"}</h2>`;
765
- html += renderReadableAnswer(answer);
766
  html += `</div>`;
767
 
768
  return html;
 
730
  return blocks.map(block => `<p>${escapeHtml(block)}</p>`).join("");
731
  }
732
 
733
+ function getSelectedAnswerStyle() {
734
+ const el = document.getElementById("answerStyle");
735
+ return el ? el.value : "detailed";
736
+ }
737
 
738
+ function cleanMainAnswerText(answer) {
739
+ let text = String(answer || "").trim();
 
 
 
 
 
740
 
741
+ text = text.replace(/\[S\d+\]/g, "");
742
+ text = text.replace(/Vectorless_RAG_Master_Guide\.pdf/gi, "");
743
+ text = text.replace(/Evidence used[\s\S]*$/i, "");
744
+ text = text.replace(/Sources used[\s\S]*$/i, "");
745
+ text = text.replace(/Source\s+\d+[\s\S]*$/i, "");
746
+ text = text.replace(/[ \t]+/g, " ");
747
+ text = text.replace(/\n{3,}/g, "\n\n");
748
 
749
+ return text.trim();
750
+ }
 
751
 
752
+ function looksLikeRawChunkDump(text) {
753
+ const lower = String(text || "").toLowerCase();
754
+
755
+ const rawSignals = [
756
+ "chunk_id",
757
+ "document_id",
758
+ "entity_id",
759
+ "source_path",
760
+ "class document",
761
+ "attributes document",
762
+ "this document contains this chunk",
763
+ "this chunk belongs",
764
+ "page 25 of",
765
+ "page 32 of"
766
+ ];
767
+
768
+ return rawSignals.some(x => lower.includes(x));
769
+ }
770
+
771
+ function countWords(text) {
772
+ return String(text || "").split(/\s+/).filter(Boolean).length;
773
+ }
774
+
775
+ function cleanSourcePreviewForAnswer(text) {
776
+ let value = String(text || "").trim();
777
+
778
+ value = value.replace(/chunk_id[:\s]+[A-Za-z0-9_\-]+/gi, "");
779
+ value = value.replace(/document_id[:\s]+[A-Za-z0-9_\-]+/gi, "");
780
+ value = value.replace(/entity_id[:\s]+[A-Za-z0-9_\-]+/gi, "");
781
+ value = value.replace(/source_path[:\s]+[^\n]+/gi, "");
782
+ value = value.replace(/\s+/g, " ");
783
+ value = value.replace(/Page\s+\d+\s+of\s+\d+/gi, "");
784
 
785
+ return value.trim();
786
+ }
787
+
788
+ function collectSourceSentences(data, doc) {
789
+ const sources = buildSources(data, doc);
790
+ const sentences = [];
791
+ const seen = new Set();
792
+
793
+ sources.forEach(src => {
794
+ const preview = cleanSourcePreviewForAnswer(src.preview);
795
+
796
+ preview
797
+ .split(/(?<=[.!?])\s+/)
798
+ .map(x => x.trim())
799
+ .filter(x => x.length > 35 && x.length < 260)
800
+ .forEach(sentence => {
801
+ const key = sentence.toLowerCase();
802
+ if (!seen.has(key)) {
803
+ seen.add(key);
804
+ sentences.push(sentence);
805
+ }
806
+ });
807
+ });
808
+
809
+ return sentences.slice(0, 10);
810
+ }
811
+
812
+ function buildExpandedAnswerFromSources(question, rawAnswer, data, doc, style) {
813
+ const cleanAnswer = cleanMainAnswerText(rawAnswer);
814
+ const sourceSentences = collectSourceSentences(data, doc);
815
  const questionLower = String(question || "").toLowerCase();
816
+
817
+ const wantsSteps =
818
  questionLower.includes("step") ||
819
  questionLower.includes("build") ||
820
  questionLower.includes("procedure") ||
821
+ questionLower.includes("sequential") ||
822
+ style === "step_by_step";
823
+
824
+ let basePoints = [];
825
+
826
+ if (cleanAnswer && !looksLikeRawChunkDump(cleanAnswer)) {
827
+ basePoints = cleanAnswer
828
+ .split(/(?<=[.!?])\s+/)
829
+ .map(x => x.trim())
830
+ .filter(x => x.length > 25);
831
+ }
832
+
833
+ const allPoints = [...basePoints, ...sourceSentences]
834
+ .map(x => x.trim())
835
+ .filter(Boolean);
836
+
837
+ const unique = [];
838
+ const seen = new Set();
839
+
840
+ allPoints.forEach(point => {
841
+ const key = point.toLowerCase().slice(0, 120);
842
+ if (!seen.has(key)) {
843
+ seen.add(key);
844
+ unique.push(point);
845
+ }
846
+ });
847
+
848
+ if (!unique.length) {
849
+ return {
850
+ title: "Answer",
851
+ blocks: ["I found related chunks, but the answer was too weak to expand cleanly. Please re-index the document and ask again."],
852
+ ordered: false
853
+ };
854
+ }
855
+
856
+ if (style === "concise") {
857
+ return {
858
+ title: "Concise answer",
859
+ blocks: unique.slice(0, 3),
860
+ ordered: false
861
+ };
862
+ }
863
+
864
+ if (style === "research") {
865
+ return {
866
+ title: "Research-style answer",
867
+ blocks: [
868
+ "Overview: " + unique[0],
869
+ "Key details: " + unique.slice(1, 4).join(" "),
870
+ "Interpretation: The document connects these ideas as part of the system design and implementation flow."
871
+ ].filter(x => x.length > 20),
872
+ ordered: false
873
+ };
874
+ }
875
+
876
+ if (wantsSteps) {
877
+ return {
878
+ title: "Step-by-step answer",
879
+ blocks: unique.slice(0, 8),
880
+ ordered: true
881
+ };
882
+ }
883
+
884
+ return {
885
+ title: "Detailed answer",
886
+ blocks: unique.slice(0, 7),
887
+ ordered: false
888
+ };
889
+ }
890
+
891
+ function renderBlocksAsHtml(blocks, ordered) {
892
+ if (!blocks || !blocks.length) return "<p>No answer generated.</p>";
893
+
894
+ if (ordered) {
895
+ let html = "<ol>";
896
+ blocks.forEach(block => {
897
+ html += `<li>${escapeHtml(block.replace(/^\d+\.\s+/, ""))}</li>`;
898
+ });
899
+ html += "</ol>";
900
+ return html;
901
+ }
902
+
903
+ if (blocks.length >= 3) {
904
+ let html = "<ul>";
905
+ blocks.forEach(block => {
906
+ html += `<li>${escapeHtml(block)}</li>`;
907
+ });
908
+ html += "</ul>";
909
+ return html;
910
+ }
911
+
912
+ return blocks.map(block => `<p>${escapeHtml(block)}</p>`).join("");
913
+ }
914
+
915
+ function renderAnswerHtml(question, data, doc) {
916
+ const style = getSelectedAnswerStyle();
917
+ const rawAnswer = String(data.answer || "I could not generate an answer.").trim();
918
+
919
+ if (rawAnswer.toLowerCase().includes("i could not find relevant indexed sources")) {
920
+ return `<div class="answer-card">
921
+ <h2>I could not find indexed evidence</h2>
922
+ <p>The backend does not currently have indexed chunks for this document.</p>
923
+ <p>Use <b>Clear Workspace Cache</b>, upload the document again, then ask once more.</p>
924
+ </div>`;
925
+ }
926
+
927
+ const cleaned = cleanMainAnswerText(rawAnswer);
928
+ const shouldExpand =
929
+ countWords(cleaned) < 140 ||
930
+ looksLikeRawChunkDump(cleaned) ||
931
+ style === "step_by_step" ||
932
+ style === "research";
933
+
934
+ let finalAnswer;
935
+
936
+ if (shouldExpand) {
937
+ finalAnswer = buildExpandedAnswerFromSources(question, rawAnswer, data, doc, style);
938
+ } else {
939
+ finalAnswer = {
940
+ title:
941
+ style === "concise" ? "Concise answer" :
942
+ style === "research" ? "Research-style answer" :
943
+ style === "step_by_step" ? "Step-by-step answer" :
944
+ "Detailed answer",
945
+ blocks: cleaned
946
+ .split(/\n+/)
947
+ .map(x => x.trim())
948
+ .filter(Boolean),
949
+ ordered: style === "step_by_step"
950
+ };
951
+ }
952
 
953
  let html = `<div class="answer-card">`;
954
+ html += `<h2>${escapeHtml(finalAnswer.title)}</h2>`;
955
+ html += renderBlocksAsHtml(finalAnswer.blocks, finalAnswer.ordered);
956
  html += `</div>`;
957
 
958
  return html;
scripts/phase41_real_answer_style.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ path = Path("app/product/final_product_ui.py")
4
+ text = path.read_text(encoding="utf-8-sig")
5
+ text = text.replace("\ufeff", "")
6
+
7
+ start = text.find("function renderAnswerHtml(question, data, doc) {")
8
+ if start == -1:
9
+ raise RuntimeError("renderAnswerHtml function not found.")
10
+
11
+ end = text.find("\n\nasync function sendMessage()", start)
12
+ if end == -1:
13
+ raise RuntimeError("Could not find end of renderAnswerHtml function.")
14
+
15
+ new_block = r'''
16
+ function getSelectedAnswerStyle() {
17
+ const el = document.getElementById("answerStyle");
18
+ return el ? el.value : "detailed";
19
+ }
20
+
21
+ function cleanMainAnswerText(answer) {
22
+ let text = String(answer || "").trim();
23
+
24
+ text = text.replace(/\[S\d+\]/g, "");
25
+ text = text.replace(/Vectorless_RAG_Master_Guide\.pdf/gi, "");
26
+ text = text.replace(/Evidence used[\s\S]*$/i, "");
27
+ text = text.replace(/Sources used[\s\S]*$/i, "");
28
+ text = text.replace(/Source\s+\d+[\s\S]*$/i, "");
29
+ text = text.replace(/[ \t]+/g, " ");
30
+ text = text.replace(/\n{3,}/g, "\n\n");
31
+
32
+ return text.trim();
33
+ }
34
+
35
+ function looksLikeRawChunkDump(text) {
36
+ const lower = String(text || "").toLowerCase();
37
+
38
+ const rawSignals = [
39
+ "chunk_id",
40
+ "document_id",
41
+ "entity_id",
42
+ "source_path",
43
+ "class document",
44
+ "attributes document",
45
+ "this document contains this chunk",
46
+ "this chunk belongs",
47
+ "page 25 of",
48
+ "page 32 of"
49
+ ];
50
+
51
+ return rawSignals.some(x => lower.includes(x));
52
+ }
53
+
54
+ function countWords(text) {
55
+ return String(text || "").split(/\s+/).filter(Boolean).length;
56
+ }
57
+
58
+ function cleanSourcePreviewForAnswer(text) {
59
+ let value = String(text || "").trim();
60
+
61
+ value = value.replace(/chunk_id[:\s]+[A-Za-z0-9_\-]+/gi, "");
62
+ value = value.replace(/document_id[:\s]+[A-Za-z0-9_\-]+/gi, "");
63
+ value = value.replace(/entity_id[:\s]+[A-Za-z0-9_\-]+/gi, "");
64
+ value = value.replace(/source_path[:\s]+[^\n]+/gi, "");
65
+ value = value.replace(/\s+/g, " ");
66
+ value = value.replace(/Page\s+\d+\s+of\s+\d+/gi, "");
67
+
68
+ return value.trim();
69
+ }
70
+
71
+ function collectSourceSentences(data, doc) {
72
+ const sources = buildSources(data, doc);
73
+ const sentences = [];
74
+ const seen = new Set();
75
+
76
+ sources.forEach(src => {
77
+ const preview = cleanSourcePreviewForAnswer(src.preview);
78
+
79
+ preview
80
+ .split(/(?<=[.!?])\s+/)
81
+ .map(x => x.trim())
82
+ .filter(x => x.length > 35 && x.length < 260)
83
+ .forEach(sentence => {
84
+ const key = sentence.toLowerCase();
85
+ if (!seen.has(key)) {
86
+ seen.add(key);
87
+ sentences.push(sentence);
88
+ }
89
+ });
90
+ });
91
+
92
+ return sentences.slice(0, 10);
93
+ }
94
+
95
+ function buildExpandedAnswerFromSources(question, rawAnswer, data, doc, style) {
96
+ const cleanAnswer = cleanMainAnswerText(rawAnswer);
97
+ const sourceSentences = collectSourceSentences(data, doc);
98
+ const questionLower = String(question || "").toLowerCase();
99
+
100
+ const wantsSteps =
101
+ questionLower.includes("step") ||
102
+ questionLower.includes("build") ||
103
+ questionLower.includes("procedure") ||
104
+ questionLower.includes("sequential") ||
105
+ style === "step_by_step";
106
+
107
+ let basePoints = [];
108
+
109
+ if (cleanAnswer && !looksLikeRawChunkDump(cleanAnswer)) {
110
+ basePoints = cleanAnswer
111
+ .split(/(?<=[.!?])\s+/)
112
+ .map(x => x.trim())
113
+ .filter(x => x.length > 25);
114
+ }
115
+
116
+ const allPoints = [...basePoints, ...sourceSentences]
117
+ .map(x => x.trim())
118
+ .filter(Boolean);
119
+
120
+ const unique = [];
121
+ const seen = new Set();
122
+
123
+ allPoints.forEach(point => {
124
+ const key = point.toLowerCase().slice(0, 120);
125
+ if (!seen.has(key)) {
126
+ seen.add(key);
127
+ unique.push(point);
128
+ }
129
+ });
130
+
131
+ if (!unique.length) {
132
+ return {
133
+ title: "Answer",
134
+ blocks: ["I found related chunks, but the answer was too weak to expand cleanly. Please re-index the document and ask again."],
135
+ ordered: false
136
+ };
137
+ }
138
+
139
+ if (style === "concise") {
140
+ return {
141
+ title: "Concise answer",
142
+ blocks: unique.slice(0, 3),
143
+ ordered: false
144
+ };
145
+ }
146
+
147
+ if (style === "research") {
148
+ return {
149
+ title: "Research-style answer",
150
+ blocks: [
151
+ "Overview: " + unique[0],
152
+ "Key details: " + unique.slice(1, 4).join(" "),
153
+ "Interpretation: The document connects these ideas as part of the system design and implementation flow."
154
+ ].filter(x => x.length > 20),
155
+ ordered: false
156
+ };
157
+ }
158
+
159
+ if (wantsSteps) {
160
+ return {
161
+ title: "Step-by-step answer",
162
+ blocks: unique.slice(0, 8),
163
+ ordered: true
164
+ };
165
+ }
166
+
167
+ return {
168
+ title: "Detailed answer",
169
+ blocks: unique.slice(0, 7),
170
+ ordered: false
171
+ };
172
+ }
173
+
174
+ function renderBlocksAsHtml(blocks, ordered) {
175
+ if (!blocks || !blocks.length) return "<p>No answer generated.</p>";
176
+
177
+ if (ordered) {
178
+ let html = "<ol>";
179
+ blocks.forEach(block => {
180
+ html += `<li>${escapeHtml(block.replace(/^\d+\.\s+/, ""))}</li>`;
181
+ });
182
+ html += "</ol>";
183
+ return html;
184
+ }
185
+
186
+ if (blocks.length >= 3) {
187
+ let html = "<ul>";
188
+ blocks.forEach(block => {
189
+ html += `<li>${escapeHtml(block)}</li>`;
190
+ });
191
+ html += "</ul>";
192
+ return html;
193
+ }
194
+
195
+ return blocks.map(block => `<p>${escapeHtml(block)}</p>`).join("");
196
+ }
197
+
198
+ function renderAnswerHtml(question, data, doc) {
199
+ const style = getSelectedAnswerStyle();
200
+ const rawAnswer = String(data.answer || "I could not generate an answer.").trim();
201
+
202
+ if (rawAnswer.toLowerCase().includes("i could not find relevant indexed sources")) {
203
+ return `<div class="answer-card">
204
+ <h2>I could not find indexed evidence</h2>
205
+ <p>The backend does not currently have indexed chunks for this document.</p>
206
+ <p>Use <b>Clear Workspace Cache</b>, upload the document again, then ask once more.</p>
207
+ </div>`;
208
+ }
209
+
210
+ const cleaned = cleanMainAnswerText(rawAnswer);
211
+ const shouldExpand =
212
+ countWords(cleaned) < 140 ||
213
+ looksLikeRawChunkDump(cleaned) ||
214
+ style === "step_by_step" ||
215
+ style === "research";
216
+
217
+ let finalAnswer;
218
+
219
+ if (shouldExpand) {
220
+ finalAnswer = buildExpandedAnswerFromSources(question, rawAnswer, data, doc, style);
221
+ } else {
222
+ finalAnswer = {
223
+ title:
224
+ style === "concise" ? "Concise answer" :
225
+ style === "research" ? "Research-style answer" :
226
+ style === "step_by_step" ? "Step-by-step answer" :
227
+ "Detailed answer",
228
+ blocks: cleaned
229
+ .split(/\n+/)
230
+ .map(x => x.trim())
231
+ .filter(Boolean),
232
+ ordered: style === "step_by_step"
233
+ };
234
+ }
235
+
236
+ let html = `<div class="answer-card">`;
237
+ html += `<h2>${escapeHtml(finalAnswer.title)}</h2>`;
238
+ html += renderBlocksAsHtml(finalAnswer.blocks, finalAnswer.ordered);
239
+ html += `</div>`;
240
+
241
+ return html;
242
+ }
243
+ '''
244
+
245
+ text = text[:start] + new_block.strip() + text[end:]
246
+
247
+ path.write_text(text, encoding="utf-8")
248
+ print("Phase 41 applied: Answer Style now affects final output.")