Skip to content

Commit 6ed6e08

Browse files
author
Yann Leflour
committed
setup chat
1 parent 6cc4b87 commit 6ed6e08

File tree

15 files changed

+7308
-982
lines changed

15 files changed

+7308
-982
lines changed

pnpm-lock.yaml

Lines changed: 6957 additions & 962 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui-sketcher-extension/.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"ecmaVersion": 6,
66
"sourceType": "module"
77
},
8-
"plugins": ["@typescript-eslint"],
8+
"extends": ["plugin:valtio/recommended"],
9+
"plugins": ["@typescript-eslint", "valtio"],
910
"rules": {
1011
"@typescript-eslint/naming-convention": "warn",
1112
"@typescript-eslint/semi": "warn",

ui-sketcher-extension/TODO.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@
88

99
- Create a mermaid chart renderer
1010
- Use diffs for faster updates (https://chat.openai.com/c/ad82c348-6d19-4cec-93a5-351c5a688c69)
11+
12+
## What could be handled as a window
13+
14+
- BPMN
15+
- Mermaid
16+
- Annotation tool
17+
- A FileSync class
18+
- Try putting drop of file
19+
- Load libs dynamically based on requirements

ui-sketcher-webview/.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
"eslint:recommended",
66
"plugin:@typescript-eslint/recommended",
77
"plugin:react-hooks/recommended",
8+
"plugin:storybook/recommended"
89
],
910
ignorePatterns: ["dist", ".eslintrc.cjs"],
1011
parser: "@typescript-eslint/parser",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { StorybookConfig } from '@storybook/react-vite';
2+
3+
const config: StorybookConfig = {
4+
"stories": [
5+
"../src/**/*.mdx",
6+
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
7+
],
8+
"addons": [
9+
"@storybook/addon-links",
10+
"@storybook/addon-essentials",
11+
"@storybook/addon-onboarding",
12+
"@storybook/addon-interactions"
13+
],
14+
"framework": {
15+
"name": "@storybook/react-vite",
16+
"options": {}
17+
},
18+
"docs": {
19+
"autodocs": "tag"
20+
}
21+
};
22+
export default config;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { Preview } from '@storybook/react'
2+
import "../src/index.css";
3+
import "./storybook.css";
4+
5+
const preview: Preview = {
6+
parameters: {
7+
actions: { argTypesRegex: '^on[A-Z].*' },
8+
class: "bg-gray-100",
9+
controls: {
10+
matchers: {
11+
color: /(background|color)$/i,
12+
date: /Date$/i,
13+
},
14+
},
15+
},
16+
};
17+
18+
export default preview;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#storybook-root {
2+
height: 100%;
3+
width: 100%;
4+
}

