Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Handle metadata
  • Loading branch information
pomber committed Jun 26, 2023
commit 78b807810add5f367dbf47f723326dc1b61190da
20 changes: 16 additions & 4 deletions packages/mdx/pages/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Chat } from "../src/chat/chat"
import { useConversation } from "../src/chat/use-conversation"
import theme from "@code-hike/lighter/themes/github-dark.json"
import { Message } from "../src/chat/types"
import { setMetadata } from "../src/chat/metadata"

export default function Page() {
const [progress, setProgress] = React.useState(max)
Expand Down Expand Up @@ -132,10 +133,21 @@ const messages = [
"```jsx index.jsx\nfunction MyComponent() {\n const [data, setData] = useState(null)\n\n useEffect(() => {\n fetch(\"https://api.example.com/data\")\n .then(response => response.json())\n .then(data => setData(data))\n .catch(error => console.error(error))\n }, [])\n\n return (\n <div>\n {data ? (\n <ul>\n {data.map(item => (\n <li key={item.id}>{item.name}</li>\n ))}\n </ul>\n ) : (\n <p>Loading...</p>\n )}\n </div>\n )\n}\n```\n\nYou can use the Fetch API to make a request to the API endpoint that returns JSON data. Then, use the `json()` method to parse the response into a JavaScript object.\n\nHere's an example of how to use `fetch()` with React's `useState()` and `useEffect()` hooks to fetch and display JSON data.\n\nDoes this help?\n\n---\n\n- Yes, thank you!\n- Can you explain what `useEffect()` does?\n- How can I handle errors when fetching data?",
role: "assistant",
},
].map(m => ({
...m,
content: m.content.replace(/~/g, "`"),
}))
]
.map(m => ({
...m,
content: m.content.replace(/~/g, "`"),
}))
.map(m =>
m.role === "assistant"
? m
: {
...m,
content: setMetadata(m.content, {
model: "GPT-3",
}),
}
)

function getSteps() {
const steps = [[]] as (Message & {
Expand Down
23 changes: 23 additions & 0 deletions packages/mdx/src/chat/__snapshots__/metadata.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Vitest Snapshot v1

exports[`add 1`] = `
"---
foo: bar
x: y
---
hello"
`;

exports[`change 1`] = `
"---
foo: y
---
hello"
`;

exports[`set 1`] = `
"---
foo: bar
---
hello"
`;
20 changes: 20 additions & 0 deletions packages/mdx/src/chat/metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from "vitest"
import { setMetadata } from "./metadata"

test("set", () => {
expect(
setMetadata("hello", { foo: "bar" })
).toMatchSnapshot()
})

test("add", () => {
const content = setMetadata("hello", { foo: "bar" })
expect(setMetadata(content, { x: "y" })).toMatchSnapshot()
})

test("change", () => {
const content = setMetadata("hello", { foo: "bar" })
expect(
setMetadata(content, { foo: "y" })
).toMatchSnapshot()
})
43 changes: 43 additions & 0 deletions packages/mdx/src/chat/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export function setMetadata(
content: string,
metadata: Record<string, string>
) {
const oldData = getMetadata(content)

const metadataString = Object.entries({
...oldData,
...metadata,
})
.map(([key, value]) => `${key}: ${value}`)
.join("\n")

const contentWithoutMetadata = removeMetadata(content)

return `---\n${metadataString}\n---\n${contentWithoutMetadata}`
}

export function getMetadata(
content: string
): Record<string, string> {
if (!content) {
return undefined
}
const metadata = content.match(/^---\n([\s\S]*?)\n---\n/)

if (!metadata) {
return {}
}

const metadataString = metadata[1]

const metadataEntries = metadataString
.split("\n")
.map(s => s.split(": "))
.filter(([key, value]) => key && value)

return Object.fromEntries(metadataEntries)
}

export function removeMetadata(content: string) {
return content.replace(/^---\n[\s\S]*?\n---\n/, "")
}
2 changes: 2 additions & 0 deletions packages/mdx/src/chat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type ConversationEntry =
| {
type: "question" | "other"
children: React.ReactNode
metadata?: Record<string, string>
}
| AnswerEntry

Expand All @@ -12,6 +13,7 @@ export type AnswerEntry = {
files: EntryCodeFile[]
activeFile?: string
children: React.ReactNode
metadata?: Record<string, string>
}

export type Message = {
Expand Down
19 changes: 16 additions & 3 deletions packages/mdx/src/chat/use-conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
loadLang,
} from "./languages"
import { highlightFile } from "./highlight-code"
import { getMetadata, removeMetadata } from "./metadata"

export function useConversation(
messages: Message[],
Expand All @@ -38,7 +39,10 @@ export function useConversation(
if (lastMessage?.role === "user") {
newMessages = [
...messages,
{ role: "assistant", content: "" },
{
role: "assistant",
content: "",
},
]
}

Expand Down Expand Up @@ -145,8 +149,11 @@ function getEntry(
return {
type: "question",
children: (
<ReactMarkdown>{newMessage.content}</ReactMarkdown>
<ReactMarkdown>
{removeMetadata(newMessage.content)}
</ReactMarkdown>
),
metadata: getMetadata(newMessage.content),
}
}

Expand All @@ -161,15 +168,21 @@ function getEntry(
conversation,
theme
)

const lastEntry = conversation[conversation.length - 1]
return {
type: "answer",
files,
activeFile,
metadata: {
...lastEntry?.metadata,
...getMetadata(parsedAnswer.content),
},
children: (
<>
{parsedAnswer.content ? (
<ReactMarkdown>
{parsedAnswer.content}
{removeMetadata(parsedAnswer.content)}
</ReactMarkdown>
) : (
<BouncingDots />
Expand Down