Skip to content

Commit

Permalink
Added Extend Functionality to Table Popover.
Browse files Browse the repository at this point in the history
modified:   src/AiPopover.tsx
            Removed unnecessary import.
            Changed handleCommandFill to work with
            autofillTable function in ai.ts.
modified:   src/TabularDataNode.tsx
            Removed Skeleton from Popover.
            Changed addMultipleRows such that it
            now renders the new rows correctly
            and removes the blank row.
modified:   src/backend/ai.ts
            Added autofillTable function and
            changed decodeTable so that they
            are flexible with both proper and
            improper markdown tables.
            Added new system message prompt
            specific to autofillTable.
            Removed unneccessary log statements.
  • Loading branch information
Kraft-Cheese committed Dec 17, 2024
1 parent 939cee7 commit 42a23e0
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 47 deletions.
45 changes: 37 additions & 8 deletions chainforge/react-server/src/AiPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@mantine/core";
import {
autofill,
autofillTable,
generateAndReplace,
AIError,
getAIFeaturesModels,
Expand All @@ -37,7 +38,6 @@ import {
TabularDataRowType,
VarsContext,
} from "./backend/typing";
import { on } from "process";
import { v4 as uuidv4 } from "uuid";
import { parseTableData } from "./backend/tableUtils";

Expand Down Expand Up @@ -313,7 +313,7 @@ export function AIGenReplaceTablePopover({
generateAndReplaceNumber,
genDiverseOutputs,
aiFeaturesProvider,
apiKeys
apiKeys,
);

const { cols, rows } = generatedTable;
Expand Down Expand Up @@ -345,23 +345,52 @@ export function AIGenReplaceTablePopover({
}
};


const handleCommandFill = async () => {
setIsCommandFillLoading(true);
setDidCommandFillError(false);

try {
const newRows = await autofill(
values.map((row) => Object.values(row).join(" ")), // Convert row to a string representation,

// Extract columns from the values, excluding the __uid column
const tableColumns = Object.keys(values[0] || {}).filter(
(col) => col !== "__uid"
);

// Extract rows as strings, excluding the __uid column and handling empty rows
const tableRows = values
.slice(0, -1) // Remove the last empty row
.map((row) =>
tableColumns.map((col) => row[col]?.trim() || "").join(" | ")
);

const tableInput = {
cols: tableColumns,
rows: tableRows
};

// Fetch new rows from the autofillTable function
const result = await autofillTable(
tableInput,
commandFillNumber,
aiFeaturesProvider,
apiKeys,
);
onAddRows(
newRows.map((row) => ({ key: row, header: row }) as TabularDataRowType),
);

// Transform result.rows into TabularDataNode format
const newRows = result.rows.map((row) => {
const newRow: TabularDataRowType = { __uid: uuidv4() };
row.split(" | ").forEach((cell, index) => {
newRow[`col-${index}`] = cell;
});
return newRow;
});

// Append the new rows to the existing rows
onAddRows(newRows);
} catch (error) {
console.error("Error generating rows:", error);
setDidCommandFillError(true);
showAlert && showAlert("Failed to generate new rows. Please try again.");
} finally {
setIsCommandFillLoading(false);
}
Expand Down
31 changes: 18 additions & 13 deletions chainforge/react-server/src/TabularDataNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -499,13 +499,25 @@ const TabularDataNode: React.FC<TabularDataNodeProps> = ({ data, id }) => {

// Function to add multiple rows to the table
const addMultipleRows = (newRows: TabularDataRowType[]) => {
const addedRows = newRows.map((value) => {
const newRow: TabularDataRowType = { __uid: uuidv4(), ...value };
return newRow;
});
setTableData((prev) => {
// Remove the last row of the current table data as it is a blank row (if table is not empty)
const newTableData = prev.length > 0 ? prev.slice(0, -1) : [];

// Add the new rows to the table
const addedRows = newRows.map((value) => {
const newRow: TabularDataRowType = { __uid: uuidv4() };

// Map to correct column keys
tableColumns.forEach((col, index) => {
newRow[col.key] = value[`col-${index}`] || ""; // If (false, empty, null, etc...), default to empty string
});

setTableData((prev) => [...prev, ...addedRows]);
setRowValues((prev) => [...prev, ...newRows.map((row) => row.value || "")]); // Update `rowValues` based on `value`
return newRow;
});

// Return the updated table data with the new rows
return [...newTableData, ...addedRows];
});
};

// Function to replace the entire table (columns and rows)
Expand Down Expand Up @@ -582,13 +594,6 @@ const TabularDataNode: React.FC<TabularDataNodeProps> = ({ data, id }) => {
</Tooltip>,
]}
/>
<Skeleton visible={isLoading}>
<div ref={setRef}>
{tableColumns.map((col) => (
<div key={col.key}>{col.header}</div>
))}
</div>
</Skeleton>
<RenameValueModal
ref={renameColumnModal}
initialValue={
Expand Down
70 changes: 44 additions & 26 deletions chainforge/react-server/src/backend/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ function autofillSystemMessage(
return `Here is a list of commands or items. Say what the pattern seems to be in a single sentence. Then, generate ${n} more commands or items following the pattern, as an unordered markdown list. ${templateVariables && templateVariables.length > 0 ? templateVariableMessage(templateVariables) : ""}`;
}

/**
* Generate the system message used for autofillingTables.
* @param n number of rows to generate
*/
function autofillTableSystemMessage(
n: number,
templateVariables?: string[],
): string {
return `Here is a table. Generate ${n} more commands or items following the pattern. You must format your response as a markdown table with labeled columns and a divider with only the next ${n} generated commands or items of the table. ${templateVariables && templateVariables.length > 0 ? templateVariableMessage(templateVariables) : ""}`;
}

/**
* Generate the system message used for generate and replace (GAR).
*/
Expand Down Expand Up @@ -199,37 +210,46 @@ function decodeTable(mdText: string): { cols: string[]; rows: Row[] } {
// Remove code block markers and trim the text
const mdTextCleaned = mdText.replace(/```markdown/g, "").replace(/```/g, "").trim();

// Split the cleaned text into lines
// Split into lines and clean up whitespace
const lines = mdTextCleaned.split("\n").map((line) => line.trim());

// Validate table structure (header, divider, row)
if (lines.length < 3) {
// If lines have less than 1 line, throw an error
if (lines.length < 1) {
throw new AIError(`Invalid table format: ${mdText}`);
}

// Extract header and divider
const headerLine = lines[0];
const dividerLine = lines[1];
let cols: string[];
let dataLines: string[];

// Regex to validate the divider line
if (!/^(\|\s*-+\s*)+\|$/.test(dividerLine)) {
throw new AIError(`Invalid table divider line: ${dividerLine}`);
}
// Check if a proper header exists
if (/^(\|\s*-+\s*)+\|$/.test(lines[1])) {
// If valid header and divider exist
cols = lines[0]
.split("|")
.map((col) => col.trim())
.filter((col) => col.length > 0);
dataLines = lines.slice(2); // Skip header and divider lines
} else {
// If no valid header/divider, generate default column names
const firstRowCells = lines[0]
.split("|")
.map((cell) => cell.trim())
.filter((cell) => cell.length > 0);

// Parse the headers
const cols = headerLine
.split("|")
.map((col) => col.trim())
.filter((col) => col.length > 0);
// Generate default column names (col_1, col_2, ...)
cols = firstRowCells.map((_, idx) => `col_${idx + 1}`);
dataLines = lines; // Treat all lines as data rows
}

// Parse the rows
const rows = lines.slice(2).map((line) => {
const cells = line
.split("|")
.map((cell) => cell.trim())
.filter((cell) => cell.length > 0);

// Join the cells with " | "
.slice(1, -1); // Remove leading/trailing "|" splits
if (cells.length !== cols.length) {
throw new AIError(`Row column mismatch: ${line}`);
}
return cells.join(" | ");
});

Expand Down Expand Up @@ -317,7 +337,7 @@ export async function autofillTable(
): Promise<{ cols: string[]; rows: Row[] }> {

// Hash the arguments to get a unique id
const id = JSON.stringify([input, n]);
const id = JSON.stringify([input.cols, input.rows, n]);

// Encode the input table to a markdown table
const encoded = encodeTable(input.cols, input.rows);
Expand All @@ -330,14 +350,12 @@ export async function autofillTable(
]),
];

console.log("System message: ", autofillSystemMessage(n, templateVariables));

const history: ChatHistoryInfo[] = [
{
messages: [
{
role: "system",
content: autofillSystemMessage(n, templateVariables),
content: autofillTableSystemMessage(n, templateVariables),
},
],
fill_history: {},
Expand All @@ -360,15 +378,15 @@ export async function autofillTable(
if (result.errors && Object.keys(result.errors).length > 0)
throw new Error(Object.values(result.errors)[0].toString());

// Extract the output from the LLM response
const output = result.responses[0].responses[0] as string;
console.log("LLM said: ", output);

const { cols: new_cols, rows: new_rows } = decodeTable(output);
const newRows = decodeTable(output).rows;

// Return the updated table with "n" number of rows
return {
cols: new_cols,
rows: new_rows.slice(0, n),
cols: input.cols,
rows: newRows // Return the new rows generated by the LLM
};
} catch (error) {
console.error("Error in autofillTable:", error);
Expand Down

0 comments on commit 42a23e0

Please sign in to comment.