Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 79 additions & 13 deletions src/chatbot/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AgentExecutor } from "langchain/agents";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import z from "zod";
import * as Blockly from "blockly/core";
import { getDefinedBlocks, getWorkspaceBlockDetails } from "./tools";


const get_workspace_json = tool(
Expand Down Expand Up @@ -36,13 +37,69 @@ const add_block = tool(
}
);

// const get_available_blocks = tool(
// getDefinedBlocks,
// {
// name: "get_available_blocks",
// description: "Returns a list of all available blocks",
// }
// );
const get_available_blocks = tool(
async () => {
return await getDefinedBlocks();
},
{
name: "get_available_blocks",
description: "Returns a list of all available block types with their details, including inputs, outputs, and descriptions",
}
);

const get_workspace_block_details = tool(
async () => {
return await getWorkspaceBlockDetails();
},
{
name: "get_workspace_block_details",
description: "Returns detailed information about all blocks currently in the workspace, including their positions, connections, and field values",
}
);

const analyze_code_blocks = tool(
async ({ code }) => {
const workspace = Blockly.getMainWorkspace();

try {
// Get current workspace details
const workspaceDetails = await getWorkspaceBlockDetails();
const availableBlocks = await getDefinedBlocks();

// Generate current code from workspace using imported pythonGenerator
const { pythonGenerator } = await import("../micropython/setup");
const currentCode = pythonGenerator.workspaceToCode(workspace);

return {
providedCode: code,
currentWorkspaceCode: currentCode,
workspaceBlocks: workspaceDetails,
availableBlockTypes: availableBlocks.map(b => ({
type: b.type,
comment: b.comment,
category: b.category
})),
analysis: {
hasBlocks: workspaceDetails.totalBlocks > 0,
blockCount: workspaceDetails.totalBlocks,
topLevelBlocks: workspaceDetails.workspaceStats.topBlocks
}
};
} catch (error) {
return {
error: `Error analyzing code: ${error.message}`,
providedCode: code
};
}
},
{
name: "analyze_code_blocks",
description: "Analyzes provided code and compares it with current workspace blocks, returns detailed analysis",
schema: z.object({
code: z.string().describe("The code to analyze and compare with current workspace")
})
}
);

