diff --git a/src/app/App.tsx b/src/app/App.tsx
index 5fad623..d9f6ad4 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -35,10 +35,6 @@ const Sidebar = () => (
Settings
-
- Twitter
- Discord
-
diff --git a/src/app/_components/TableInput.tsx b/src/app/_components/TableInput.tsx
deleted file mode 100644
index 33bbbfa..0000000
--- a/src/app/_components/TableInput.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { parseHTML, indexOf, slice } from "../_util"
-import { Table } from "./Table"
-
-interface TableInputProps {
- class?: string
- value: string[][]
- onChange?: (value: string[][]) => void
-}
-
-/**
- * Simple spreadsheet-like table input
- */
-export const TableInput = ({ class: className = "", value }: TableInputProps) => {
- return (
-
-
- {value.map(row => (
-
- {row.map(cell => (
-
- {cell}
- |
- ))}
-
- ))}
-
-
- )
-}
-
-const restoreFocus = e => (e.target.querySelector("[aria-selected=true]") ?? e.target.querySelector("td"))?.focus()
-
-const updateSelection = e => {
- e.target
- .closest("table")!
- .querySelectorAll("[aria-selected=true]")
- .forEach(td => ((td.tabIndex = -1), td.removeAttribute("aria-selected")))
- e.target.setAttribute("aria-selected", "true")
- e.target.tabIndex = 0
-}
-
-const handleMouseDown = e => {
- if (e.target instanceof HTMLTableCellElement) {
- e.preventDefault()
- window.getSelection()?.setPosition(e.target, 0)
- }
-}
-
-const handleKeyDown = e => {
- if (e.ctrlKey || e.metaKey || e.altKey || e.key === "Tab" || e.key === "Shift") return
-
- const { td, tr, x } = findCell(e)
-
- // Move focus
- if (e.key.startsWith("Arrow") || e.key === "Enter") {
- // TODO: Handle selection
- if (e.shiftKey) return
-
- e.preventDefault()
-
- switch (e.key) {
- case "ArrowLeft":
- return td.previousElementSibling?.focus()
- case "ArrowRight":
- return td.nextElementSibling?.focus()
- case "ArrowUp":
- return tr.previousElementSibling?.children[x]?.focus()
- case "ArrowDown":
- case "Enter":
- return tr.nextElementSibling?.children[x]?.focus()
- }
- }
-
- // Delete cell content
- const selection = window.getSelection()
- if (selection?.anchorOffset === 0) {
- selection.selectAllChildren(td)
- }
-}
-
-const findCell = (e: any) => {
- const td = e.target.closest("td")
- const tr = td.closest("tr")
- const x = indexOf(tr.children, td)
- const y = indexOf(tr.parentElement.children, tr)
-
- return { td, tr, x, y }
-}
-
-const handlePaste = e => {
- e.preventDefault()
-
- if (e.target instanceof HTMLTableCellElement) {
- const html = e.clipboardData?.getData("text/html")
- const text = e.clipboardData?.getData("text/plain")
- const data = html
- ? [...parseHTML(html).querySelectorAll("tr")].map(tr => [...tr.querySelectorAll("td")].map(td => td.textContent ?? ""))
- : text.split("\n").map(r => r.split("\t"))
-
- if (data) {
- const { tr, x, y } = findCell(e)
-
- slice(tr.parentElement.children, y, y + data.length).forEach((tr, i) => {
- slice(tr.children, x, x + data[i].length).forEach((td, j) => {
- td.textContent = data[i][j]
- })
- })
- }
- }
-}
diff --git a/src/app/_components/index.tsx b/src/app/_components/index.tsx
index 851bf6b..369eef8 100644
--- a/src/app/_components/index.tsx
+++ b/src/app/_components/index.tsx
@@ -18,6 +18,5 @@ export { Resizable } from "./Resizable"
export { SearchField } from "./SearchField"
export { Select } from "./Select"
export { Table } from "./Table"
-export { TableInput } from "./TableInput"
export { Tabs } from "./Tabs"
export { Value } from "./Value"
diff --git a/src/app/_util/index.ts b/src/app/_util/index.ts
index 4716df6..4850209 100644
--- a/src/app/_util/index.ts
+++ b/src/app/_util/index.ts
@@ -3,5 +3,4 @@ export { err } from "./err"
export { dedent, fmtCount, fmtSize, fmtDuration, fmtDate, humanize } from "./fmt"
export { jsonLines } from "./jsonLines"
export { basename } from "./path"
-export { parseHTML } from "./parseHTML"
export { template, parseVars } from "./template"
diff --git a/src/app/_util/parseHTML.ts b/src/app/_util/parseHTML.ts
deleted file mode 100644
index f746457..0000000
--- a/src/app/_util/parseHTML.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-export const parseHTML = (input: string, clean = false) => {
- const doc = new DOMParser().parseFromString(input, "text/html")
-
- if (clean) {
- // Strip out scripts, styles and javascript links
- doc.querySelectorAll("script, link, style, a[href^='javascript:']").forEach(el => el.remove())
-
- // Remove style attribute
- doc.querySelectorAll("[style]").forEach(el => el.removeAttribute("style"))
-
- // Then for all elements (but in reverse)
- Array.from(doc.querySelectorAll("*"))
- .reverse()
- .forEach(el => {
- // Remove all data-* attributes
- for (const k of el.getAttributeNames()) {
- if (k.startsWith("data-")) el.removeAttribute(k)
- }
-
- // Remove if empty
- if (!el.innerHTML.trim()) el.remove()
- })
- }
-
- return doc
-}
diff --git a/src/app/router.ts b/src/app/router.ts
index 2cda620..2eb5106 100644
--- a/src/app/router.ts
+++ b/src/app/router.ts
@@ -5,8 +5,6 @@ import { QuickTools } from "./quick-tools/QuickTools"
import { CreateTool } from "./quick-tools/CreateTool"
import { QuickTool } from "./quick-tools/QuickTool"
import { EditTool } from "./quick-tools/EditTool"
-import { Workflows } from "./workflow/Workflows"
-import { Workflow } from "./workflow/Workflow"
import { SearchModels } from "./models/SearchModels"
import { DownloadManager } from "./models/DownloadManager"
import { Models } from "./models/Models"
@@ -23,8 +21,6 @@ const routes = [
{ path: "/quick-tools/new", component: CreateTool },
{ path: "/quick-tools/:id", component: QuickTool },
{ path: "/quick-tools/:id/edit", component: EditTool },
- NEXT && { path: "/workflows", component: Workflows },
- NEXT && { path: "/workflows/:id", component: Workflow },
{ path: "/playground", component: Playground },
{ path: "/models", component: SearchModels },
{ path: "/models/downloads", component: DownloadManager },
diff --git a/src/app/workflow/Workflow.tsx b/src/app/workflow/Workflow.tsx
deleted file mode 100644
index 79b729c..0000000
--- a/src/app/workflow/Workflow.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { createContext } from "preact"
-import { useContext } from "preact/hooks"
-import { useSignal } from "@preact/signals"
-import { Clock, Cloud, SquareMousePointer, Play, Repeat2, TableProperties, Wand2 } from "lucide"
-import { Field, Form, FormGrid, Icon, IconButton, Page } from "../_components"
-import { err, fmtDuration, humanize } from "../_util"
-import { examples } from "./_examples"
-import { handlers, runWorkflow, stepDefaults } from "./runner"
-
-const SelectionContext = createContext({ selection: null, select: null } as any)
-
-export const Workflow = ({ params }) => {
- const workflow = examples.find(w => w.id === +params.id) ?? err("Unknown workflow") // TODO: useApi()
-
- const ctx = {
- selection: useSignal(null as any),
- select: step => (ctx.selection.value = step),
- }
-
- return (
-
-
- runWorkflow(workflow)} />
-
-
-
- {!workflow.steps.length && Add steps from the right to build your workflow.
}
-
-
- {workflow.steps.map(s => (
-
- ))}
-
-
-
-
-
-
-
-
- )
-}
-
-const Step = ({ step }) => {
- const { selection, select } = useContext(SelectionContext)
- const [[kind, props]] = Object.entries(step)
- const [title, icon, subtitle] = renderers[kind](props)
-
- const selected = selection?.value === step
-
- return (
- select(step)}
- draggable
- >
-
-
-
-
-
- )
-}
-
-const PropsPane = ({ step }) => (
- <>
-
- {step ? Object.keys(step)[0] : "No step selected"}
-
-
-
- {step ? (
-
- ) : (
-
Click on a step to edit its properties, or drag a step from the sidebar to add it to the workflow.
- )}
-
- >
-)
-
-const StepCatalog = () => (
-
-
Step Catalog
-
-
- {Object.keys(handlers).map(k => (
-
- ))}
-
-
-)
-
-type Renderers = {
- [k in keyof typeof handlers]: (s: (typeof stepDefaults)[k]) => [string, typeof Clock, string?]
-}
-
-const renderers: Renderers = {
- wait: s => [`Wait ${fmtDuration(s.duration)}`, Clock],
- generate: s => ["Generate", Wand2],
- instruction: s => ["Instruction", Wand2, s.instruction],
- http_request: s => ["HTTP Request", Cloud, s.url],
- query_selector: s => ["Query Selector", SquareMousePointer, s.selector],
- // extract: s => ["Extract", TableProperties, s.fields.join(", ")],
- // for_each: s => ["For Each", Repeat2],
-}
diff --git a/src/app/workflow/Workflows.tsx b/src/app/workflow/Workflows.tsx
deleted file mode 100644
index ad4a583..0000000
--- a/src/app/workflow/Workflows.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Plus } from "lucide"
-import { Alert, IconButton, Link, Page, Table } from "../_components"
-import { examples } from "./_examples"
-import { router } from "../router"
-
-export const Workflows = () => {
- const createWorkflow = () => {
- const id = Date.now()
- examples.push({ id, name: "New Workflow", steps: [] })
- router.navigate(`/workflows/${id}`)
- }
-
- return (
-
-
-
-
-
-
-
- This feature is experimental.
- No changes are saved to the database
-
-
-
-
-
- Name |
- Kind |
-
-
-
- {examples.map(w => (
-
-
-
- {w.name}
-
- |
- Manual |
- {/* TODO: Automatic when the first action is a trigger/cron */}
-
- ))}
-
-
-
-
- )
-}
diff --git a/src/app/workflow/_examples.ts b/src/app/workflow/_examples.ts
deleted file mode 100644
index a2f52aa..0000000
--- a/src/app/workflow/_examples.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-export const examples = [
- {
- id: 1,
- name: "Chuck Norris Jokes Explained",
- steps: [
- { http_request: { method: "GET", url: "https://api.chucknorris.io/jokes/random" } },
- { instruction: { instruction: "Extract the value part." } },
- { instruction: { instruction: "Explain the joke, reason step by step." } },
- ],
- },
-
- {
- id: 2,
- name: "Local Llama Top Posts",
- steps: [
- { http_request: { method: "GET", url: "https://old.reddit.com/r/LocalLLaMA/top/" } },
- { query_selector: { selector: ".thing" } },
- { instruction: { instruction: "Extract the title and url and respond with valid JSON." } },
- ],
- },
-
- {
- id: 3,
- name: "Scrape Hacker News Jobs",
- steps: [
- { wait: { duration: 2 } },
- { http_request: { method: "GET", url: "https://hn.svelte.dev/jobs/1" } },
- { query_selector: { selector: "main article", limit: 5 } },
- {
- instruction: {
- instruction: "Extract the job title, company and expected skills for the role. Respond with valid JSON.",
- },
- },
- ],
- },
-]
diff --git a/src/app/workflow/runner.ts b/src/app/workflow/runner.ts
deleted file mode 100644
index b19a221..0000000
--- a/src/app/workflow/runner.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { signal } from "@preact/signals"
-import { generate } from "../_hooks/useGenerate"
-import { parseHTML } from "../_util"
-
-export type Step = (typeof stepDefaults)[keyof typeof stepDefaults]
-
-// TODO: this should be retrieved from the server
-export const stepDefaults = {
- wait: { duration: 1 },
- generate: {}, // TODO: opts for sampling, etc.
- instruction: { instruction: "" },
- http_request: { method: "GET", url: "" },
- query_selector: { selector: "", limit: 2, clean: true },
-}
-
-type Handlers = {
- [k in keyof typeof stepDefaults]: (opts: (typeof stepDefaults)[k], input: string) => string | Promise
-}
-
-// TODO: this should be implemented on the server side
-export const handlers: Handlers = {
- wait: ({ duration }) => {
- return new Promise(resolve => setTimeout(resolve, duration * 1000))
- },
-
- generate: (opts, input) => {
- const result = signal("")
- const status = signal(null)
-
- return generate({ ...opts, prompt: input }, result, status)
- },
-
- instruction: ({ instruction, ...opts }, input) => {
- return handlers.generate(opts, `ASSISTANT: ${input}\n\nUSER: ${instruction}\n\nASSISTANT: Sure!`)
- },
-
- http_request: ({ url }) => {
- return fetch("/api/proxy", { method: "POST", body: JSON.stringify({ url }) }).then(res => res.text())
- },
-
- query_selector: ({ selector, limit = 2, clean = true }, input) => {
- return [...parseHTML(input, clean).querySelectorAll(selector)]
- .slice(0, limit)
- .map(el => el.innerHTML)
- .join("")
- },
-}
-
-export const runWorkflow = async workflow => {
- const runStep = async (step, input) => {
- for (const k in handlers) {
- if (k in step) {
- const res = await handlers[k](step[k], input)
-
- return res
- }
- }
-
- throw new Error(`No handler found for step ${JSON.stringify(step)}`)
- }
-
- let input = null
- for (const step of workflow.steps) {
- input = await runStep(step, input)
- }
-}