Skip to content

Commit

Permalink
add open in playground context menu item (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored Nov 15, 2024
1 parent 177df3a commit 3b13562
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 137 deletions.
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@effect/ai": "^0.2.18",
"@effect/ai-openai": "^0.4.2",
"@effect/experimental": "^0.32.2",
"@effect/ai": "^0.2.24",
"@effect/ai-openai": "^0.4.8",
"@effect/experimental": "^0.32.8",
"@effect/language-service": "^0.2.0",
"@effect/opentelemetry": "^0.39.12",
"@effect/platform": "^0.69.18",
"@effect/platform-node": "^0.64.20",
"@effect/opentelemetry": "^0.39.15",
"@effect/platform": "^0.69.24",
"@effect/platform-node": "^0.64.26",
"@octokit/types": "^13.6.1",
"@opentelemetry/exporter-metrics-otlp-http": "^0.54.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.54.1",
"@opentelemetry/exporter-metrics-otlp-http": "^0.54.2",
"@opentelemetry/exporter-trace-otlp-http": "^0.54.2",
"@opentelemetry/sdk-metrics": "^1.27.0",
"@opentelemetry/sdk-trace-base": "^1.27.0",
"@opentelemetry/sdk-trace-node": "^1.27.0",
"@types/node": "^22.9.0",
"dfx": "^0.106.0",
"effect": "^3.10.12",
"effect": "^3.10.15",
"html-entities": "^2.5.2",
"octokit": "^4.0.2",
"prettier": "^3.3.3",
Expand Down
232 changes: 116 additions & 116 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion src/Ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ import {
OpenAiConfig,
} from "@effect/ai-openai"
import { NodeHttpClient } from "@effect/platform-node"
import { Chunk, Config, Effect, Layer, pipe } from "effect"
import { Chunk, Config, Effect, Layer, pipe, Schedule } from "effect"
import * as Str from "./utils/String.js"
import { Tokenizer } from "@effect/ai/Tokenizer"
import { Discord, DiscordREST } from "dfx"
import { DiscordApplication } from "./Discord.js"
import { HttpClient } from "@effect/platform"

export const OpenAiLive = OpenAiClient.layerConfig({
apiKey: Config.redacted("OPENAI_API_KEY"),
organizationId: Config.redacted("OPENAI_ORGANIZATION").pipe(
Config.withDefault(undefined),
),
transformClient: Config.succeed(
HttpClient.retryTransient({
times: 3,
schedule: Schedule.exponential(500),
}),
),
}).pipe(Layer.provide(NodeHttpClient.layerUndici))

export const CompletionsLive = OpenAiCompletions.layer({
Expand Down
12 changes: 1 addition & 11 deletions src/AutoThreads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,14 @@ import {
Config,
ConfigProvider,
Data,
Duration,
Effect,
Layer,
Option,
Schedule,
Schema,
pipe,
} from "effect"
import { AiHelpers } from "./Ai.js"

const retryPolicy = pipe(
Schedule.fixed(Duration.millis(500)),
Schedule.intersect(Schedule.recurs(2)),
)

export class NotValidMessageError extends Data.TaggedError(
"NotValidMessageError",
)<{
Expand Down Expand Up @@ -68,12 +61,9 @@ const make = Effect.gen(function* () {
const channel = yield* channels
.get(event.guild_id!, event.channel_id)
.pipe(Effect.flatMap(EligibleChannel))

const title = yield* ai.generateTitle(event.content).pipe(
Effect.tapErrorCause(Effect.log),
Effect.retry({
schedule: retryPolicy,
while: err => err._tag === "AiError",
}),
Effect.withSpan("AutoThreads.generateTitle"),
Effect.orElseSucceed(() =>
pipe(
Expand Down
71 changes: 71 additions & 0 deletions src/Playground.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { InteractionsRegistry } from "dfx/gateway"
import { Discord, Ix, IxHelpers } from "dfx"
import { Effect, Encoding, Layer, Option } from "effect"
import { DiscordLive } from "./Discord.js"

export const PlaygroundLive = Effect.gen(function* () {
const registry = yield* InteractionsRegistry

const linkFromCode = (code: string) =>
Effect.sync(() => {
const encoded = Encoding.encodeBase64Url(code)
return `https://effect.website/play/?code=${encoded}`
})

const menu = Ix.global(
{
type: Discord.ApplicationCommandType.MESSAGE,
name: "Open in playground",
},
Effect.gen(function* () {
const interaction = yield* Ix.Interaction
const command = yield* Ix.ApplicationCommand
const resolved = yield* IxHelpers.resolved(interaction)
const message = resolved.messages?.[command.target_id!]
const content = yield* Effect.fromNullable(message?.content)
const code = yield* extractCode(content)
const url = yield* linkFromCode(code)

const response = `Here is your [playground link](${url}).`
if (response.length > 1950) {
return Ix.response({
type: Discord.InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: Discord.MessageFlag.EPHEMERAL,
content: `The code snippet is too long to be displayed in a single message.`,
},
})
}
return Ix.response({
type: Discord.InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: Discord.MessageFlag.EPHEMERAL,
content: `Here is your [playground link](${url}).`,
},
})
}).pipe(
Effect.catchTag("NoSuchElementException", () =>
Effect.succeed(
Ix.response({
type: Discord.InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: Discord.MessageFlag.EPHEMERAL,
content: "No code snippets were found in the message.",
},
}),
),
),
),
)

const ix = Ix.builder.add(menu).catchAllCause(Effect.logError)
yield* registry.register(ix)
}).pipe(Layer.effectDiscard, Layer.provide(DiscordLive))

const extractCode = (content: string): Option.Option<string> => {
const codeBlock = content.matchAll(/```.*$([\s\S]*?)```/gm)
const items = [...codeBlock]
return items.length > 0
? Option.some(items.map(([, code]) => code.trim()).join("\n\n\n"))
: Option.none()
}
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ReproRequesterLive } from "bot/ReproRequester"
import { Summarizer } from "bot/Summarizer"
import { TracingLive } from "bot/Tracing"
import { Config, Effect, Layer, LogLevel, Logger } from "effect"
import { PlaygroundLive } from "./Playground.js"

const LogLevelLive = Layer.unwrapEffect(
Effect.gen(function* () {
Expand All @@ -24,6 +25,7 @@ const MainLive = Layer.mergeAll(
NoEmbedLive,
DocsLookupLive,
IssueifierLive,
PlaygroundLive,
RemindersLive,
ReproRequesterLive,
Summarizer.Default,
Expand Down
9 changes: 9 additions & 0 deletions test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
I have a doubt about effectful interfaces. I have a lib which exposes a bunch of functions to call remote functions in different ways. One, for example, asks for a lambda client (aws sdk) to make invocations.

I have the need to consume such functions from a repo which doesn't have effect. Is there an existing combinator or such which trasposes the requirements to normal function arguments, maybe using a map or a record or something?

```
class Test {}
const test = new Test()
```

0 comments on commit 3b13562

Please sign in to comment.