Skip to content

Commit d4c9fde

Browse files
committed
simplified review ui
1 parent a9d4434 commit d4c9fde

File tree

3 files changed

+183
-642
lines changed

3 files changed

+183
-642
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Augmenta is an AI agent for enhancing datasets with information from the interne
1212

1313
Large Language Models (LLMs) can be powerful tools for processing a lot of information very quickly. However, they don't do it entirely accurately. LLMs are prone to [hallucinations](https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)), making them unreliable sources of truth, particularly when it comes to tasks that require domain-specific knowledge.
1414

15-
Augmenta aims to address this shortcoming by allowing LLMs to search and browse the internet for information. This technique is known as "search-based [Retrieval-Augmented Generation (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation)", and has been shown to significantly improve output quality. It does not, however, eliminate the risk of hallucinations entirely, so you should always verify the results before publishing them.
15+
Augmenta aims to address this shortcoming by allowing LLMs to search and browse the internet for information. This technique is known as "search-based [Retrieval-Augmented Generation (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation)", or "[grounding](https://techcommunity.microsoft.com/blog/fasttrackforazureblog/grounding-llms/3843857)", and has been shown to significantly improve output quality. It does not, however, eliminate the risk of hallucinations entirely, so you should always verify the results before publishing them.
1616

1717
## Installation
1818

review/Code.gs