ui-sketcher-webview/package.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,25 @@
77
"dev": "vite",
88
"build": "tsc && vite build --emptyOutDir",
99
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10-
"preview": "vite preview"
10+
"preview": "vite preview",
11+
"storybook": "storybook dev -p 6006",
12+
"build-storybook": "storybook build"
1113
},
1214
"dependencies": {
1315
"@tldraw/tldraw": "2.0.0-canary.ba4091c59418",
16+
"@types/react-syntax-highlighter": "^15.5.11",
1417
"canvas-size": "^1.2.6",
1518
"daisyui": "^4.4.19",
1619
"firacode": "^6.2.0",
1720
"iter-tools": "^7.5.3",
1821
"mermaid": "^10.6.1",
1922
"openai": "^4.16.1",
2023
"react": "^18.2.0",
24+
"react-bootstrap-icons": "^1.10.3",
2125
"react-dom": "^18.2.0",
26+
"react-markdown": "^9.0.1",
27+
"react-syntax-highlighter": "^15.5.0",
28+
"remark-gfm": "^4.0.0",
2229
"valtio": "^1.12.1"
2330
},
2431
"pnpm": {
@@ -32,6 +39,15 @@
3239
}
3340
},
3441
"devDependencies": {
42+
"@storybook/addon-essentials": "^7.6.4",
43+
"@storybook/addon-interactions": "^7.6.4",
44+
"@storybook/addon-links": "^7.6.4",
45+
"@storybook/addon-onboarding": "^1.0.10",
46+
"@storybook/blocks": "^7.6.4",
47+
"@storybook/react": "^7.6.4",
48+
"@storybook/react-vite": "^7.6.4",
49+
"@storybook/test": "^7.6.4",
50+
"@tailwindcss/typography": "^0.5.10",
3551
"@types/canvas-size": "^1.2.2",
3652
"@types/node": "^20.9.0",
3753
"@types/react": "^18.2.15",
@@ -44,9 +60,12 @@
4460
"eslint": "^8.45.0",
4561
"eslint-plugin-react-hooks": "^4.6.0",
4662
"eslint-plugin-react-refresh": "^0.4.3",
63+
"eslint-plugin-storybook": "^0.6.15",
64+
"eslint-plugin-valtio": "^0.6.2",
4765
"postcss": "^8.4.31",
4866
"prettier": "^3.0.3",
4967
"prettier-plugin-tailwindcss": "^0.5.9",
68+
"storybook": "^7.6.4",
5069
"tailwindcss": "^3.3.5",
5170
"typescript": "^5.0.2",
5271
"vite": "^4.4.5",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Meta, StoryObj } from "@storybook/react";
2+
3+
import { Chat } from "./Chat";
4+
5+
const meta: Meta<typeof Chat> = {
6+
component: Chat,
7+
};
8+
9+
export default meta;
10+
type Story = StoryObj<typeof Chat>;
11+
12+
export const Primary: Story = {};
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { proxy, useSnapshot } from "valtio";
2+
import RMarkdown from "react-markdown";
3+
import remarkGfm from "remark-gfm";
4+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
5+
import { a11yDark } from "react-syntax-highlighter/dist/esm/styles/prism";
6+
import { useCallback, useEffect, useRef, useState } from "react";
7+
import { ArrowDownCircleFill } from "react-bootstrap-icons";
8+
9+
function useLocal<T extends object | undefined>(input: T) {
10+
return useRef(proxy(input)).current as T;
11+
}
12+
13+
const history = proxy([
14+
{
15+
type: "system",
16+
message: `
17+
# Hello World
18+
This is a test
19+
20+
\`\`\`js
21+
console.log("Hello World!");
22+
return true;
23+
\`\`\`
24+
25+
`,
26+
},
27+
{ type: "user", message: "Hi!" },
28+
{ type: "system", message: "Hello!" },
29+
{ type: "user", message: "Hi!" },
30+
{ type: "system", message: "Hello!" },
31+
{ type: "user", message: "Hi!" },
32+
{ type: "system", message: "Hello!" },
33+
{ type: "user", message: "Hi!" },
34+
{ type: "system", message: "Hello!" },
35+
{ type: "user", message: "Hi!" },
36+
{ type: "system", message: "Hello!" },
37+
{ type: "user", message: "Hi!" },
38+
]);
39+
40+
const Mardown = ({ children }: { children: string }) => (
41+
<RMarkdown
42+
remarkPlugins={[remarkGfm]}
43+
components={{
44+
code: Code,
45+
}}
46+
>
47+
{children}
48+
</RMarkdown>
49+
);
50+
51+
const Code = (
52+
props: React.DetailedHTMLProps<
53+
React.HTMLAttributes<HTMLElement>,
54+
HTMLElement
55+
>,
56+
) => {
57+
const { children, className, ref, ...rest } = props;
58+
const match = /language-(\w+)/.exec(className || "");
59+
60+
if (!match) {
61+
return (
62+
<div className="p-3">
63+
<code {...rest}>{children}</code>
64+
</div>
65+
);
66+
}
67+
68+
return (
69+
<div>
70+
<SyntaxHighlighter
71+
{...rest}
72+
PreTag="div"
73+
customStyle={{ margin: 0 }}
74+
language={match[1]}
75+
style={a11yDark}
76+
>
77+
{String(children).replace(/\n$/, "")}
78+
</SyntaxHighlighter>
79+
</div>
80+
);
81+
};
82+
83+
const History = ({ history }: { history: any[] }) => {
84+
const scrollRef = useRef<HTMLDivElement>(null);
85+
const [displayScrollButton, setDisplayScrollButton] = useState(false);
86+
87+
const onScroll = (e: React.UIEvent<HTMLDivElement>) => {
88+
const element = e.currentTarget;
89+
if (element.scrollTop + element.clientHeight < element.scrollHeight) {
90+
setDisplayScrollButton(true);
91+
} else {
92+
setDisplayScrollButton(false);
93+
}
94+
};
95+
96+
const scrollToBottom = useCallback(
97+
(smooth = true) => {
98+
if (scrollRef.current) {
99+
scrollRef.current.scrollTo({
100+
top: scrollRef.current.scrollHeight,
101+
behavior: smooth ? "smooth" : "instant",
102+
});
103+
}
104+
},
105+
[scrollRef],
106+
);
107+
108+
// Start at bottom on mount
109+
useEffect(() => {
110+
scrollToBottom(false);
111+
}, []);
112+
113+
// Scroll to bottom when history changes
114+
useEffect(() => {
115+
scrollToBottom();
116+
}, [history, scrollToBottom]);
117+
118+
return (
119+
<div
120+
className="scroll relative flex flex-1 flex-col overflow-y-scroll px-3"
121+
onScroll={onScroll}
122+
ref={scrollRef}
123+
>
124+
{history.map((item, index) => {
125+
switch (item.type) {
126+
case "system":
127+
return (
128+
<div key={index} className="border-b-2 py-4 text-lg font-bold">
129+
<div className="text-l sticky top-0 bg-white">Assistant:</div>
130+
<div className="prose p-2">
131+
<Mardown>{item.message}</Mardown>
132+
</div>
133+
</div>
134+
);
135+
case "user":
136+
return (
137+
<div key={index} className="border-b-2 py-4 text-lg font-bold">
138+
<div className="sticky top-0 bg-white">You:</div>
139+
<div className="prose p-2">
140+
<Mardown>{item.message}</Mardown>
141+
</div>
142+
</div>
143+
);
144+
}
145+
})}
146+
{displayScrollButton && (
147+
<div className="sticky bottom-2 flex w-full justify-center ">
148+
<button
149+
onClick={() => scrollToBottom()}
150+
className="opacity-40 hover:opacity-100"
151+
>
152+
<ArrowDownCircleFill size="2rem" />
153+
</button>
154+
</div>
155+
)}
156+
</div>
157+
);
158+
};
159+
160+
const Input = () => {
161+
return (
162+
<form>
163+
<textarea
164+
className="textarea textarea-primary w-full rounded-none pl-5"
165+
placeholder="Bio"
166+
></textarea>
167+
<span className="button"></span>
168+
</form>
169+
);
170+
};
171+
172+
export const Chat = () => {
173+
const historySnap = useSnapshot(history);
174+
const [value] = useState("");
175+
176+
const onSizeChange = (size: number) => {};
177+
178+
return (
179+
<div className="flex h-full max-h-full flex-col gap-3 bg-white">
180+
<History history={historySnap} />
181+
<Input />
182+
</div>
183+
);
184+
};

0 commit comments

Comments
 (0)