Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runner polish #310

Merged
merged 3 commits into from
Apr 17, 2024
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
4 changes: 4 additions & 0 deletions seeds/team-experiments/src/breadboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
These files are all candidates for moving back into Breadboard or becoming
their own package that makes running boards in apps easier.

This is why they are separated out.
30 changes: 30 additions & 0 deletions seeds/team-experiments/src/breadboard/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { InputResponse, OutputResponse } from "@google-labs/breadboard";
import { RunInputEvent, RunOutputEvent } from "./types.js";

const opts = {
composed: true,
bubbles: true,
cancelable: true,
};

export class InputEvent extends Event implements RunInputEvent {
static readonly eventName = "input";

constructor(public data: InputResponse) {
super(InputEvent.eventName, { ...opts });
}
}

export class OutputEvent extends Event implements RunOutputEvent {
static readonly eventName = "output";

constructor(public data: OutputResponse) {
super(OutputEvent.eventName, { ...opts });
}
}
7 changes: 7 additions & 0 deletions seeds/team-experiments/src/breadboard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

export { Runner } from "./runner.js";
61 changes: 61 additions & 0 deletions seeds/team-experiments/src/breadboard/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { InputValues } from "@google-labs/breadboard";
import { RunConfig, run } from "@google-labs/breadboard/harness";
import { InputResolveRequest } from "@google-labs/breadboard/remote";
import { InputEvent, OutputEvent } from "./events.js";
import { RunEventTarget } from "./types.js";

