Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mcnuttandrew committed Jan 16, 2025
1 parent aa99f0c commit ecae091
Show file tree
Hide file tree
Showing 9 changed files with 1,361 additions and 156 deletions.
70 changes: 70 additions & 0 deletions apps/lil-buddy/netlify/functions/ai-call.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import OpenAI from "openai";
// const { GoogleGenerativeAI } = require("@google/generative-ai");
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_KEY as string);
const model = genAI.getGenerativeModel({ model: "gemini-pro" });
import Anthropic from "@anthropic-ai/sdk";

const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_KEY, // defaults to process.env["ANTHROPIC_API_KEY"]
});

export function errorResponse(callback, err) {
console.error(err);

callback(null, {
statusCode: 500,
body: JSON.stringify({ error: err }),
});
}

const engines = {
google: (prompt: string) => model.generateContent(prompt),
openai: (prompt: string) =>
openai.chat.completions.create({
messages: [{ role: "user", content: prompt }],
// model: "gpt-3.5-turbo",
n: 1,
temperature: 0,
model: "gpt-4o",
// model: "gpt-4",
// model: "gpt-4-turbo-preview",
}),
anthropic: (prompt: string) =>
anthropic.messages.create({
// model: "claude-3-opus-20240229",
model: "claude-3-haiku-20240307",
// model: "claude-3-sonnet-20240229",
max_tokens: 256,
temperature: 0,
messages: [{ role: "user", content: prompt }],
}),
};

export const handler = async (event, _context, callback) => {
let promptInput;
try {
promptInput = event.body;
} catch (e) {
console.log(e);
errorResponse(callback, "Bad submit");
return;
}
const engine = event.queryStringParameters.engine;
if (!engine) {
errorResponse(callback, "No engine");
return;
}
if (typeof engine !== "string" || !engines[engine]) {
errorResponse(callback, "Bad engine");
return;
}
const result = await engines[engine](promptInput);

console.log(result);
callback(null, { statusCode: 200, body: JSON.stringify(result) });
};
3 changes: 3 additions & 0 deletions apps/lil-buddy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@tsconfig/svelte": "^5.0.2",
"@types/node": "^20.14.1",
"autoprefixer": "^10.4.16",
"netlify-cli": "^18.0.1",
"postcss": "^8.4.32",
"svelte": "^4.2.3",
"svelte-check": "^3.6.0",
Expand All @@ -30,11 +31,13 @@
"vitest": "^1.1.3"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.33.1",
"color-buddy-color-lists": "*",
"color-buddy-color-namer": "*",
"color-buddy-palette": "*",
"color-buddy-palette-lint": "*",
"monaco-editor": "^0.45.0",
"openai": "^4.78.1",
"tailwindcss": "^3.3.5"
}
}
260 changes: 137 additions & 123 deletions apps/lil-buddy/src/lib/api-calls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Palette } from "color-buddy-palette";
import * as Json from "jsonc-parser";
import { Color, makePalFromString } from "color-buddy-palette";
import LintWorker from "./lint-worker.worker?worker";
import { summarizePal } from "./utils";
import type { WorkerCommand } from "./worker-types";
Expand Down Expand Up @@ -88,134 +89,147 @@ const engineToScaffold = {
none: openAIScaffold,
};

// supports the add color search function
export function suggestAdditionsToPalette(
palette: Palette,
engine: Engine,
search: string
): Promise<string[]> {
const body = JSON.stringify({
...palToString(palette),
name: palette.name,
search,
});

return engineToScaffold[engine]<string>(`get-color-suggestions`, body, true);
}

export function suggestPal(prompt: string, engine: Engine) {
const body = JSON.stringify({ prompt });
return engineToScaffold[engine]<SimplePal>(`suggest-a-pal`, body, true);
}