Lines changed: 70 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -1,281 +1,96 @@
1-
/**
2-
* @OnlyCurrentDoc Limits the script to only accessing the spreadsheet it's bound to.
3-
*/
4-
5-
const CONFIG = {
6-
REVIEW_STATUS: "Review Status",
7-
REVIEWER_EMAIL: "Reviewer Email",
8-
TIMESTAMP: "Review Timestamp",
9-
NOTES: "Review Notes",
10-
STATUS_IN_PROGRESS: "In Progress",
11-
LOCK_TIMEOUT: 10000, // Reduced to 10 seconds
12-
CHUNK_SIZE: 1000, // Process data in chunks
13-
REQUIRED_COLUMNS: ["Review Status", "Reviewer Email", "Review Timestamp", "Review Notes"],
14-
VALID_DECISIONS: new Set(["True", "False", "Unsure"]),
15-
CACHE_DURATION: 21600 // 6 hours in seconds
1+
// Sheet column names and values
2+
const REVIEW_STATUS = 'review_status';
3+
const REVIEW_NOTES = 'review_notes';
4+
const REVIEW_USER = 'review_user';
5+
const REVIEW_TIMESTAMP = 'review_timestamp';
6+
7+
// Review status values
8+
const STATUS = {
9+
IN_PROGRESS: 'in_progress',
10+
CORRECT: 'correct',
11+
INCORRECT: 'incorrect',
12+
NOT_SURE: 'not_sure'
1613
};
1714

18-
function doGet(e) {
19-
const sheetId = PropertiesService.getScriptProperties().getProperty('SHEET_ID');
20-
if (!sheetId) {
21-
return HtmlService.createHtmlOutput(
22-
'<b>Error:</b> Spreadsheet ID not configured. Please set the SHEET_ID script property.'
23-
);
24-
}
25-
26-
try {
27-
SpreadsheetApp.openById(sheetId).getName(); // Test access
28-
return HtmlService.createTemplateFromFile('Index')
29-
.evaluate()
30-
.setTitle('Augmenta Review')
31-
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
32-
} catch (err) {
33-
Logger.log("Error accessing Sheet ID '%s': %s", sheetId, err);
34-
return HtmlService.createHtmlOutput(
35-
`<b>Error:</b> Cannot access Spreadsheet with ID: ${sheetId}. Check ID and permissions. Error: ${err.message}`
36-
);
37-
}
38-
}
39-
4015
/**
41-
* Executes a function with a lock and proper cleanup
42-
* @param {Function} callback Function to execute under lock
43-
* @param {number} timeout Lock timeout in milliseconds
44-
* @return {*} Result of the callback function
16+
* Get the active sheet and its header row
4517
*/
46-
function withLock_(callback, timeout = CONFIG.LOCK_TIMEOUT) {
47-
const lock = LockService.getScriptLock();
48-
try {
49-
if (!lock.tryLock(timeout)) {
50-
throw new Error("Could not obtain lock. Server might be busy. Please try again.");
51-
}
52-
return callback();
53-
} finally {
54-
if (lock.hasLock()) {
55-
lock.releaseLock();
56-
}
57-
}
18+
function getSheet() {
19+
const sheet = SpreadsheetApp.getActiveSheet();
20+
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
21+
return { sheet, headers };
5822
}
5923

6024
/**
61-
* Gets cached header indices or generates them if not cached
62-
* @param {Sheet} sheet The sheet to get headers from
63-
* @return {object} Header indices
25+
* Find column index by name
6426
*/
65-
function getHeaderIndices_(sheet) {
66-
const cache = CacheService.getScriptCache();
67-
const cacheKey = `header_indices_${sheet.getSheetId()}`;
68-
69-
let indices = cache.get(cacheKey);
70-
if (indices) {
71-
return JSON.parse(indices);
72-
}
73-
74-
const headerData = getOrAddHeaders_(sheet);
75-
if (!headerData.success) {
76-
throw new Error(headerData.error);
77-
}
78-
79-
cache.put(cacheKey, JSON.stringify(headerData.indices), CONFIG.CACHE_DURATION);
80-
return headerData.indices;
27+
function getColumnIndex(headers, columnName) {
28+
const index = headers.indexOf(columnName);
29+
if (index === -1) throw new Error(`Column ${columnName} not found`);
30+
return index + 1;
8131
}
8232

8333
/**
84-
* Finds the next unreviewed row efficiently
85-
* @param {Sheet} sheet The sheet to search
86-
* @param {number} statusCol Status column index
87-
* @param {number} reviewerCol Reviewer column index
88-
* @return {number} Row index or -1 if none found
34+
* Returns the next available row and marks it as in_progress
8935
*/
90-
function findNextUnreviewedRow_(sheet, statusCol, reviewerCol) {
36+
function getNextRow() {
37+
const { sheet, headers } = getSheet();
38+
const statusCol = getColumnIndex(headers, REVIEW_STATUS);
39+
40+
// Get all review status values
9141
const lastRow = sheet.getLastRow();
92-
if (lastRow <= 1) return -1;
93-
94-
for (let startRow = 2; startRow <= lastRow; startRow += CONFIG.CHUNK_SIZE) {
95-
const endRow = Math.min(startRow + CONFIG.CHUNK_SIZE - 1, lastRow);
96-
const range = sheet.getRange(startRow, statusCol, endRow - startRow + 1, 1);
97-
const values = range.getValues();
98-
99-
const rowIndex = values.findIndex(row => !row[0]);
100-
if (rowIndex !== -1) {
101-
return startRow + rowIndex;
102-
}
103-
}
104-
return -1;
105-
}
106-
107-
/**
108-
* Gets the next available row for review and assigns it to the current user.
109-
* Uses optimized locking and caching strategies.
110-
* @return {object} Response containing row data or error/message
111-
*/
112-
function getNextRowToReview() {
113-
const userEmail = Session.getActiveUser().getEmail();
114-
if (!userEmail) {
115-
Logger.log('Could not get user email.');
116-
return { error: "Could not identify the current user. Please ensure you are logged into a Google Account." };
117-
}
118-
119-
const ss = SpreadsheetApp.openById(PropertiesService.getScriptProperties().getProperty('SHEET_ID'));
120-
const sheet = ss.getActiveSheet();
121-
122-
return withLock_(() => {
123-
try {
124-
const headerIndices = getHeaderIndices_(sheet);
125-
const statusCol = headerIndices[CONFIG.REVIEW_STATUS] + 1;
126-
const reviewerCol = headerIndices[CONFIG.REVIEWER_EMAIL] + 1;
127-
128-
const nextRowIndex = findNextUnreviewedRow_(sheet, statusCol, reviewerCol);
129-
if (nextRowIndex === -1) {
130-
return { message: "All rows have been reviewed or assigned." };
131-
}
132-
133-
// Batch update status and reviewer
134-
sheet.getRange(nextRowIndex, statusCol, 1, 2).setValues([[
135-
CONFIG.STATUS_IN_PROGRESS,
136-
userEmail
137-
]]);
138-
139-
// Get complete row data outside of critical section
140-
const rowRange = sheet.getRange(nextRowIndex, 1, 1, sheet.getLastColumn());
141-
const rowData = rowRange.getValues()[0];
142-
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
143-
144-
Logger.log(`Assigned row ${nextRowIndex} to ${userEmail}`);
42+
if (lastRow <= 1) return null; // Only header row exists
43+
44+
const statusRange = sheet.getRange(2, statusCol, lastRow - 1, 1);
45+
const statusValues = statusRange.getValues();
46+
47+
// Find first empty status row
48+
for (let i = 0; i < statusValues.length; i++) {
49+
if (!statusValues[i][0]) {
50+
const row = i + 2; // Add 2 for header row and 0-based index
51+
52+
// Mark as in_progress and return row data
53+
sheet.getRange(row, statusCol).setValue(STATUS.IN_PROGRESS);
54+
55+
// Get all row data
56+
const rowData = sheet.getRange(row, 1, 1, headers.length).getValues()[0];
14557
return {
146-
rowIndex: nextRowIndex,
58+
rowIndex: row,
14759
headers: headers,
148-
rowData: rowData
60+
data: rowData
14961
};
150-
151-
} catch (error) {
152-
Logger.log(`Error in getNextRowToReview: ${error}`);
153-
return { error: `An error occurred: ${error.message}` };
15462
}
155-
});
63+
}
64+
65+
return null; // No available rows
15666
}
15767

15868
/**
159-
* Submits review and gets next row in one operation for maximum performance.
160-
* @param {number} rowIndex Row being reviewed (1-based)
161-
* @param {string} decision Review decision ('True', 'False', 'Unsure')
162-
* @param {string} notes Reviewer notes
163-
* @return {object} Result containing success status and next row data
69+
* Submit a review for a specific row
16470
*/
165-
function submitAndGetNext(rowIndex, decision, notes) {
166-
const userEmail = Session.getActiveUser().getEmail();
167-
if (!userEmail) {
168-
return { success: false, message: "Could not identify current user" };
169-
}
170-
171-
if (!rowIndex || !CONFIG.VALID_DECISIONS.has(decision)) {
172-
return { success: false, message: "Invalid submission data" };
173-
}
174-
175-
const ss = SpreadsheetApp.openById(PropertiesService.getScriptProperties().getProperty('SHEET_ID'));
176-
const sheet = ss.getActiveSheet();
177-
178-
return withLock_(() => {
179-
try {
180-
const headerIndices = getHeaderIndices_(sheet);
181-
const statusCol = headerIndices[CONFIG.REVIEW_STATUS] + 1;
182-
const reviewerCol = headerIndices[CONFIG.REVIEWER_EMAIL] + 1;
183-
const timestampCol = headerIndices[CONFIG.TIMESTAMP] + 1;
184-
const notesCol = headerIndices[CONFIG.NOTES] + 1;
185-
186-
// Submit current review
187-
sheet.getRange(rowIndex, statusCol, 1, 4).setValues([[
188-
decision,
189-
userEmail,
190-
new Date(),
191-
notes || ""
192-
]]);
193-
194-
// Find and assign next row
195-
const nextRowIndex = findNextUnreviewedRow_(sheet, statusCol, reviewerCol);
196-
if (nextRowIndex === -1) {
197-
return { success: true, message: "All rows reviewed" };
198-
}
199-
200-
// Assign next row and get its data
201-
sheet.getRange(nextRowIndex, statusCol, 1, 2).setValues([[CONFIG.STATUS_IN_PROGRESS, userEmail]]);
202-
const rowData = sheet.getRange(nextRowIndex, 1, 1, sheet.getLastColumn()).getValues()[0];
203-
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
204-
205-
SpreadsheetApp.flush();
206-
return {
207-
success: true,
208-
rowIndex: nextRowIndex,
209-
headers: headers,
210-
rowData: rowData
211-
};
212-
213-
} catch (error) {
214-
Logger.log(`Error in submitAndGetNext: ${error}`);
215-
return { success: false, message: error.message };
216-
}
217-
});
71+
function submitReview(rowIndex, decision, notes) {
72+
const { sheet, headers } = getSheet();
73+
74+
// Get column indices
75+
const statusCol = getColumnIndex(headers, REVIEW_STATUS);
76+
const notesCol = getColumnIndex(headers, REVIEW_NOTES);
77+
const userCol = getColumnIndex(headers, REVIEW_USER);
78+
const timeCol = getColumnIndex(headers, REVIEW_TIMESTAMP);
79+
80+
// Update each field individually to ensure correct column order
81+
sheet.getRange(rowIndex, statusCol).setValue(decision);
82+
sheet.getRange(rowIndex, notesCol).setValue(notes);
83+
sheet.getRange(rowIndex, userCol).setValue(Session.getActiveUser().getEmail());
84+
sheet.getRange(rowIndex, timeCol).setValue(new Date());
85+
86+
return true;
21887
}
21988

22089
/**
221-
* Ensures required columns exist and returns their indices
222-
* @param {Sheet} sheet The sheet to check/modify
223-
* @return {object} Header information and status
90+
* Web app entry point - serves HTML interface
22491
*/
225-
function getOrAddHeaders_(sheet) {
226-
try {
227-
const headerRange = sheet.getRange(1, 1, 1, sheet.getMaxColumns());
228-
const headers = headerRange.getValues()[0].map(h => String(h).trim());
229-
const headerIndices = {};
230-
const missingCols = [];
231-
let nextCol = headers.length;
232-
233-
// Find existing headers and identify missing ones
234-
CONFIG.REQUIRED_COLUMNS.forEach(colName => {
235-
const index = headers.findIndex(h => h === colName);
236-
if (index !== -1) {
237-
headerIndices[colName] = index;
238-
} else {
239-
missingCols.push(colName);
240-
headerIndices[colName] = nextCol++;
241-
}
242-
});
243-
244-
// Add missing columns if needed
245-
if (missingCols.length > 0) {
246-
Logger.log("Adding missing columns: " + missingCols.join(', '));
247-
const currentCols = sheet.getMaxColumns();
248-
const neededCols = Math.max(...Object.values(headerIndices)) + 1;
249-
250-
if (neededCols > currentCols) {
251-
sheet.insertColumnsAfter(currentCols, neededCols - currentCols);
252-
}
253-
254-
// Batch update for missing headers
255-
const headerUpdates = missingCols.map(colName => [colName]);
256-
const updateRange = sheet.getRange(1, headerIndices[missingCols[0]] + 1, 1, missingCols.length);
257-
updateRange.setValues(headerUpdates);
258-
259-
// Refresh headers after adding new columns
260-
const updatedHeaders = sheet.getRange(1, 1, 1, neededCols).getValues()[0];
261-
return {
262-
success: true,
263-
headers: updatedHeaders,
264-
indices: headerIndices
265-
};
266-
}
267-
268-
return {
269-
success: true,
270-
headers: headers,
271-
indices: headerIndices
272-
};
273-
274-
} catch (error) {
275-
Logger.log(`Error in getOrAddHeaders_: ${error}`);
276-
return {
277-
success: false,
278-
error: `Failed to set up review columns: ${error.message}`
279-
};
280-
}
92+
function doGet() {
93+
return HtmlService.createHtmlOutputFromFile('Index')
94+
.setTitle('Review Interface')
95+
.setFaviconUrl('https://www.google.com/sheets/about/favicon.ico');
28196
}

0 commit comments

Comments
 (0)