// TODO: Decide if this interaction model is better.
export class Runner extends (EventTarget as RunEventTarget) implements Runner {
#run: ReturnType<typeof run> | null = null;
#pendingInput: ((data: InputResolveRequest) => Promise<void>) | null = null;

start(config: RunConfig) {
this.#run = run(config);
this.#pendingInput = null;
this.resume();
}

waitingForInputs() {
return !!this.#pendingInput;
}

provideInputs(inputs: InputValues) {
if (!this.#pendingInput) {
return false;
}
this.#pendingInput({ inputs });
this.#pendingInput = null;
this.resume();
}

async resume(): Promise<boolean> {
if (!this.#run) return false;
if (this.waitingForInputs()) return true;

for (;;) {
const result = await this.#run.next();
if (result.done) {
this.#run = null;
return false;
}
const { type, data, reply } = result.value;
switch (type) {
case "input": {
this.#pendingInput = reply;
this.dispatchEvent(new InputEvent(data));
return true;
}
case "output": {
this.dispatchEvent(new OutputEvent(data));
break;
}
}
}
}
}
43 changes: 43 additions & 0 deletions seeds/team-experiments/src/breadboard/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { InputResponse, OutputResponse } from "@google-labs/breadboard";

type RunEventMap = {
input: RunInputEvent;
output: RunOutputEvent;
};

export type RunInputEvent = Event & {
data: InputResponse;
};

export type RunOutputEvent = Event & {
data: OutputResponse;
};

export type TypedEventTarget<EventMap extends object> = {
new (): IntermediateEventTarget<EventMap>;
};

// TODO: This needs to be a more global helper.
interface IntermediateEventTarget<EventMap> extends EventTarget {
addEventListener<K extends keyof EventMap>(
type: K,
callback: (
event: EventMap[K] extends Event ? EventMap[K] : never
) => EventMap[K] extends Event ? void : never,
options?: boolean | AddEventListenerOptions
): void;

addEventListener(
type: string,
callback: EventListenerOrEventListenerObject | null,
options?: EventListenerOptions | boolean
): void;
}

export type RunEventTarget = TypedEventTarget<RunEventMap>;
17 changes: 0 additions & 17 deletions seeds/team-experiments/src/events/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { InputResponse, OutputResponse } from "@google-labs/breadboard";
import type { ConversationInputPart, State } from "../types/types.js";

const opts = {
Expand All @@ -29,22 +28,6 @@ export class ConversationItemCreateEvent extends Event {
}
}

export class RunInputRequestEvent extends Event {
static readonly eventName = "runinputrequest";

constructor(public data: InputResponse) {
super(RunInputRequestEvent.eventName, { ...opts });
}
}

export class RunOutputProvideEvent extends Event {
static readonly eventName = "runoutputprovide";

constructor(public data: OutputResponse) {
super(RunOutputProvideEvent.eventName, { ...opts });
}
}

export class TeamSelectEvent extends Event {
static readonly eventName = "teamselect";

Expand Down
79 changes: 9 additions & 70 deletions seeds/team-experiments/src/ui/elements/team-job/team-job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,80 +8,22 @@ import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import {
ConversationItemCreateEvent,
RunInputRequestEvent,
RunOutputProvideEvent,
TeamSectionSelectEvent,
} from "../../../events/events.js";

// Mock data - to replace.
import { assetItems, jobDescription } from "../../../mock/assets.js";
import { activityItems } from "../../../mock/activity.js";
import { RunConfig, run } from "@google-labs/breadboard/harness";
import {
ConversationItem,
ItemFormat,
ItemType,
Participant,
TeamListItem,
} from "../../../types/types.js";
import { InputValues } from "@google-labs/breadboard";
import { InputResolveRequest } from "@google-labs/breadboard/remote";
import { clamp } from "../../utils/clamp.js";
import { Switcher } from "../elements.js";

// TODO: Decide if this interaction model is better.
class Run extends EventTarget {
#run: ReturnType<typeof run> | null;
#pendingInput: ((data: InputResolveRequest) => Promise<void>) | null;

constructor(config: RunConfig) {
super();
this.#run = run(config);
this.#pendingInput = null;
}

finished() {
return !this.#run;
}

waitingForInputs() {
return !!this.#pendingInput;
}

provideInputs(inputs: InputValues) {
if (!this.#pendingInput) {
return false;
}
this.#pendingInput({ inputs });
this.#pendingInput = null;
this.resume();
}

async resume(): Promise<boolean> {
if (!this.#run) return false;
if (this.waitingForInputs()) return true;

for (;;) {
const result = await this.#run.next();
if (result.done) {
this.#run = null;
return false;
}
const { type, data, reply } = result.value;
switch (type) {
case "input": {
this.#pendingInput = reply;
this.dispatchEvent(new RunInputRequestEvent(data));
return true;
}
case "output": {
this.dispatchEvent(new RunOutputProvideEvent(data));
break;
}
}
}
}
}
import { Runner } from "../../../breadboard/index.js";

@customElement("at-team-job")
export class TeamJob extends LitElement {
Expand Down Expand Up @@ -109,19 +51,15 @@ export class TeamJob extends LitElement {
}
`;

#run: Run | null = null;
#run: Runner | null = null;

#addConversationItem(item: ConversationItem) {
this.conversation = [...this.conversation, item];
}

async #startRun() {
this.#run = new Run({
url: "/bgl/insta/mock-conversation.bgl.json",
kits: [],
});
this.#run.addEventListener(RunOutputProvideEvent.eventName, (evt) => {
const e = evt as RunOutputProvideEvent;
this.#run = new Runner();
this.#run.addEventListener("output", (e) => {
const { outputs, timestamp } = e.data;
const role = "Team Lead";
if (outputs.text) {
Expand All @@ -145,8 +83,7 @@ export class TeamJob extends LitElement {
});
}
});
this.#run.addEventListener(RunInputRequestEvent.eventName, (evt) => {
const e = evt as RunInputRequestEvent;
this.#run.addEventListener("input", (e) => {
// Nasty stuff. Should I use like, inspector API here?
// Note, this diving into schema and the whole
// this.inputValue is only needed to grab sample
Expand All @@ -155,7 +92,10 @@ export class TeamJob extends LitElement {
const schema = e.data.inputArguments.schema;
this.inputValue = schema?.properties?.text.examples?.[0] || "";
});
this.#run.resume();
this.#run.start({
url: "/bgl/insta/mock-conversation.bgl.json",
kits: [],
});
}

connectedCallback(): void {
Expand Down Expand Up @@ -192,7 +132,6 @@ export class TeamJob extends LitElement {

if (!this.#run) return;

if (this.#run.finished()) return;
if (!this.#run.waitingForInputs()) return;

this.#run.provideInputs({ text: evt.message });
Expand Down
Loading