export function suggestNameForPalette(
palette: Palette,
engine: Engine
): Promise<string[]> {
const body = JSON.stringify({ ...palToString(palette) });
return engineToScaffold[engine]<string>(`suggest-name`, body, true);
async function genericCall(prompt: string, engine: Engine) {
return engineToScaffold[engine]<string>(`ai-call`, prompt, true);
}

export function suggestContextualAdjustments(
prompt: string,
currentPal: Palette,
engine: Engine
export function generateTest(
lintProgram: string,
engine: Engine,
testResult: "passes" | "fails"
) {
const adjustedPrompt = `${summarizePal(currentPal)}\n\n${prompt}`;
const body = JSON.stringify({
prompt: adjustedPrompt,
...palToString(currentPal),
});
return engineToScaffold[engine]<SimplePal>(
`suggest-contextual-adjustments`,
body,
true
);
}

export function suggestFix(currentPal: Palette, msg: string, engine: Engine) {
const body = JSON.stringify({
...palToString(currentPal),
error: msg,
context: summarizePal(currentPal),
});
return engineToScaffold[engine]<SimplePal>(`suggest-fix`, body, true);
}

export function suggestLint(lintPrompt: string, engine: Engine) {
const body = JSON.stringify({ lintPrompt });
return engineToScaffold[engine]<string>(`suggest-lint`, body, true);
}

export function suggestLintMetadata(lintProgram: string, engine: Engine) {
const body = JSON.stringify({ lintProgram });
return engineToScaffold[engine]<{
description: string;
failMessage: string;
name: string;
}>(`suggest-lint-metadata`, body, true);
}

// instantiate the worker
const ViteWorker = new LintWorker();

// send and receive messages from the worker
const randID = () => Math.random().toString(36).substring(7);
function workerDispatch() {
const waitingCallbacks: { [key: string]: (msg: string) => void } = {};
ViteWorker.addEventListener("message", (msg: MessageEvent<WorkerCommand>) => {
const { id, content } = msg.data;
if (id && waitingCallbacks[id]) {
waitingCallbacks[id](content);
delete waitingCallbacks[id];
return genericCall(
`
You are an expert tester for a new color palette generator. You are given a lint program that checks for a property. Generate a test that ${testResult}. The test should consist of a new color palette.
The lint program is: ${lintProgram}.
Give your answer with the following type {"colors": hexString[], "background": hexString}.
Do not offer any preamble. Only given the json response. You should be creative in your selection of colors.
Your test palette that ${testResult} for this program as follows:`,
engine
).then((x) => {
if (x.length === 0) {
throw new Error("No suggestions");
}
});

return async function caller(msg: WorkerCommand) {
const id = msg.id;
ViteWorker.postMessage(msg);
return new Promise<string>((resolve) => {
waitingCallbacks[id] = resolve;
});
};
}
const dispatch = workerDispatch();

function prepPalForWorker(pal: Palette) {
return JSON.stringify({
...pal,
background: pal.background.toString(),
colors: pal.colors.map((x) => ({
...x,
color: x.toString(),
})),
return x.map((el: any) => makePalFromString(el.colors, el.background))[0];
});
}

export function lint(pal: Palette, computeMessage: boolean) {
// this may be too deep a copy?
return dispatch({
type: computeMessage ? "run-lint" : "run-lint-no-message",
content: prepPalForWorker(pal),
id: randID(),
}).then((x) => {
return x as unknown as any[];
});
}

export function loadLints() {
return dispatch({ type: "load-lints", content: "", id: randID() });
}

export function suggestMonteFix(pal: Palette, lintIds: string[]) {
return dispatch({
type: "monte-carlo-fix",
content: JSON.stringify({
palString: prepPalForWorker(pal),
lintIds,
}),
id: randID(),
}).then((x) => {
return x as unknown as any[];
});
export function suggestLint(lintPrompt: string, engine: Engine) {
const prompt = `
# Color check language documentation
A valid program starts with an expression, as described below. The only allowed syntax is listed here.
Expressions
Conjunction | Quantifier | Comparison | Boolean
Value = Variable | Number | Boolean | ColorString
Notes:
- ColorString: strings defined using web valid hex colors, such as #ff00ff
- Variable is a string that is a variable name
Predefined variables: colors, background
Conjunctions:
{and: [EXPR, EXPR, ...]}
{or: [EXPR, EXPR, EXPR]}
{not: EXPR}
Quantifiers
{all: {varbs: Variable, predicate: EXPR, where?: EXPR, in: Variable | Map}}
{exist: {varbs: Variable, predicate: EXPR, where?: EXPR, in: Variable | Map}}
Notes:
- varbs each listed variable into the scope, as well as variables like index(a) for a variable a
- To slice you might do something like {...where: {"<" : {"left": "index(a)", "right": 3}}}. This is important! There is no other way to subset or filter for quantifiers. THE IN CLAUSE MUST ONLY HAVE A VARIABLE AND THING ELSE.
Comparisons:
{"==": {left: Value, right: Value}}
{"!=": {left: Value, right: Value}}
{"<": {left: Value, right: Value}}
{">": {left: Value, right: Value}}
{"similar": {left: Value, right: Value, threshold: Number}}
Notes
- Threshold has units of dE2000s, so 10 would be glance-ably different.
Math Operations:
{"\*": {left: Number | Variable, right: Number | Variable}}
{"+": {left: Number | Variable, right: Number | Variable}}
{"/": {left: Number | Variable, right: Number | Variable}}
{"-": {left: Number | Variable, right: Number | Variable}}
{absDiff: {left: Number | Variable, right: Number | Variable}}
{"%": {left: Number | Variable, right: Number | Variable}}
Value Comparisons:
{dist: {left: Color | Variable, right: Color | Variable}, space: COLOR_SPACE }
{deltaE: {left: Color | Variable, right: Color | Variable}, algorithm: '2000' | etc }
{contrast: {left: Color | Variable, right: Color | Variable}, algorithm: | "APCA" | "WCAG21" | "Michelson" | "Weber" | "Lstar" | "DeltaPhi"}
Aggregates
{count: Variable | Number[] | Color[]}
{sum: Variable | Number[]}
{min: Variable | Number[]}
{max: Variable | Number[]}
{mean: Variable | Number[]}
{std: Variable | Number[]}
{first: Variable | Number[]}
{last: Variable | Number[]}
{extent: Variable | Number[]}
Color Manipulations:
{toSpace: Variable, space: 'lab' | 'hsl' | etc, channel: 'a' | 'b' | 'l' | etc}
{cvdSim: Variable, type: 'protanomaly' | 'deuteranomaly' | 'tritanopia' | 'grayscale'}
{name: Variable}
{inGamut: Variable | Color}
{isTag: Variable | Color, value: string}
Notes
- toSpace has a shorthand like {"rgb.b": Variable}
- When comparing colors, it can be helpful to switch color spaces. For instance, to check if a value is blue you might switch it to HSL and check if the hue is in a certain range.
Maps:
{map: Variable | Value[], func: Operation, varb: Variable}
{sort: Variable | Value[], func: Operation, varb: Variable}
{filter: Variable | Value[], func: EXPR, varb: Variable}
{reverse: Variable | Value[]}
{speed: Variable | Value[]}
# LintProgram Examples
Example prompt: All colors should be color blind friendly for deuteranopia
Example Result:
{"not": {"exist": {
"in": "colors",
"varbs": ["a", "b"],
"predicate": {
"!=": {"left": { "cvdSim": "a", "type": "deuteranopia" }, "right": { "cvdSim": "b", "type": "deuteranopia" }
}}}}}
Example prompt: Colors should not be extreme
Example Result:
{"all": {
"in": "colors",
"varb": "a",
"predicate": {
"all": {"in": ["#000000", "#ffffff"], "varb": "b",
"predicate": { "!=": { "left": "a", "right": "b" } },
}}}}
# Identity
You are a color expert and domain-specific language programmer. You take in a lint prompt and suggest a lint using the above programming language. You are very good at your job you do not make mistakes.
# Task and output format
Given a lint prompt, suggest a lint using the color check linting language. Your response should be a JSON object written in the above JSON DSL. You must be explicit in your response and include all necessary information. If a list of colors is suggested you should guess what those colors are and give explicit values.
You should include an extra field in your output called "comments" that explains your reasoning for the lint. This is a string.
Prompt: ${JSON.stringify(lintPrompt)}
Your response: `;
return engineToScaffold[engine]<string>(`suggest-lint`, prompt, true);
}
Loading

0 comments on commit ecae091

Please sign in to comment.