From c1ca66e4a81be093aab769d209262c4bb96bdb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adelaide=20=E3=81=82=E3=81=A7=20Fisher?= Date: Fri, 7 Feb 2025 00:59:31 +0100 Subject: [PATCH] include context --- bun.lock | 8 +- src/feature/chat/chat.tsx | 11 ++- .../inference/adapters/adapter.test.ts | 13 ++- src/feature/inference/adapters/anthropic.ts | 15 ++- src/feature/inference/atoms.ts | 2 +- src/feature/router/Explore.tsx | 95 ++++++++++++------- src/feature/router/NewExperiment.tsx | 33 +++++-- src/feature/ui/ListBox.tsx | 1 - src/feature/ui/Popover.tsx | 7 +- src/feature/ui/Select.tsx | 30 +++--- src/feature/ui/Slider.tsx | 3 +- src/feature/ui/Switch.tsx | 11 ++- src/feature/ui/shared.tsx | 3 +- src/style/form.ts | 2 +- src/style/index.tsx | 24 +++-- src/style/mixins.ts | 4 + src/style/palette.ts | 4 + src/types.ts | 7 +- 18 files changed, 185 insertions(+), 88 deletions(-) diff --git a/bun.lock b/bun.lock index 28b32b7..b5de725 100644 --- a/bun.lock +++ b/bun.lock @@ -522,11 +522,11 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "react": ["react@19.1.0-canary-d85cf3e5-20250205", "", {}, "sha512-jpLQ+iB3ky8ik5XGOIeIOQI3tK9XodDyuU09zIjwgb2nADvDgo45tNuU9gII05LHetooQO4ScekTjFHr0hgKRw=="], + "react": ["react@19.1.0-canary-ff628334-20250205", "", {}, "sha512-lJUElq6aqp9uYWXx+yGb1yzVU/vvdu1ffQ+Ly56jlKPrgJQeZvX3dTmSjIcbTJJGq9OhvoRWz/0PUC1r3lx0xw=="], "react-aria": ["react-aria@3.37.0", "", { "dependencies": { "@internationalized/string": "^3.2.5", "@react-aria/breadcrumbs": "^3.5.20", "@react-aria/button": "^3.11.1", "@react-aria/calendar": "^3.7.0", "@react-aria/checkbox": "^3.15.1", "@react-aria/color": "^3.0.3", "@react-aria/combobox": "^3.11.1", "@react-aria/datepicker": "^3.13.0", "@react-aria/dialog": "^3.5.21", "@react-aria/disclosure": "^3.0.1", "@react-aria/dnd": "^3.8.1", "@react-aria/focus": "^3.19.1", "@react-aria/gridlist": "^3.10.1", "@react-aria/i18n": "^3.12.5", "@react-aria/interactions": "^3.23.0", "@react-aria/label": "^3.7.14", "@react-aria/link": "^3.7.8", "@react-aria/listbox": "^3.14.0", "@react-aria/menu": "^3.17.0", "@react-aria/meter": "^3.4.19", "@react-aria/numberfield": "^3.11.10", "@react-aria/overlays": "^3.25.0", "@react-aria/progress": "^3.4.19", "@react-aria/radio": "^3.10.11", "@react-aria/searchfield": "^3.8.0", "@react-aria/select": "^3.15.1", "@react-aria/selection": "^3.22.0", "@react-aria/separator": "^3.4.5", "@react-aria/slider": "^3.7.15", "@react-aria/ssr": "^3.9.7", "@react-aria/switch": "^3.6.11", "@react-aria/table": "^3.16.1", "@react-aria/tabs": "^3.9.9", "@react-aria/tag": "^3.4.9", "@react-aria/textfield": "^3.16.0", "@react-aria/tooltip": "^3.7.11", "@react-aria/utils": "^3.27.0", "@react-aria/visually-hidden": "^3.8.19", "@react-types/shared": "^3.27.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-u3WUEMTcbQFaoHauHO3KhPaBYzEv1o42EdPcLAs05GBw9Q6Axlqwo73UFgMrsc2ElwLAZ4EKpSdWHLo1R5gfiw=="], - "react-dom": ["react-dom@19.1.0-canary-d85cf3e5-20250205", "", { "dependencies": { "scheduler": "0.26.0-canary-d85cf3e5-20250205" }, "peerDependencies": { "react": "19.1.0-canary-d85cf3e5-20250205" } }, "sha512-AxrNK5Kt4Cb9007Cvt6WOKnrIDWQGHQfN1C/HR3k+UA0h8oAJNGDrsHxOalx9Fu/Iwdh55Zk5FbUJMjCw+CSMg=="], + "react-dom": ["react-dom@19.1.0-canary-ff628334-20250205", "", { "dependencies": { "scheduler": "0.26.0-canary-ff628334-20250205" }, "peerDependencies": { "react": "19.1.0-canary-ff628334-20250205" } }, "sha512-O/FFWzQNHqKLavROUliPXDPAwJcjwDcFbZmg2YfsWtSDJj1pRFTNzYvOUChC/hgBQiIM5rAiznQjBw2D9naH1g=="], "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], @@ -548,7 +548,7 @@ "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], - "scheduler": ["scheduler@0.26.0-canary-d85cf3e5-20250205", "", {}, "sha512-ivuko9JqWNVtK/Gw17V54bRdMZNNr55RsjUJtUJY2LNL4Wvt7P4EVD9GQRq5K2PvMBSYXYJ/2of2J6Btdekxcg=="], + "scheduler": ["scheduler@0.26.0-canary-ff628334-20250205", "", {}, "sha512-U0tZuylPZVLsLMHfoqXdURMPdHXd8nBqqJXIQCiT0NQqY5wxc4I+Wsg1XFAY2WY0WRh6U0c3WMljp2JvRjXKow=="], "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="], @@ -582,7 +582,7 @@ "turbo-stream": ["turbo-stream@2.4.0", "", {}, "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="], - "typescript": ["typescript@5.8.0-dev.20250205", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-ElowVOyGLZ8RSwHA7UZF8M3n4iT/QaU6jCF9N9z2vT/lTjvM3K2eFliQsmF6Iaz9vOhFVUNfe1Mr8aXnpMXiHA=="], + "typescript": ["typescript@5.8.0-dev.20250206", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-lnf72PhBy8HHFElX7VbfSwtG86LdWnE+p7h5KjlK459t6WYtW4RL/icVhebS4GM++AmJMGpcZLCyWSrF8NCXHg=="], "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], diff --git a/src/feature/chat/chat.tsx b/src/feature/chat/chat.tsx index 00fd853..2e81257 100644 --- a/src/feature/chat/chat.tsx +++ b/src/feature/chat/chat.tsx @@ -12,7 +12,7 @@ import { deepEqual } from "../../utils"; import { useHandlers } from "../../utils/keyboard"; import { useScrollToTop } from "../../utils/scroll"; import { View, collapsedAtom } from "../ui/view"; -import type { _Message, Experiment, ExperimentWithMeta, Message } from "../../types"; +import type { _Message, Experiment, ExperimentWithMeta, Message, Role } from "../../types"; const baseHeight = bs(6); export const ChatContainer = styled.div` @@ -22,7 +22,7 @@ export const ChatContainer = styled.div` code { background-color: ${(p) => (p.isDarkMode ? Palette.white + "50" : Palette.black + "20")}; - border-radius: ${bs(1 / 8)}; + border-radius: ${bs(Palette.borderSpan)}; } *:not(pre) > code { @@ -59,7 +59,7 @@ const getAlign = (fromServer: boolean, experimentLayout: Store["experimentLayout }; export const MessageComponent = styled.article<{ - role: "assistant" | "developer" | "info" | "error" | "system" | "tool" | "user"; + role: Role; contentType?: string; ioType?: "input" | "output"; isSelected?: boolean; @@ -161,6 +161,11 @@ export const MessageComponent = styled.article<{ border-color: ${Palette.red}; `); } + if (role === "context") { + styles.push(css` + border-color: ${Palette.teal}; + `); + } if (isSelected) { if (isDarkMode) { styles.push(css` diff --git a/src/feature/inference/adapters/adapter.test.ts b/src/feature/inference/adapters/adapter.test.ts index c5ba39e..2e7adb0 100644 --- a/src/feature/inference/adapters/adapter.test.ts +++ b/src/feature/inference/adapters/adapter.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "bun:test"; import { experimentToAnthropic } from "./anthropic"; import templates from "../../../../fixtures/templates.json"; +import anthropicTool from "../../../../fixtures/tools/stockPrice.json"; import { experimentToOpenai } from "./openai"; import { experimentToMistral } from "./mistral"; @@ -10,13 +11,17 @@ for (const [provider, adapter] of [ ["openai", experimentToOpenai], ] as const) { describe(`inference/${provider}`, () => { - it("tool use experiment", () => { - const result = adapter(templates["Tool use"].messages); + it("tool use experiment", async () => { + const result = await adapter(templates["Tool use"].messages); expect(result).toMatchSnapshot(); }); - it("sample", () => { - const result = adapter(templates["Sample chat"].messages); + it("sample", async () => { + const result = await adapter(templates["Sample chat"].messages); expect(result).toMatchSnapshot(); }); + // it("anthropic tool format", async () => { + // const result = await adapter([{ role: "tool", content: anthropicTool }]); + // expect(result).toMatchSnapshot(); + // }); }); } diff --git a/src/feature/inference/adapters/anthropic.ts b/src/feature/inference/adapters/anthropic.ts index 639b144..57faac9 100644 --- a/src/feature/inference/adapters/anthropic.ts +++ b/src/feature/inference/adapters/anthropic.ts @@ -6,11 +6,12 @@ import type { } from "@anthropic-ai/sdk/resources/index.mjs"; import type { Message } from "../../../types"; import { tokenLimit } from "../../../const"; +import { createContextFromFiles, iterateDir } from "../../router/Explore"; -export const experimentToAnthropic = ( +export async function experimentToAnthropic( experiment: Message[], { max_tokens = tokenLimit }: { max_tokens?: number } = {}, -): MessageCreateParams | MessageCreateParamsNonStreaming => { +): Promise { let system = ""; const messages: MessageParam[] = []; const tools: Tool[] = []; @@ -18,6 +19,7 @@ export const experimentToAnthropic = ( if (role === "system") { system += `${content} `; + continue; } if (role === "user" || role === "assistant") { if (typeof content === "object") { @@ -26,6 +28,13 @@ export const experimentToAnthropic = ( if (typeof content === "string") { messages.push({ role, content }); } + continue; + } + if (role === "context") { + const directory = content.directory as string; + const files = await iterateDir(directory); + const context = await createContextFromFiles(files, directory); + messages.push({ role: "user", content: context }); } if (role === "tool" && !fromServer) { if (typeof content === "object") { @@ -49,4 +58,4 @@ export const experimentToAnthropic = ( max_tokens, stream: true, }; -}; +} diff --git a/src/feature/inference/atoms.ts b/src/feature/inference/atoms.ts index 49cfd1d..087b47d 100644 --- a/src/feature/inference/atoms.ts +++ b/src/feature/inference/atoms.ts @@ -168,7 +168,7 @@ export const runExperimentAsAnthropic = entangledAtom( return; } - const { stream, ...experimentAsAnthropic } = experimentToAnthropic(experiment); + const { stream, ...experimentAsAnthropic } = await experimentToAnthropic(experiment); const anthropic = new Anthropic({ apiKey: resolvedToken, diff --git a/src/feature/router/Explore.tsx b/src/feature/router/Explore.tsx index a2769b7..264bf9f 100644 --- a/src/feature/router/Explore.tsx +++ b/src/feature/router/Explore.tsx @@ -1,41 +1,33 @@ -import { type Setter, atom, useAtom } from "jotai"; -import { useEffect } from "react"; +import { atom, useAtom } from "jotai"; +import { readFile, readdir } from "node:fs/promises"; import path from "node:path"; -import { readdir, readFile } from "node:fs/promises"; +import { useEffect } from "react"; -import { navigateAtom, titleOverrideAtom } from "."; -import templates from "../../../fixtures/templates.json"; -import { filenames, importsRegistry, selectedChat } from "../../atoms/client"; -import { experimentAtom, isNavPanelOpenAtom, layoutAtom, templatesAtom } from "../../atoms/common"; -import { type Config, ConfigRenderer } from "../ui/ConfigRenderer"; -import { collapsedAtom, View } from "../ui/view"; -import type { ExperimentWithMeta } from "../../types"; -import { ExperimentPreview } from "../chat/ExperimentPreview"; -import { selectionAtom } from "../chat/chat"; -import { Actions } from "../ui/Actions"; -import { DesktopOnly } from "../ui/Mobile"; +import { Maybe } from "true-myth"; +import { titleOverrideAtom } from "."; +import { nopeAtom } from "../../atoms/util"; +import { store } from "../../store"; +import { entangledAtom } from "../../utils/entanglement"; +import { getRealm } from "../../utils/realm"; import { SidebarInput } from "../ui/Navigation"; import { Page } from "../ui/Page"; -import { getRealm } from "../../utils/realm"; -import { entangledAtom } from "../../utils/entanglement"; -import { nopeAtom } from "../../atoms/util"; -import { Maybe } from "true-myth"; +import { View, collapsedAtom } from "../ui/view"; -const pwdAtom = getRealm() === "server" ? atom(Bun.env.PWD) : nopeAtom; +export const pwdAtom = getRealm() === "server" ? atom(Bun.env.PWD) : nopeAtom; const dirOverrideAtom = atom(null); -const currentDirAtom = atom((get) => { +export const currentDirAtom = entangledAtom("cwd", atom((get) => { const override = get(dirOverrideAtom); return override ?? get(pwdAtom); -}); +})); const goToAtom = entangledAtom( "gotodir", atom(null, (get, set, dir: string) => { if (getRealm() !== "server") return; const currentDir = Maybe.of(get(currentDirAtom)); - const newPath = currentDir.map(currentDir => path.join(currentDir, dir)); + const newPath = currentDir.map((currentDir) => path.join(currentDir, dir)); if (newPath.isJust) { set(dirOverrideAtom, newPath.value); } @@ -64,6 +56,7 @@ export async function filesInDir(thisPath: string) { return files; } +const includeExtensions = ["ts", "tsx", "md", "json", "yml"]; export async function iterateDir(dir: string, ignore: string[] = [".git"]) { const thisIgnores = [...ignore]; const entries = await readdir(dir, { withFileTypes: true }); @@ -82,38 +75,74 @@ export async function iterateDir(dir: string, ignore: string[] = [".git"]) { for (const entry of entries) { if (thisIgnores.some((ignore) => entry.name === ignore)) continue; if (entry.isDirectory()) { + if (entry.name === "fixtures") continue; directories.push(entry.name); } else { - files.push(`${entry.parentPath}${path.sep}${entry.name}`); + const parts = entry.name.split("."); + const ext = parts[parts.length - 1]; + if (entry.isFile() && includeExtensions.includes(ext)) { + files.push(`${entry.parentPath}${path.sep}${entry.name}`); + } } } directories.sort(); - files.sort(); for (const directory of directories) { files = [...(await iterateDir(path.join(dir, directory), thisIgnores)), ...files]; } + files.sort(); return files; } -const currentDirNameAtom = entangledAtom( - "cwd", +const currentDirContentAtom = entangledAtom( + "cwd-content", atom(async (get) => { const currentDir = get(currentDirAtom); if (currentDir) { - const foo = await iterateDir(currentDir); - for (const url of foo) { - console.log(url.slice(currentDir.length)); + try { + const files = await filesInDir(currentDir); + const entry = path.parse(currentDir); + return { [entry.name]: Object.fromEntries(files.map((file) => [file.name, {}])) }; + } catch { + store.set(dirOverrideAtom, null); } - const files = await filesInDir(currentDir); - const entry = path.parse(currentDir); - return { [entry.name]: Object.fromEntries(files.map((file) => [file.name, {}])) }; } return null; }), ); +export const createContextFromFiles = async (files: string[], basePath: string) => { + let context = ""; + let index = 1; + for (const url of files) { + const relUrl = url.slice(basePath.length); + const content = await readFile(url, "utf-8"); + const thisDoc = ` + + ${relUrl} + + ${content} + + + `; + context += thisDoc; + index += 1; + } + return `\n${context}\n`; +}; + +export const currentDirContextAtom = entangledAtom( + "currentdircontext", + atom(async (get) => { + const currentDir = Maybe.of(get(currentDirAtom)).map(async (currentDir) => { + const files = await iterateDir(currentDir); + return await createContextFromFiles(files, currentDir); + }); + return await currentDir.unwrapOr(async () => null); + }), +); + const SidebarContents = () => { - const [currentDir] = useAtom(currentDirNameAtom); + const [currentDir] = useAtom(currentDirContentAtom); const [_, goToDir] = useAtom(goToAtom); const [collapsed, setCollapsed] = useAtom(collapsedAtom); if (!currentDir) { diff --git a/src/feature/router/NewExperiment.tsx b/src/feature/router/NewExperiment.tsx index 8375181..3f5433e 100644 --- a/src/feature/router/NewExperiment.tsx +++ b/src/feature/router/NewExperiment.tsx @@ -33,15 +33,16 @@ import { modelOptions } from "../inference/types"; import { Actions } from "../ui/Actions"; import { Page } from "../ui/Page"; import { TextArea } from "../ui/TextArea"; +import { currentDirAtom } from "./Explore"; +import { hasBackend } from "../../utils/realm"; -const baseRadius = 3 / 4; const baseMargin = 1 / 2; export const Block = styled.div` display: flex; flex-direction: column; - border-radius: ${bs(baseRadius)}; + border-radius: ${bs(Palette.baseRadius)}; position: sticky; bottom: 0; overflow: clip; @@ -64,8 +65,8 @@ export const Block = styled.div` textarea { padding: 0 ${bs(baseMargin)} ${bs(baseMargin)}; resize: none; - border-bottom-left-radius: ${bs(baseRadius)}; - border-bottom-right-radius: ${bs(baseRadius)}; + border-bottom-left-radius: ${bs(Palette.baseRadius)}; + border-bottom-right-radius: ${bs(Palette.baseRadius)}; &:focus { outline: none; } @@ -112,13 +113,13 @@ export const Block = styled.div` const ActionRow = styled.div` display: flex; - border-top-left-radius: ${bs(baseRadius)}; - border-top-right-radius: ${bs(baseRadius)}; + border-top-left-radius: ${bs(Palette.baseRadius)}; + border-top-right-radius: ${bs(Palette.baseRadius)}; select { - border-top-left-radius: ${bs(baseRadius)}; + border-top-left-radius: ${bs(Palette.baseRadius)}; } button { - border-top-right-radius: ${bs(baseRadius)}; + border-top-right-radius: ${bs(Palette.baseRadius)}; } `; @@ -227,6 +228,22 @@ export const actionsAtom = atom((get) => { counter++; } } + if (hasBackend()){ + config.Actions.push({ + Special: { + buttons: [ + { + label: "Add Context", + action: (set: Setter) => { + const dir = get(currentDirAtom); + set(experimentAtom, [...experiment, { role: "context", content: {directory: dir} }]) + }, + }, + ], + }, + }); + counter++; + } return { config, counter }; }); diff --git a/src/feature/ui/ListBox.tsx b/src/feature/ui/ListBox.tsx index 7065d3a..f07c952 100644 --- a/src/feature/ui/ListBox.tsx +++ b/src/feature/ui/ListBox.tsx @@ -40,7 +40,6 @@ const ListItem = styled.li` props.isFocused ? "white" : props.isSelected ? Palette.black : "#333"}; - font-size: 14px; font-weight: ${(props) => (props.isSelected ? "600" : "normal")}; padding: 8px; display: flex; diff --git a/src/feature/ui/Popover.tsx b/src/feature/ui/Popover.tsx index f344fa0..9aee8db 100644 --- a/src/feature/ui/Popover.tsx +++ b/src/feature/ui/Popover.tsx @@ -4,6 +4,7 @@ import styled from "@emotion/styled"; import * as React from "react"; import { usePopover, DismissButton, Overlay } from "@react-aria/overlays"; import { Palette } from "../../style/palette"; +import { bs } from "../../style"; interface PopoverProps extends Omit { children: React.ReactNode; @@ -16,9 +17,9 @@ const Wrapper = styled.div` top: 100%; z-index: 1; width: 200px; - border: 1px solid lightgray; - border-radius: 4px; - margin-top: 6px; + overflow: hidden; + border-radius: ${bs(Palette.borderCode)}; + margin-top: ${bs(1 / 2)}; box-shadow: 2px 2px 8px ${Palette.buttonShadowDark}21; background: ${Palette.actionableBackground}; `; diff --git a/src/feature/ui/Select.tsx b/src/feature/ui/Select.tsx index e1340fa..9b8075f 100644 --- a/src/feature/ui/Select.tsx +++ b/src/feature/ui/Select.tsx @@ -8,27 +8,28 @@ import { ListBox } from "./ListBox"; import { Popover } from "./Popover"; import { Label, InputContainer } from "./shared"; import { TRIANGLE } from "../../const"; +import { interactive } from "../../style/mixins"; +import { bevelStyle, iSawTheButtonGlow } from "../../style"; +import { withDarkMode, type WithDarkMode } from "../../style/darkMode"; +import { useAtom } from "jotai"; +import { isDarkModeAtom } from "../../atoms/common"; -interface ButtonProps { +type ButtonProps = { isOpen?: boolean; isFocusVisible?: boolean; -} +} & WithDarkMode; const Button = styled.button` appearance: none; - background: ${(props) => (props.isOpen ? "#eee" : "white")}; - border: 1px solid; - padding: 6px 2px 6px 8px; outline: none; - border-color: ${(props) => (props.isFocusVisible ? "seagreen" : "lightgray")}; - border-radius: 4px; display: inline-flex; align-items: center; justify-content: space-between; width: 200px; text-align: left; - font-size: 14px; - color: black; + ${interactive} + ${bevelStyle} + ${(p) => withDarkMode(p.isDarkMode, iSawTheButtonGlow)} `; const Value = styled.span` @@ -48,6 +49,7 @@ export function Select(props: AriaSelectProps) { const { buttonProps } = useButton(triggerProps, ref); const { focusProps, isFocusVisible } = useFocusRing(); + const [isDarkMode] = useAtom(isDarkModeAtom); return ( @@ -55,9 +57,15 @@ export function Select(props: AriaSelectProps) { : null} - {state.isOpen && ( diff --git a/src/feature/ui/Slider.tsx b/src/feature/ui/Slider.tsx index 755504a..c2600d0 100644 --- a/src/feature/ui/Slider.tsx +++ b/src/feature/ui/Slider.tsx @@ -6,7 +6,7 @@ import { useRef } from "react"; import { VisuallyHidden, mergeProps, useFocusRing, useNumberFormatter, useSlider, useSliderThumb } from "react-aria"; import { useAtomValue } from "jotai"; import { Palette } from "../../style/palette"; -import { withDarkMode } from "../../style/darkMode"; +import { withDarkMode, type WithDarkMode } from "../../style/darkMode"; import { TRIANGLE } from "../../const"; import { bs } from "../../style"; import { InputContainer } from "./shared"; @@ -19,6 +19,7 @@ const Container = styled(InputContainer)<{ orientation: "horizontal" | "vertical const LabelContainer = styled.div` display: flex; justify-content: space-between; + margin-bottom: ${bs(1 / 3)}; `; const Track = styled.div<{ orientation: "horizontal" | "vertical"; disabled?: boolean } & WithDarkMode>( diff --git a/src/feature/ui/Switch.tsx b/src/feature/ui/Switch.tsx index a726f0f..683c3e4 100644 --- a/src/feature/ui/Switch.tsx +++ b/src/feature/ui/Switch.tsx @@ -1,6 +1,7 @@ import styled from "@emotion/styled"; import { ellipsis } from "polished"; -import { Button } from "../../style"; +import { bs, Button } from "../../style"; +import { Palette } from "../../style/palette"; export type SwitchValue = boolean | undefined; @@ -11,12 +12,12 @@ const Container = styled.div` ${ellipsis()} border-radius: 0; &:first-of-type { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; + border-top-left-radius: ${bs(Palette.borderCode)}; + border-bottom-left-radius: ${bs(Palette.borderCode)}; } &:last-of-type { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; + border-top-right-radius: ${bs(Palette.borderCode)}; + border-bottom-right-radius: ${bs(Palette.borderCode)}; } } button + button { diff --git a/src/feature/ui/shared.tsx b/src/feature/ui/shared.tsx index 3353897..4fcf25d 100644 --- a/src/feature/ui/shared.tsx +++ b/src/feature/ui/shared.tsx @@ -1,9 +1,10 @@ import styled from "@emotion/styled"; -import { bs } from "../style"; +import { bs } from "../../style"; export const Label = styled.label` display: block; text-align: left; + margin-bottom: ${bs(1 / 3)}; `; export const InputContainer = styled.div` diff --git a/src/style/form.ts b/src/style/form.ts index 61fb4e7..a946a4b 100644 --- a/src/style/form.ts +++ b/src/style/form.ts @@ -9,7 +9,7 @@ type FormProps = { _type?: "success" }; const shared = css` padding: ${bs(1 / 5)} ${bs(1 / 2)}; border: none; - border-radius: 4px; + border-radius: ${Palette.borderCode}; `; const success = css` background-color: ${Palette.successGreen}; diff --git a/src/style/index.tsx b/src/style/index.tsx index 08fdc85..7af47e6 100644 --- a/src/style/index.tsx +++ b/src/style/index.tsx @@ -10,6 +10,7 @@ import { Palette } from "./palette"; import { reset } from "./reset"; import { cover, fontFace } from "polished"; import { withOnMobile } from "./layout"; +import { interactive } from "./mixins"; const config: { fontScale: "majorSecond" | "minorThird" | "majorThird" | "perfectFourth" | "augmentedFourth"; @@ -51,7 +52,7 @@ const shevyStyle = css({ "p, ol, ul, pre": content, }); -const internalDarkModeButton = css` +export const iSawTheButtonGlow = css` &:not(:disabled) { box-shadow: 2px 2px 8px ${Palette.buttonShadowDark}21; :hover { @@ -60,7 +61,7 @@ const internalDarkModeButton = css` } `; -const InternalButton = styled.button<{ isDarkMode: boolean | undefined }>` +export const bevelStyle = css` &:not(:disabled) { box-shadow: 2px 2px 8px ${Palette.black}20; text-shadow: @@ -73,9 +74,12 @@ const InternalButton = styled.button<{ isDarkMode: boolean | undefined }>` transform: translate(0px, 1px); } } - ${(p) => withDarkMode(p.isDarkMode, internalDarkModeButton)} `; +const InternalButton = styled.button<{ isDarkMode: boolean | undefined }>(bevelStyle, (p) => + withDarkMode(p.isDarkMode, iSawTheButtonGlow), +); + // function injectAtoms, P extends {}>(atoms: T) { // const newProps = {} as Record; // for (const key in atoms) { @@ -115,14 +119,15 @@ const button = css` ${content} padding: 4px 13px; margin: 0; + outline: 0; transition: background-color 0.1s ease-out, box-shadow 0.1s ease-out, transform 0.1s cubic-bezier(0.18, 0.89, 0.32, 1.28); border: 1px solid transparent; color: ${Palette.black}; - border-radius: 6px; - cursor: pointer; + border-radius: ${bs(Palette.borderCode)}; + ${interactive} transform: translate(0px, 0px); &[disabled] { background-color: ${Palette.buttonDisabledBackground}; @@ -193,7 +198,7 @@ export const Sidebar = styled.aside` padding: ${bs()}; overflow: auto; & > div { - margin-bottom: ${bs(1/2)}; + margin-bottom: ${bs(1 / 2)}; } `; @@ -260,7 +265,7 @@ export const appStyle = [ font-size: 0.75em; background-color: ${Palette.black}10; padding: ${bs(1 / 3)} ${bs(1 / 2)}; - border-radius: ${bs(1 / 3)}; + border-radius: ${bs(Palette.borderCode)}; overflow-x: scroll; } @@ -291,6 +296,11 @@ export const appStyle = [ ${button} ${input} + + blockquote { + border-left: 2px solid currentColor; + padding-left: ${bs(1 / 4)}; + } `, ]; diff --git a/src/style/mixins.ts b/src/style/mixins.ts index a8a9570..ffd1af7 100644 --- a/src/style/mixins.ts +++ b/src/style/mixins.ts @@ -1,5 +1,9 @@ import { css } from "@emotion/react"; +export const interactive = css` + cursor: pointer; +`; + export const nonInteractive = css` pointer-events: none; user-select: none; diff --git a/src/style/palette.ts b/src/style/palette.ts index 33ac295..05a4365 100644 --- a/src/style/palette.ts +++ b/src/style/palette.ts @@ -22,4 +22,8 @@ export const Palette = { green: "lightgreen", pink: "color(display-p3 0.9 0.66 0.81)", red: "red", + teal: "rgb(0, 255, 231)", + baseRadius: 3 / 4, + borderCode: 1 / 3, + borderSpan: 1 / 8, } as const; diff --git a/src/types.ts b/src/types.ts index 2365392..6ef45f6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,13 +2,16 @@ import { Union, Literal, type Static } from "runtypes"; export type StringContent = { content: string }; export type ObjectOrStringContent = { content: object | string }; +export type ObjectContent = { content: object }; export const StringType = Union(Literal("system"), Literal("developer"), Literal("user")); export const ObjectOrStringType = Union(Literal("assistant"), Literal("info"), Literal("tool")); +export const ObjectType = Union(Literal("context"), Literal("error")) export type _Message = | ({ role: Static } & StringContent) - | ({ role: Static } & ObjectOrStringContent); + | ({ role: Static } & ObjectOrStringContent) + | ({ role: Static } & ObjectContent); export type WithIdentity = { name?: string }; export type WithDirection = { fromServer?: boolean }; @@ -18,7 +21,7 @@ export type WithTemplate = { export type Message = _Message & WithDirection & WithTemplate & WithIdentity; -export type Role = "system" | "developer" | "user" | "assistant" | "tool" | "info"; +export type Role = "system" | "developer" | "user" | "assistant" | "tool" | "info" | "context" | "error"; export type ExperimentWithMeta = { id?: string;