diff --git a/js/chain.js b/js/chain.js
index 1bebc27..3ed6de1 100644
--- a/js/chain.js
+++ b/js/chain.js
@@ -12,7 +12,8 @@ let input;
let questions;
-let plan;
+let plan = [];
+let priorPlans = [];
let requirements = {};
let remainingRequirements = {};
@@ -70,7 +71,7 @@ function nextPhase() {
{role: "system", content: narrowQuestionPrompt},
{role: "user", content: "The user's research query is: " + input}
]
- newActivity('Generating questions to narrow the task', undefined, undefined, true);
+ newActivity('Recognizing any ambiguity', undefined, undefined, true);
} else if (phase === 'refiningQuestionsAsked') {
@@ -101,23 +102,17 @@ function nextPhase() {
newActivity('Creating a search plan', undefined, undefined, true);
- } else if (phase === 'createRequirements') {
-
- let docDescStr;
-
- plan.forEach(section => {
- docDescStr += `${section.section_title}: ${section.description}`
- })
-
- // console.log(docDescStr)
-
+ } else if (phase === 'reviseFormatting') {
payload['messages'] = [
- {role: "system", content: requiredInfoPrompt},
- {role: "user", content: `: Paper overview and descriptions for each section: ${docDescStr}`}
+ {role: "system", content: reviseFormattingPrompt},
+ {role: "user", content: `
+ DOCUMENT_LAYOUT: ${JSON.stringify(plan)}
+ FORMAT_REQUIREMENTS: ${refinedFormattingRequirements}`
+ },
+ {role: "assistant", content: "{"}
]
- newActivity('Gathering required materials');
-
+ newActivity('Confirming formatting adherence', undefined, undefined, true);
}
makeRequest(payload);
@@ -240,13 +235,20 @@ function makeRequest(payload) {
fullResponse.choices[0].message.content = fullResponse.choices[0].message.content.replace(/\s+/g, ' ')
}
- if (fullResponse.choices[0].message.content.trim().charAt(0) !== '{') {
- context = '{' + fullResponse.choices[0].message.content;
+ let content = fullResponse.choices[0].message.content.trim();
+ if (content.charAt(content.length - 1) !== '}') {
+ const lastClosingBraceIndex = content.lastIndexOf('}');
+ console.log(content)
+ content = content.substring(0, lastClosingBraceIndex + 1);
+ console.log(content)
+ }
+ if (content.charAt(0) !== '{') {
+ context = '{' + content;
console.log('adding bracket, new json:')
console.log(context)
context = JSON.parse(context);
} else {
- context = JSON.parse(fullResponse.choices[0].message.content.replace('```json', '').replace('```', ''));
+ context = JSON.parse(content.replace('```json', '').replace('```', ''));
}
} catch (e) {
@@ -272,7 +274,7 @@ function makeRequest(payload) {
addTokenUsageToActivity(usage, undefined, latestTimerId())
setPhase('refiningQuestionsAsked')
- newActivity('Asking user to refine their request');
+ newActivity('Waiting for task clarification');
enableBar();
} else if (phase === 'confirmingValidTopic') {
@@ -346,20 +348,31 @@ function makeRequest(payload) {
addTokenUsageToActivity(usage, undefined, latestTimerId())
newActivity('Planned an outline');
addPlanToOutline()
- beginSearches();
- // setPhase('createRequirements')
+
+ // beginSearches();
+
+
+ setPhase('reviseFormatting')
+ nextPhase();
// nextPhase()
- // currently unused
- } else if (phase === 'createRequirements') {
+ } else if (phase === 'reviseFormatting') {
- context.topics.forEach((topic, index) => {
- requirements[index] = topic
- remainingRequirements[index] = topic
- })
+ addTokenUsageToActivity(usage, undefined, latestTimerId())
- addTokenUsageToActivity(usage)
- // beginSearches();
+ const follows_formatting = context.follows_formatting
+
+ console.log(context)
+
+ if (follows_formatting) {
+ newActivity('Layout conforms with formatting')
+ beginSearches();
+ } else {
+ newActivity('Modified layout to follow formatting')
+ priorPlans.push(plan)
+ plan = context.modified_layout
+ nextPhase(); // iterate again
+ }
} else {
diff --git a/js/prompts.js b/js/prompts.js
index e3eb68e..9f67cc4 100644
--- a/js/prompts.js
+++ b/js/prompts.js
@@ -67,7 +67,7 @@ const refactorPrompt = `You are a research query analyzer. Your task is to analy
Your response must be JSON in this format:
{
"query":
,
- "formatting_requirements": ,
+ "formatting_requirements": ,
"content_requirements":
}
@@ -85,17 +85,13 @@ For the query analysis:
- Do NOT include any formatting requirements in this section
For the formatting requirements, specify (if mentioned):
-- Required depth and breadth of information
- Any specific formats (tables, lists, diagrams, etc.)
-- Required metrics or measurements
-- Types of evidence or sources needed
- Structure of the response (paragraphs, sections, etc.)
- Level of technical detail required
- Any specific citation formats
-- Requirements for examples or illustrations
-- Whether comparisons or analyses are needed
- Any specific organizational requirements
- Do not specify a citation format unless otherwise mentioned
+- Do not mention any required topics or subjects
For the required content elements:
- List all specifically requested topics that must be covered
@@ -154,7 +150,7 @@ Return a JSON array where each object represents a document section. The respons
* Contain ONLY valid JSON with no additional text or formatting. Do not surround the JSON in backticks. Do not add newline characters. Do not format the JSON as a code block. Return the JSON in raw-text.
* Include exactly these three fields for each section:
* "section_title": String containing a clear, professional title for the section.
- * "description": String containing an extremely detailed explanation of how to implement all requirements from the three inputs for this section. Do not include any newline characters in the description. The description should start with "A... "
+ * "description": String containing an extremely detailed explanation of how to implement all requirements from the three inputs for this section. Do not include any newline characters in the description.
* "search_keywords": Array of strings containing specific search terms from all three inputs that would help find sources for this section's required information.
Input Format:
@@ -180,33 +176,96 @@ Example format: (no backticks)
`
-const requiredInfoPrompt = `You will be provided with a document layout containing sections and detailed descriptions outlining their goals and contents. Analyze these sections and generate a list of required information/sources needed to complete the document.
+const reviseFormattingPrompt = `Return only a JSON object that analyzes if a document layout follows specific formatting requirements. You must modify the layout as needed to make it fully comply with ALL FORMAT_REQUIREMENTS, including changing, adding, or removing any number of sections. Any changes you describe in changes_explanation MUST be reflected in the modified_layout you return.
-For each required element:
-1. First create a "topic" - a clear, specific sentence describing the information/source needed
-2. Then create a "search_phrase" - a concise, keyword-rich query optimized for search engines to find relevant sources on this topic
+Input format:
+1. DOCUMENT_LAYOUT: An array of section objects with the structure:
+ [
+ {
+ "section_title": "string: professional title of the section",
+ "description": "string: comprehensive explanation of section requirements",
+ "search_keywords": ["string: specific search term", ...]
+ },
+ ...
+ ]
-Requirements:
-- Each dictionary should be self-contained and understandable independently
-- Search phrases should include key technical terms, organizations, time frames, and source types (e.g., reports, studies, datasets)
-- Prioritize actionable search terms that would effectively locate authoritative sources
-- Break down your response into the minimum amount of topics to fully complete the task and find all information.
+2. FORMAT_REQUIREMENTS: A string containing specific formatting rules
-Return response as a JSON array of dictionaries with "topic" and "search_phrase" keys.
+Output format (return ONLY this JSON object with no additional text):
+{
+ "follows_formatting": boolean,
+ // The following keys are only included if formatting changes were required:
+ "modified_layout": [
+ {
+ "section_title": "string: section title following requirements",
+ "description": "string: section description following requirements",
+ "search_keywords": ["string: updated search terms", ...]
+ },
+ ...
+ ],
+ "changes_explanation": "string: detailed explanation of all formatting changes made to the layout"
+}
-Example response format:
+Rules:
+- You MUST modify the layout structure to fully comply with ALL FORMAT_REQUIREMENTS by:
+ - Adding new sections when required
+ - Removing sections that don't match requirements
+ - Merging or splitting sections as needed
+ - Reordering sections to match required structure
+ - Modifying section content and formatting
+ - Changing any aspect of sections to meet requirements
+- The modified_layout you return MUST exactly match the changes you describe in changes_explanation
+- If DOCUMENT_LAYOUT fully complies with FORMAT_REQUIREMENTS:
+ Return: { "follows_formatting": true }
+- If any formatting issues exist:
+ Return: {
+ "follows_formatting": false,
+ "modified_layout": [fully corrected layout matching your changes_explanation],
+ "changes_explanation": "explanation of changes that exactly match your modified_layout"
+ }
+
+Example:
+Input:
{
- "topics": [
- {
- "topic": "Analysis of historical climate change trends over the past century",
- "search_phrase": "historical climate change data 1900-2000 NOAA report"
- },
- {
- "topic": "Statistics on current greenhouse gas emissions by country",
- "search_phrase": "GHG emissions by country 2023 IPCC dataset"
- }
- ]
-}`;
+ "DOCUMENT_LAYOUT": [
+ {
+ "section_title": "Energy Drink Sales",
+ "description": "Overview of all energy drink sales",
+ "search_keywords": ["sales", "overview"]
+ }
+ ],
+ "FORMAT_REQUIREMENTS": "Must have separate sections for each brand and a trends section"
+}
+
+Output if changes needed:
+{
+ "follows_formatting": false,
+ "modified_layout": [
+ {
+ "section_title": "Monster Energy Sales",
+ "description": "Sales data for Monster energy drinks",
+ "search_keywords": ["monster", "sales"]
+ },
+ {
+ "section_title": "Bang Energy Sales",
+ "description": "Sales data for Bang energy drinks",
+ "search_keywords": ["bang", "sales"]
+ },
+ {
+ "section_title": "Sales Trends",
+ "description": "Overall sales trends analysis",
+ "search_keywords": ["trends", "analysis"]
+ }
+ ],
+ "changes_explanation": "Split single overview section into three sections: Monster sales, Bang sales, and overall trends"
+}
+
+Output if no changes needed:
+{
+ "follows_formatting": true
+}`
+
+
const categorizeSourcePrompt = `You will be provided a large body of text. Your task is to return a string sufficiently describing what the content within the text is and contains. Be extremely detailed and thorough; cover all the info covered in the text, but do not explain its purpose. The end goal is categorize this text based on its description of its contents.
@@ -448,10 +507,10 @@ const analyzeArticlesPrompt = `You will be provided the subject of a section, it
- **NO markdown, bullet points, or section titles.** Write in plain prose.
- **NO filler phrases** (e.g., "This section will discuss…"). Assume the reader is already within the document.
- **NO extraneous information.** Exclude anything not explicitly tied to the section's description.
-- **MANDATORY citation format:** Include source ID in square brackets [ID] immediately after any information, quote, or analysis derived from that source. When information comes from multiple sources, include all relevant IDs: [1][2].
-- **NO uncited information.** Every piece of information must be traced to its source(s).
- **NO repetition.** Do not excessively repeat facts or texts. Rather, delve deeply into their significance with respect to the description.
+- **MANDATORY citation format:** Include source ID in square brackets [ID] immediately after any information, quote, or analysis derived from that source. When information comes from multiple sources, include all relevant IDs: [1][2].
- When analyzing contradictions or complementary information across sources, focus on the substance while clearly citing each source.
+- Do *not* directly refer or mention the sources in sentences, rather cite the source ID afterward. (*NO saying* "the author...", "the text...", "the source...", "in source x...")
**Citation Examples:**
- Single source: "The temperature increased by 2.5 degrees" [SRC_1]
@@ -473,6 +532,9 @@ Example input format:
}`
// **After every paragraph in your response**: Include the source ID of the sources you used by wrwapping the source ID integer (from the supplied dictionary) in square brackets.
+//- **NO uncited information.** Every piece of information must be traced to its source(s).
+
+
// const checkFulfillsDescriptionPrompt = `You are provided with a required information description, a collection of source descriptions (each being a concise summary of a source's content), and a list of previously attempted search queries. Your task is to determine if the source descriptions collectively fulfill the required information description.
diff --git a/js/search.js b/js/search.js
index d09017a..5689aa3 100644
--- a/js/search.js
+++ b/js/search.js
@@ -38,7 +38,7 @@ function appendURLS(links) {
function replaceSourceWithLink(text) {
return text.replace(/\[SRC_(\d+)\]/g, (match, id) => {
const source = sources[id];
- return source ? `${id+1}` : match;
+ return source ? `${parseInt(id)+1}` : match;
});
}
@@ -356,7 +356,7 @@ async function getRelevantAndNeededSources(sectionDescription, sources_only) {
let sourceDescriptions = {}
for (const [key, value] of Object.entries(sources)) {
- sourceDescriptions[key] = value.description;
+ sourceDescriptions[`SRC_${key}`] = value.description;
}
let prompt;
@@ -391,20 +391,19 @@ async function getRelevantAndNeededSources(sectionDescription, sources_only) {
-function sendRequestToDecoder(messages_payload, max_tokens) {
+function sendRequestToDecoder(messages_payload, max_tokens, do_stream = false) {
return new Promise((resolve, reject) => {
let payload = {
model: decoderModelId,
- // repetition_penalty: 1.1,
temperature: 0.7,
top_p: 0.9,
- // top_k: 40,
messages: messages_payload,
max_tokens: 8192,
+ stream: do_stream
};
if (max_tokens) {
- payload['max_tokens'] = max_tokens
+ payload['max_tokens'] = max_tokens;
}
fetch(decoderBase, {
@@ -417,16 +416,17 @@ function sendRequestToDecoder(messages_payload, max_tokens) {
})
.then(response => {
if (!response.ok) {
+ // Rate limit handling remains the same
if (response.status === 429) {
if (!isWaiting) {
isWaiting = true;
- newActivity(`Received error 429: Too many requests. Retrying in 1 minute...`, is_error = true);
+ newActivity(`Received error 429: Too many requests. Retrying in 1 minute...`, true);
let countdown = remainingWaitTime / 1000;
const countdownInterval = setInterval(() => {
countdown--;
$('.activity-working').text(`Received error 429: Too many requests. Retrying in ${countdown}s...`);
}, 1000);
-
+
return new Promise(resolve => setTimeout(() => {
clearInterval(countdownInterval);
newActivity(`Trying again after waiting for 1 minute...`);
@@ -436,36 +436,146 @@ function sendRequestToDecoder(messages_payload, max_tokens) {
}, remainingWaitTime))
.then(() => sendRequestToDecoder(messages_payload, max_tokens));
} else {
- // Modify the else branch to also call sendRequestToDecoder after waiting:
return new Promise(resolve => setTimeout(resolve, remainingWaitTime))
.then(() => sendRequestToDecoder(messages_payload, max_tokens));
}
}
-
- newActivity(`Receive error: ${response.status}`);
+
return response.text().then(text => {
- newActivity(`Response body: ${text}`); // Log the response body in newActivity
- reject(new Error(`HTTP error! status: ${response.status}`));
+ newActivity(`Received error: ${response.status}`);
+ newActivity(`Response body: ${text}`);
+ throw new Error(`HTTP error! status: ${response.status}`);
});
- } else {
- return response.json(); // Return the promise for the JSON data
}
- })
- .then(response_json => {
- if (!response_json) {
- console.log("Response JSON:", response_json);
- throw new Error("Invalid response format: response_json is undefined.");
- } else if (!response_json.choices) {
- console.log("Response JSON:", response_json);
- throw new Error("Invalid response format: response_json does not contain 'choices'.");
- } else if (response_json.choices.length === 0) {
- console.log("Response JSON:", response_json);
- throw new Error("Invalid response format: response_json.choices is empty.");
+
+ if (do_stream) {
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder("utf-8");
+ let result = '';
+ let usage = null;
+ let hasResolved = false;
+
+ function read() {
+ return reader.read().then(({ done, value }) => {
+ if (done) {
+ // Process any remaining complete messages in the buffer
+ if (buffer) {
+ const finalMessages = buffer.split('\n\n')
+ .filter(line => line.trim() !== '')
+ .filter(line => line.startsWith('data: '));
+
+ for (const message of finalMessages) {
+ try {
+ const jsonString = message.slice(6);
+ if (jsonString === '[DONE]') continue;
+
+ const data = JSON.parse(jsonString);
+ if (data.choices?.[0]?.delta?.content) {
+ result += data.choices[0].delta.content;
+ addToModalMessage(data.choices[0].delta.content);
+ }
+ if (data.usage) {
+ usage = data.usage;
+ }
+ } catch (error) {
+ console.warn('Failed to parse final buffer message:', error);
+ }
+ }
+ }
+
+ if (!hasResolved) {
+ hasResolved = true;
+ resolve({
+ choices: [{
+ message: {
+ content: result
+ }
+ }],
+ usage: usage
+ });
+ }
+ return;
+ }
+
+ // Decode the chunk with streaming support
+ const chunk = decoder.decode(value, { stream: true });
+
+ // Buffer management
+ let buffer = '';
+ buffer += chunk;
+
+ // Look for complete messages
+ const messages = buffer.split('\n\n');
+ // Keep the last potentially incomplete message in the buffer
+ buffer = messages.pop() || '';
+
+ // Process complete messages
+ const dataObjects = messages.filter(line => line.trim() !== '');
+
+ for (const dataObject of dataObjects) {
+ if (!dataObject.startsWith('data: ')) continue;
+
+ try {
+ const jsonString = dataObject.slice(6);
+
+ if (jsonString === '[DONE]') {
+ if (!hasResolved) {
+ hasResolved = true;
+ resolve({
+ choices: [{
+ message: {
+ content: result
+ }
+ }],
+ usage: usage
+ });
+ }
+ return;
+ }
+
+ const data = JSON.parse(jsonString);
+
+ if (data.usage) {
+ usage = data.usage;
+ }
+
+ if (data.choices?.[0]?.delta?.content) {
+ const contentChunk = data.choices[0].delta.content;
+ result += contentChunk;
+ addToModalMessage(contentChunk);
+ }
+ } catch (error) {
+ console.error('Failed to parse chunk:', error);
+ console.error('Problematic chunk:', dataObject);
+ }
+ }
+
+ return read();
+ }).catch(error => {
+ console.error('Stream reading error:', error);
+ if (!hasResolved) {
+ hasResolved = true;
+ reject(error);
+ }
+ });
+ }
+
+ return read();
}
-
- // Modify the content as needed
- response_json.choices[0].message.content = response_json.choices[0].message.content.replace('```json', '').replace('```', '');
- resolve(response_json);
+
+ return response.json().then(response_json => {
+ if (!response_json?.choices?.length) {
+ console.error("Invalid response format:", response_json);
+ throw new Error("Invalid response format");
+ }
+
+ response_json.choices[0].message.content =
+ response_json.choices[0].message.content
+ .replace('```json', '')
+ .replace('```', '');
+
+ resolve(response_json);
+ });
})
.catch(error => {
console.error('Error:', error);
@@ -491,7 +601,8 @@ async function analyzeSearch(searchData, section) {
` }
];
- const data = await sendRequestToDecoder(messages_payload);
+ newModelMessageElm(true)
+ const data = await sendRequestToDecoder(messages_payload, undefined, true);
const content = data.choices[0].message.content;
const usage = data.usage;
diff --git a/js/settings.js b/js/settings.js
index ff90841..3ac2b6e 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -1,4 +1,4 @@
-const maxBranches = 2;
+const maxBranches = 0;
const defaultSettings = {
'braveKey': null,