Skip to content

Commit

Permalink
metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
actualwitch committed Jan 14, 2025
1 parent c5dbd16 commit f40fc64
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 61 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ env:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -41,7 +41,7 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
runs-on: macos-latest
name: Deploy
steps:
- name: Deploy to GitHub Pages
Expand Down
Binary file modified bun.lockb
Binary file not shown.
22 changes: 19 additions & 3 deletions src/atoms/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { divergentAtom, entangledAtom } from "../utils/entanglement";
import { getRealm, hasBackend } from "../utils/realm";
import { author } from "../const";
import type { _Message, SerialExperiment, ExperimentWithMeta, Message } from "../types";
import type { ProviderType } from "../feature/inference/atoms";
import { modelLabels, type ProviderType } from "../feature/inference/atoms";

export type LayoutType = "mobile" | "desktop";
export const layoutAtom = atom<LayoutType>();
Expand Down Expand Up @@ -101,6 +101,11 @@ export const storeAtom = divergentAtom(
);

export const experimentAtom = entangledAtom("experiment", atom<Message[]>([]));
export const selectedProviderAtom = entangledAtom("selected-provider", focusAtom(storeAtom, (o) => o.prop("selectedProvider")));
export const modelAtom = entangledAtom(
"model",
focusAtom(storeAtom, (o) => o.prop("selectedModel")),
);

