8000 feat(web): draw assistant tools on the canvas by adeelahmad · Pull Request #99 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content
Merged
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
2 changes: 1 addition & 1 deletion DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Todo:
- [x] Prompt and web tools, skills, hooks in the chat tool loop; approvals for change tools
- [ ] Code tools: Python in a sandboxed subprocess (admins create; time limit, no secrets, no network unless allowed)
- [x] Canvas: a `tool` workflow scope whose arg nodes are the tool's parameters (ask_model, call_tool nodes)
- [ ] Canvas editor for tool graphs in the web app (save a graph as a tool)
- [x] Canvas editor for tool graphs in the web app (Extensions → Draw a tool)
- [x] Authoring from chat and voice: the assistant drafts and saves extensions behind an approval card
- [x] Extensions page in the web app: list, manifest editor, switch on/off, share, try, versions
- [ ] `lens ext` CLI (push/pull manifests)
Expand Down
20 changes: 20 additions & 0 deletions nextjs-frontend/__tests__/workflow-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
normalize,
portsOf,
problems,
inScope,
replaceSelection,
starterTool,
withBody,
type CustomDef,
type WfGraph,
Expand Down Expand Up @@ -103,3 +105,21 @@ describe("bodies", () => {
expect(foldSelection(g, ["in", "p"])).toMatch(/start/);
});
});

describe("tool graphs", () => {
it("checks an assistant tool drawn on the canvas", () => {
const g = starterTool();
expect(problems(g, () => undefined, "tool", "custom")).toEqual({});
expect(inScope("ask_model", "tool")).toBe(true);
expect(inScope("extract_rules", "tool")).toBe(false);
const empty = { ...g, nodes: g.nodes.map((n) => (n.type === "ask_model" ? { ...n, config: { prompt: " " } } : n)) };
expect(problems(empty, () => undefined, "tool", "custom").ask).toBe("Write its prompt.");
const call = {
...g,
nodes: g.nodes.map((n) => (n.type === "ask_model" ? { ...n, type: "call_tool", config: { tool: "X" } } : n)),
};
expect(problems(call, () => undefined, "tool", "custom").ask).toBe("Name the tool it calls.");
const recording = { ...g, nodes: [...g.nodes, { id: "keep", type: "save_entities", config: {} }] };
expect(problems(recording, () => undefined, "tool", "custom").keep).toBe("This node isn’t for tools.");
});
});
14 changes: 14 additions & 0 deletions nextjs-frontend/app/(app)/extensions/[id]/canvas/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";

import { ToolGraphEditor } from "@/components/extensions/tool-graph-editor";

export const metadata: Metadata = { title: "Tool" };

/** An assistant tool on the canvas. */
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const n = Number(id);
if (!Number.isInteger(n) || n < 1) notFound();
return <ToolGraphEditor id={n} />;
}
10 changes: 10 additions & 0 deletions nextjs-frontend/app/(app)/extensions/new/canvas/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Metadata } from "next";

import { ToolGraphEditor } from "@/components/extensions/tool-graph-editor";

export const metadata: Metadata = { title: "New tool" };

/** A new assistant tool, drawn on the canvas. */
export default function Page() {
return <ToolGraphEditor />;
}
7 changes: 7 additions & 0 deletions nextjs-frontend/components/extensions/extension-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ export function ExtensionEditor({ id }: { id?: number }) {
</Link>
</div>
<PageHeader
actions={
e?.kind === "tool" && (e.spec.run as { type?: string } | undefined)?.type === "graph" ? (
<Button asChild variant="secondary">
<Link href={`/extensions/${e.id}/canvas`}>Open on the canvas</Link>
</Button>
) : undefined
}
title={e ? <span className="font-mono">{e.name}</span> : "New extension"}
meta={
e ? (
Expand Down
8 changes: 7 additions & 1 deletion nextjs-frontend/components/extensions/extensions-page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Blocks, MessagesSquare, Mic, Plus } from "lucide-react";
import { Blocks, MessagesSquare, Mic, Plus, Workflow } from "lucide-react";
import Link from "next/link";

import { Extensions } from "@/app/openapi-client";
Expand Down Expand Up @@ -65,6 +65,12 @@ export function ExtensionsPage() {
Ask the assistant
</Link>
</Button>
<Button asChild variant="secondary">
<Link href="/extensions/new/canvas">
<Workflow aria-hidden />
Draw a tool
</Link>
</Button>
<Button asChild variant="primary">
<Link href="/extensions/new">
<Plus aria-hidden />
Expand Down
Loading
Loading
0