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
New chat
  • Loading branch information
pomber committed Jun 23, 2023
commit 032060b84a4f74aed3da43be824e6c40927598de
2 changes: 1 addition & 1 deletion packages/mdx/dev/chat/fake-gpt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type ConversationEntry = {

type Conversation = ConversationEntry[]
const THINKING_MS = 300
const WRITING_MS = 400
const WRITING_MS = 100

export function useFakeGPT(
convo: Conversation,
Expand Down
68 changes: 68 additions & 0 deletions packages/mdx/pages/new-chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from "react"
import { NewChat } from "../src/chat/new-chat"
import { useConversation } from "../src/chat/use-conversation"
import theme from "@code-hike/lighter/themes/github-dark.json"

export default function Page() {
const conversation = useConversation(
messages,
false,
x => {
console.log(x)
},
theme
)
console.log(conversation)
return (
<div>
<style jsx global>{`
html,
body,
body > div:first-child,
div#__next,
div#__next > div {
background: #ccc !important;
color: #fff;
}

.ch-chat {
width: 900px;
margin: 10vh auto;
}
`}</style>
<NewChat
conversation={conversation}
height="80vh"
theme={theme as any}
/>
</div>
)
}

const messages = [
{
role: "user",
content: "hi",
},
{
role: "assistant",
content: `
~~~js foo.js
console.log("this is foo")
~~~

~~~js bar.js
console.log("this is bar")
~~~

That is foo
`,
},
{
role: "user",
content: "help me `code`",
},
].map(m => ({
...m,
content: m.content.replace(/~/g, "`"),
}))
108 changes: 108 additions & 0 deletions packages/mdx/src/chat/__snapshots__/answer-parser.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Vitest Snapshot v1

exports[`
\`\`\`js foo.js
console.log(1)

1`] = `
{
"content": "",
"fileInfoList": [
{
"lang": "js",
"name": "foo.js",
"open": true,
"text": "console.log(1)

",
},
],
"replies": [],
}
`;

exports[`
\`\`\`js foo.js
console.log(1)

\`\`\`

\`\`\`ts bar.ts
console.log(2)
\`\`\`

# hello

world 1`] = `
{
"content": "# hello

world",
"fileInfoList": [
{
"lang": "js",
"name": "foo.js",
"open": false,
"text": "console.log(1)
",
},
{
"lang": "ts",
"name": "bar.ts",
"open": false,
"text": "console.log(2)",
},
],
"replies": [],
}
`;

exports[`
\`\`\`js foo.js
console.log(1)
\`\`\`

# hello

world

---

- yes
- no

1`] = `
{
"content": "# hello

world",
"fileInfoList": [
{
"lang": "js",
"name": "foo.js",
"open": false,
"text": "console.log(1)",
},
],
"replies": [
"yes",
"no",
],
}
`;

exports[` 1`] = `
{
"content": "",
"fileInfoList": [],
"replies": [],
}
`;

exports[`Hello 1`] = `
{
"content": "Hello",
"fileInfoList": [],
"replies": [],
}
`;
44 changes: 44 additions & 0 deletions packages/mdx/src/chat/answer-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect, test } from "vitest"
import { parseAnswer } from "./answer-parser"

const fixtures = [
``,
`Hello`,
`
~~~js foo.js
console.log(1)
~~~

# hello

world

---

- yes
- no

`,
`
~~~js foo.js
console.log(1)

~~~

~~~ts bar.ts
console.log(2)
~~~

# hello

world`,
`
~~~js foo.js
console.log(1)

`,
].map(x => x.replace(/~/g, "`"))

test.each(fixtures)("%s", async markdown => {
expect(parseAnswer(markdown)).toMatchSnapshot()
})
65 changes: 65 additions & 0 deletions packages/mdx/src/chat/answer-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// turns streaming markdown into codeblocks, content and replies
// doesnt care about previous answers
export function parseAnswer(markdown: string) {
const { markdownWithoutCode, fileInfoList } =
extractCodeBlocks(markdown)
const [answerText, repliesText] =
markdownWithoutCode.split(/\n+---\n+/)
const replies = repliesText
? repliesText
.split(/\n/)
.map((r: string) => r.replace(/^-\s*/, "").trim())
.filter((r: any) => r !== "")
: []
return {
fileInfoList: fileInfoList,
content: answerText,
replies,
}
}

function extractCodeBlocks(markdown: string) {
const closedCodeBlocks =
markdown.match(/```[\s\S]*?```/g) || []
const markdownWithoutClosedCodeBlocs = markdown.replace(
/```[\s\S]*?```/g,
""
)

const openCodeBlock =
markdownWithoutClosedCodeBlocs.match(/```[\s\S]*?$/g)

const markdownWithoutCode = markdownWithoutClosedCodeBlocs
.replace(/```[\s\S]*?$/g, "")
.trim()

const fileInfoList = closedCodeBlocks.map(s => ({
...codeblockToFileInfo(s),
open: false,
}))

if (openCodeBlock) {
fileInfoList.push({
...codeblockToFileInfo(openCodeBlock[0] + "\n```"),
open: true,
})
}
return { markdownWithoutCode, fileInfoList }
}

function codeblockToFileInfo(codeblock: string): {
lang: string
name: string
text: string
} {
const codeBlockPattern =
/```([^ \n]+)? ?([^\n]+)?\n([\s\S]*?)\n?```/g

const match = codeBlockPattern.exec(codeblock)

return {
lang: match?.[1] || "",
name: match?.[2] || "",
text: match?.[3] || "",
}
}
34 changes: 34 additions & 0 deletions packages/mdx/src/chat/laguages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { preload } from "@code-hike/lighter/dist/browser.esm.mjs"

const cache = new Map<
string,
{ promise: Promise<boolean>; loaded: boolean }
>()

export function getLoadedLanguages() {
return Array.from(cache.entries())
.filter(([, { loaded }]) => loaded)
.map(([lang]) => lang)
}

export function isLangLoaded(lang: string) {
// if is server side return false
if (typeof window === "undefined") {
return false
}

return cache.get(lang)?.loaded
}

export async function loadLang(lang: string) {
// TODO what happens if the lang is not supported?
if (cache.has(lang)) {
return cache.get(lang)!.promise
}

const promise = preload([lang])
cache.set(lang, { promise, loaded: false })
await promise
cache.set(lang, { promise, loaded: true })
return promise
}
Loading