export const experimentIdsAtom = entangledAtom(
"experimentIds",
Expand Down Expand Up @@ -150,7 +155,7 @@ export const createExperiment = atom(
null,
(get, set, messages?: Message[], id?: string, runId?: string): ExperimentCursor => {
const exp = get(experimentsAtom) ?? {};

const parent = get(parentAtom);
id ??= parent;

Expand All @@ -159,9 +164,20 @@ export const createExperiment = atom(
const thisExperiment = exp[id] ?? {};
runId ??= String(Object.keys(thisExperiment).length + 1);

const provider = get(selectedProviderAtom);
const model = get(modelAtom);
const modelName = provider && model && modelLabels[provider][model];

set(experimentsAtom, (prev) => ({
...prev,
[id]: { ...thisExperiment, [runId]: messages ?? [] },
[id]: {
...thisExperiment,
[runId]: {
messages: messages ?? [],
timestamp: new Date().toISOString(),
model: modelName,
},
},
}));

return { id, runId };
Expand Down
45 changes: 22 additions & 23 deletions src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import styled from "@emotion/styled";
import { atom, useAtom, useAtomValue } from "jotai";
import { type CSSProperties, type ReactNode, useEffect, useMemo, useRef } from "react";
import { TRIANGLE } from "../const";
import {
type Store,
experimentLayoutAtom,
isDarkModeAtom,
templatesAtom,
} from "../atoms/common";
import { type Store, experimentLayoutAtom, isDarkModeAtom, templatesAtom } from "../atoms/common";
import { isRunningAtom } from "../feature/inference/atoms";
import { bs } from "../style";
import type { WithDarkMode } from "../style/darkMode";
Expand All @@ -17,7 +12,7 @@ import { deepEqual } from "../utils";
import { useHandlers } from "../utils/keyboard";
import { useScrollToTop } from "../utils/scroll";
import { View, collapsedAtom } from "./view";
import type { _Message, Message } from "../types";
import type { _Message, Experiment, ExperimentWithMeta, Message } from "../types";

const baseHeight = bs(6);
export const ChatContainer = styled.div<WithDarkMode>`
Expand Down Expand Up @@ -67,7 +62,8 @@ export const MessageComponent = styled.article<{
isSelected?: boolean;
isDarkMode?: boolean;
experimentLayout: Store["experimentLayout"];
}>(({ role, ioType, contentType, isSelected, isDarkMode, experimentLayout }) => {
name?: string
}>(({ name, role, ioType, contentType, isSelected, isDarkMode, experimentLayout }) => {
const fromServer = ioType === "output";
const align = getAlign(fromServer, experimentLayout);
const styles: SerializedStyles[] = [
Expand All @@ -84,7 +80,7 @@ export const MessageComponent = styled.article<{
}
&:before {
content: "${[contentType, role].filter(Boolean).join(` ${TRIANGLE} `)}";
content: "${[contentType, name ?? role].filter(Boolean).join(` ${TRIANGLE} `)}";
position: absolute;
${align}: 0;
transform-origin: ${align};
Expand Down Expand Up @@ -175,7 +171,7 @@ export const MessageComponent = styled.article<{
type Path = [number] | [number, "content"];
export const selectionAtom = atom<Path | null>(null);

function hasMessages(obj: _Message | { messages: Message[] }): obj is { messages: Message[] } {
function hasMessages(obj: _Message | ExperimentWithMeta): obj is ExperimentWithMeta{
return Object.hasOwn(obj, "messages");
}

Expand Down Expand Up @@ -285,6 +281,7 @@ export const ChatMessage = ({ message: _message, index }: { message: Message; in
ref={ref}
role={message.role}
contentType={contentType}
name={message.name}
isSelected={isSelected}
isDarkMode={isDarkMode}
experimentLayout={experimentLayout}
Expand All @@ -311,18 +308,29 @@ const Banner = styled.div`
`;

export function ChatPreview({
messages,
experiment,
autoScroll,
autoScrollAnchor = "first",
autoScrollAnchor = "first"
}: {
messages: Message[];
experiment: Experiment;
autoScroll?: boolean;
autoScrollAnchor?: "first" | "last";
}) {
const Anchor = useScrollToTop("top", [messages.length, autoScroll, autoScrollAnchor]);
const [selection, setSelection] = useAtom(selectionAtom);
const [isDarkMode] = useAtom(isDarkModeAtom);
const [isRunning] = useAtom(isRunningAtom);
const computedMessages = useMemo(() => {
const messages = Array.isArray(experiment) ? experiment : experiment.messages;
const keyed = messages.map((message, index) => {
return { ...message, key: index };
});
const reversed = [...keyed].reverse();
if (isRunning && !reversed[0].fromServer) {
reversed.unshift({ role: "assistant", content: "...", fromServer: true, key: -1 });
}
return reversed;
}, [experiment, isRunning]);
const Anchor = useScrollToTop("top", [computedMessages.length, autoScroll, autoScrollAnchor]);

useHandlers({
Escape: () => {
Expand All @@ -334,15 +342,6 @@ export function ChatPreview({
return () => void setSelection(null);
}, []);

const computedMessages = useMemo(() => {
const keyed = messages.map((message, index) => ({ ...message, key: index }));
const reversed = [...keyed].reverse();
if (isRunning && !reversed[0].fromServer) {
reversed.unshift({ role: "assistant", content: "...", fromServer: true, key: -1 });
}
return reversed;
}, [messages, isRunning]);

if (computedMessages.length === 0) {
return <Banner></Banner>;
}
Expand Down
8 changes: 5 additions & 3 deletions src/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useSetAtom } from "jotai";
import { useNavigate } from "react-router";
import { experimentAtom, parentAtom, type Message } from "../atoms/common";
import { experimentAtom, parentAtom } from "../atoms/common";
import { Button } from "../style";
import type { Experiment } from "../types";

export const ForkButton = ({ experiment, parent }: { experiment?: Message[]; parent?: string }) => {
export const ForkButton = ({ experiment, parent }: { experiment?: Experiment; parent?: string }) => {
const setExoeriment = useSetAtom(experimentAtom);
const setParent = useSetAtom(parentAtom);
const navigate = useNavigate();
Expand All @@ -12,7 +13,8 @@ export const ForkButton = ({ experiment, parent }: { experiment?: Message[]; par
type="submit"
onClick={() => {
if (!experiment) return;
setExoeriment(experiment);
const messages = Array.isArray(experiment) ? experiment : experiment.messages;
setExoeriment(messages);
if (parent) setParent(parent);
navigate("/");
}}
Expand Down
13 changes: 7 additions & 6 deletions src/feature/inference/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { experimentToOpenai } from "./adapters/openai";
import { spawn } from "../../utils";
import { entangledAtom } from "../../utils/entanglement";
import { hasBackend } from "../../utils/realm";
import {createExperiment, experimentAtom, parentAtom, storeAtom, tokensAtom } from "../../atoms/common";
import {createExperiment, experimentAtom, modelAtom, selectedProviderAtom, storeAtom, tokensAtom } from "../../atoms/common";
import type { Message } from "../../types";

export function withIds<T extends string>(items: T[] | readonly T[]) {
Expand Down Expand Up @@ -45,8 +45,6 @@ export const availableProviderOptionsAtom = atom((get) => {
}));
});

export const selectedProviderAtom = entangledAtom("selected-provider", focusAtom(storeAtom, (o) => o.prop("selectedProvider")));

export const OpenAIModel = Union(
Literal("gpt-4o"),
Literal("gpt-4o-mini"),
Expand Down Expand Up @@ -99,12 +97,11 @@ export const modelOptionsAtom = atom((get) => {
if (!provider) return [];
return modelOptions[provider].map((model) => ({
id: model,
name: modelLabels[provider][model],
name: modelLabels?.[provider]?.[model] ?? model,
}));
});

export const tempAtom = entangledAtom("temp", atom(0.0));
export const modelAtom = entangledAtom("model", focusAtom(storeAtom, (o) => o.prop("selectedModel")));
export const isRunningAtom = entangledAtom("is running", atom(false));

export const modelSupportsTemperatureAtom = atom((get) => {
Expand Down Expand Up @@ -155,7 +152,11 @@ export const runInferenceAtom = entangledAtom(
break;
}
}
set(saveExperimentAtom);
const experiment: Message[] = get(experimentAtom);
if (experiment.length > 0) {
const model = get(modelAtom);
set(createExperiment, experiment);
}
} catch (e) {
console.error(e);
} finally {
Expand Down
20 changes: 15 additions & 5 deletions src/feature/router/Experiment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import { type ExperimentCursor, getExperimentAtom } from "../../atoms/common";
import { Button } from "../../style";
import { entangledAtom } from "../../utils/entanglement";
import { Actions, Page } from "./_page";
import type { Message } from "../../types";
import type { Experiment } from "../../types";
import { titleOverrideAtom } from "../../atoms/meta";
import { DesktopOnly } from "../../components/Mobile";
import { useEffect } from "react";
import { useEffect, useMemo } from "react";
import { View } from "../../components/view";

const cursorAtom = entangledAtom("cursor", atom<ExperimentCursor | null>(null));
const selectedExperimentAtom = entangledAtom(
"selected-experiment",
atom<Message[]>((get) => {
atom<Experiment>((get) => {
const cursor = get(cursorAtom);
if (cursor) {
const experiment = get(getExperimentAtom(cursor));
Expand All @@ -24,7 +25,7 @@ const selectedExperimentAtom = entangledAtom(
return [];
}),
);
export default function Experiment() {
export default function () {
const { id, runId } = useParams();
const [cursor, setCursor] = useAtom(cursorAtom);
const [experiment] = useAtom(selectedExperimentAtom);
Expand All @@ -40,13 +41,22 @@ export default function Experiment() {
return () => setTitleOverride(null);
}, []);

const meta = useMemo(() => {
if (Array.isArray(experiment)) {
return null;
}
const { messages, ...meta } = experiment;
return meta;
}, [experiment]);

return (
<>
<Page>
<DesktopOnly>
<h2>{title}</h2>
</DesktopOnly>
<ChatPreview key={id + runId} messages={experiment ?? []} />
{meta && <View>{meta}</View>}
<ChatPreview key={title} experiment={experiment} />
</Page>
<Actions>
<h3>Actions</h3>
Expand Down
10 changes: 5 additions & 5 deletions src/feature/router/Import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function () {
);

const [filename, idx] = selected ?? [];
const chat: ExperimentWithMeta | undefined = filename && idx ? registry[filename][idx] : undefined;
const experiment: ExperimentWithMeta | undefined = filename && idx ? registry[filename][idx] : undefined;

const title = "Import CSV";
const [titleOverride, setTitleOverride] = useAtom(titleOverrideAtom);
Expand All @@ -79,8 +79,8 @@ export default function () {
return (
<>
<Page>
{chat ?
<ChatPreview key={`${filename}-${idx}`} messages={chat.messages} />
{experiment ?
<ChatPreview key={`${filename}-${idx}`} experiment={experiment} />
: <>
<DesktopOnly>
<h2>{title}</h2>
Expand Down Expand Up @@ -110,11 +110,11 @@ export default function () {
<CsvInput />
{selected && (
<div>
<ForkButton experiment={chat?.messages} />
<ForkButton experiment={experiment} />
<Button
type="submit"
onClick={() => {
navigator.clipboard.writeText(JSON.stringify(chat));
navigator.clipboard.writeText(JSON.stringify(experiment));
}}
>
Copy JSON
Expand Down
8 changes: 3 additions & 5 deletions src/feature/router/NewExperiment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ import { NavLink } from "react-router";

import { type Config, ConfigRenderer } from "../../components/ConfigRenderer";
import { ChatPreview, selectionAtom } from "../../components/chat";
import { experimentAtom, isActionPanelOpenAtom, isDarkModeAtom, parentAtom, templatesAtom } from "../../atoms/common";
import { experimentAtom, isActionPanelOpenAtom, isDarkModeAtom, modelAtom, parentAtom, selectedProviderAtom, templatesAtom } from "../../atoms/common";
import {
availableProviderOptionsAtom,
isRunningAtom,
modelAtom,
modelOptions,
modelOptionsAtom,
modelSupportsTemperatureAtom,
runInferenceAtom,
selectedProviderAtom,
tempAtom,
} from "../inference/atoms";
import { bs } from "../../style";
Expand Down Expand Up @@ -233,7 +231,7 @@ export default function NewExperiment() {
}, [provider]);

useEffect(() => {
if (provider && !models.includes(model)) {
if (provider && model && !models.includes(model)) {
setModel(models[0]);
}
}, [provider, models, model]);
Expand Down Expand Up @@ -347,7 +345,7 @@ export default function NewExperiment() {
return (
<>
<Page>
<ChatPreview messages={experiment} autoScroll />
<ChatPreview experiment={experiment} autoScroll />
<Block isDarkMode={isDarkMode}>
<ActionRow>
<select value={role} onChange={(e) => setRole(e.target.value as Role)} style={{ flex: 1 }}>
Expand Down
8 changes: 5 additions & 3 deletions src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,11 @@ export const Shell = ({
<Hydration />
</ErrorBoundary>
</Suspense>
{additionalScripts?.filter(Boolean).map((script, index) => {
return <script suppressHydrationWarning key={index} dangerouslySetInnerHTML={{ __html: script }} />;
})}
{additionalScripts
?.filter((script): script is string => Boolean(script))
.map((script, index) => {
return <script suppressHydrationWarning key={index} dangerouslySetInnerHTML={{ __html: script }} />;
})}
{bootstrap && <script suppressHydrationWarning type="module" src={`${baseUrl ?? ""}${clientFile}`} async />}
</body>
</html>
Expand Down
Loading

0 comments on commit f40fc64

Please sign in to comment.