const add_print_block = tool(
async ({ message }) => {
Expand All @@ -64,24 +121,33 @@ const add_print_block = tool(
name: "add_print_block",
description:
"Adds a print block to the workspace to print a specific message",
schema: z.object(
{
block_type: z.string().describe("Text to print")
}
)
schema: z.object({
message: z.string().describe("Text to print")
})
}
);

const tools = [
get_workspace_json,
add_block,
add_print_block,
get_available_blocks,
get_workspace_block_details,
analyze_code_blocks,
];

const prompt = ChatPromptTemplate.fromMessages([
[
"system",
"You are a helpful assistant who can work with the blockly workspace. You can add blocks to the workspace based on user requests. You can find the defined blocks and plan your actions accordingly.Use only available blocks",
"You are a helpful assistant specialized in working with Blockly visual programming workspace. You have comprehensive tools to:\n" +
"1. Analyze and understand all available block types with their properties and capabilities\n" +
"2. Examine the current workspace and understand what blocks are already placed\n" +
"3. Add new blocks to the workspace based on user requirements\n" +
"4. Analyze code and understand how it relates to the visual blocks\n" +
"5. Get detailed information about block connections, inputs, outputs, and configurations\n\n" +
"Always use the available tools to understand the current state before making changes. " +
"Use only blocks that are available in the system. When adding blocks, consider their " +
"connections and relationships with existing blocks in the workspace.",
],
["placeholder", "{chat_history}"],
["human", "{input}"],
Expand Down
127 changes: 120 additions & 7 deletions src/chatbot/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,131 @@ import { blocks } from "../blocky/blocks";
async function getDefinedBlocks() {
const blocksInfo = [];

for (const [type, block] of Object.entries(Blockly.Blocks)) {
if (type == "variables_get_dynamic" || type == "variables_set_dynamic" || type == "variables_get" || type == "variables_set" || type == "text_multiline" || type == "text_join") continue;
let block = blocks[type];
let comment = block.getCommentText();
if (comment) {
for (const [type, blockDef] of Object.entries(Blockly.Blocks)) {
// Skip default Blockly blocks and focus on custom blocks
if (type == "variables_get_dynamic" || type == "variables_set_dynamic" ||
type == "variables_get" || type == "variables_set" ||
type == "text_multiline" || type == "text_join") continue;

try {
// Create a temporary instance to extract the comment
const tempWorkspace = new Blockly.Workspace();
const tempBlock = tempWorkspace.newBlock(type);
const comment = tempBlock.getCommentText();

// Get additional block information
const blockInfo = {
type: type,
comment: comment || `Block type: ${type}`,
category: tempBlock.getCategory && tempBlock.getCategory() || 'custom',
colour: tempBlock.getColour && tempBlock.getColour() || '#000000',
hasOutput: tempBlock.outputConnection !== null,
hasPrevious: tempBlock.previousConnection !== null,
hasNext: tempBlock.nextConnection !== null,
inputs: []
};

// Extract input information
for (let i = 0; i < tempBlock.inputList.length; i++) {
const input = tempBlock.inputList[i];
blockInfo.inputs.push({
name: input.name,
type: input.type,
fields: input.fieldRow.map(field => ({
name: field.name,
value: field.getValue && field.getValue() || ''
}))
});
}

tempWorkspace.dispose();
blocksInfo.push(blockInfo);
} catch (error) {
// If there's an error creating the block, still include basic info
blocksInfo.push({
type: type,
comment: comment,
comment: `Block type: ${type} (error reading details)`,
category: 'unknown',
colour: '#000000',
hasOutput: false,
hasPrevious: false,
hasNext: false,
inputs: []
});
}
}
return blocksInfo;
}

export { getDefinedBlocks };
async function getWorkspaceBlockDetails() {
const workspace = Blockly.getMainWorkspace();
if (!workspace) {
return { error: "No workspace available" };
}

const blockDetails = [];
const allBlocks = workspace.getAllBlocks();

for (const block of allBlocks) {
const blockInfo = {
id: block.id,
type: block.type,
comment: block.getCommentText() || `${block.type} block`,
isStatement: block.previousConnection !== null || block.nextConnection !== null,
isValue: block.outputConnection !== null,
position: {
x: block.getRelativeToSurfaceXY().x,
y: block.getRelativeToSurfaceXY().y
},
inputs: [],
fields: {},
connections: {
hasOutput: block.outputConnection !== null,
hasPrevious: block.previousConnection !== null,
hasNext: block.nextConnection !== null
}
};

// Extract field values
for (const fieldName of Object.keys(block.fields_)) {
const field = block.getField(fieldName);
if (field) {
blockInfo.fields[fieldName] = field.getValue();
}
}

// Extract input information
for (let i = 0; i < block.inputList.length; i++) {
const input = block.inputList[i];
const inputInfo = {
name: input.name,
type: input.type,
connectedBlock: null
};

// Check if there's a connected block
if (input.connection && input.connection.targetConnection) {
const connectedBlock = input.connection.targetConnection.sourceBlock_;
inputInfo.connectedBlock = {
id: connectedBlock.id,
type: connectedBlock.type
};
}

blockInfo.inputs.push(inputInfo);
}

blockDetails.push(blockInfo);
}

return {
totalBlocks: allBlocks.length,
blocks: blockDetails,
workspaceStats: {
topBlocks: workspace.getTopBlocks().length,
hasCode: allBlocks.length > 0
}
};
}

export { getDefinedBlocks, getWorkspaceBlockDetails };