Snapshot
Browse files- frontend/esbuild.js +22 -16
- frontend/package.json +2 -1
- frontend/public/index.css +84 -0
- frontend/public/index.html +0 -1
- frontend/src/components/app.tsx +46 -17
- frontend/src/index.tsx +2 -1
frontend/esbuild.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
| 1 |
import * as esbuild from 'esbuild'
|
| 2 |
|
| 3 |
-
esbuild
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import * as esbuild from 'esbuild'
|
| 2 |
|
| 3 |
+
const ctx = await esbuild.context({
|
| 4 |
+
entryPoints: [
|
| 5 |
+
"./src/index.tsx",
|
| 6 |
+
],
|
| 7 |
+
bundle: true,
|
| 8 |
+
minify: false,
|
| 9 |
+
sourcemap: process.env.NODE_ENV !== "production",
|
| 10 |
+
target: ["chrome120", "firefox110"],
|
| 11 |
+
outdir: "public",
|
| 12 |
+
outbase: "src",
|
| 13 |
+
define: {
|
| 14 |
+
"process.env.NODE_ENV": `"${process.env.NODE_ENV}"`
|
| 15 |
+
}
|
| 16 |
+
})
|
| 17 |
+
|
| 18 |
+
if (process.argv.includes('--watch')) {
|
| 19 |
+
await ctx.watch()
|
| 20 |
+
console.log('Watching...')
|
| 21 |
+
} else {
|
| 22 |
+
await ctx.rebuild()
|
| 23 |
+
await ctx.dispose()
|
| 24 |
+
}
|
frontend/package.json
CHANGED
|
@@ -4,7 +4,8 @@
|
|
| 4 |
"main": "index.js",
|
| 5 |
"type": "module",
|
| 6 |
"scripts": {
|
| 7 |
-
"build": "node esbuild.js"
|
|
|
|
| 8 |
},
|
| 9 |
"author": "",
|
| 10 |
"license": "ISC",
|
|
|
|
| 4 |
"main": "index.js",
|
| 5 |
"type": "module",
|
| 6 |
"scripts": {
|
| 7 |
+
"build": "node esbuild.js",
|
| 8 |
+
"watch": "node esbuild.js --watch"
|
| 9 |
},
|
| 10 |
"author": "",
|
| 11 |
"license": "ISC",
|
frontend/public/index.css
CHANGED
|
@@ -1,3 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
main {
|
| 2 |
display: flex;
|
| 3 |
flex-direction: column;
|
|
@@ -51,3 +125,13 @@ details > summary {
|
|
| 51 |
font-family: monospace;
|
| 52 |
color: red;
|
| 53 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
* {
|
| 2 |
+
box-sizing: border-box;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
:root {
|
| 6 |
+
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
| 7 |
+
font-size: 16px;
|
| 8 |
+
line-height: 24px;
|
| 9 |
+
font-weight: 400;
|
| 10 |
+
|
| 11 |
+
color: #213547;
|
| 12 |
+
background-color: #ffffff;
|
| 13 |
+
|
| 14 |
+
font-synthesis: none;
|
| 15 |
+
text-rendering: optimizeLegibility;
|
| 16 |
+
-webkit-font-smoothing: antialiased;
|
| 17 |
+
-moz-osx-font-smoothing: grayscale;
|
| 18 |
+
-webkit-text-size-adjust: 100%;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
a {
|
| 22 |
+
font-weight: 500;
|
| 23 |
+
color: #646cff;
|
| 24 |
+
text-decoration: inherit;
|
| 25 |
+
}
|
| 26 |
+
a:hover {
|
| 27 |
+
color: #747bff;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
body {
|
| 31 |
+
margin: 0;
|
| 32 |
+
display: flex;
|
| 33 |
+
place-items: center;
|
| 34 |
+
min-width: 320px;
|
| 35 |
+
min-height: 100vh;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
h1 {
|
| 39 |
+
font-size: 3.2em;
|
| 40 |
+
line-height: 1.1;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.card {
|
| 44 |
+
padding: 2em;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
#app {
|
| 48 |
+
width: min(900px, 100vw);
|
| 49 |
+
min-height: 100vh;
|
| 50 |
+
margin: 0 auto;
|
| 51 |
+
padding: 2rem;
|
| 52 |
+
text-align: center;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
button {
|
| 56 |
+
border-radius: 4px;
|
| 57 |
+
border: 1px solid transparent;
|
| 58 |
+
padding: 0.6em 1.2em;
|
| 59 |
+
font-size: 1em;
|
| 60 |
+
font-weight: 500;
|
| 61 |
+
font-family: inherit;
|
| 62 |
+
background-color: #e5e5e5;
|
| 63 |
+
cursor: pointer;
|
| 64 |
+
transition: border-color 0.25s;
|
| 65 |
+
}
|
| 66 |
+
button:hover {
|
| 67 |
+
border-color: #646cff;
|
| 68 |
+
}
|
| 69 |
+
button:focus,
|
| 70 |
+
button:focus-visible {
|
| 71 |
+
outline: 4px auto -webkit-focus-ring-color;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
main {
|
| 76 |
display: flex;
|
| 77 |
flex-direction: column;
|
|
|
|
| 125 |
font-family: monospace;
|
| 126 |
color: red;
|
| 127 |
}
|
| 128 |
+
|
| 129 |
+
span.token-chip {
|
| 130 |
+
background-color: #222222;
|
| 131 |
+
color: white;
|
| 132 |
+
padding: 2px;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
span.token-chip.flagged {
|
| 136 |
+
background-color: #dd1111;
|
| 137 |
+
}
|
frontend/public/index.html
CHANGED
|
@@ -5,7 +5,6 @@
|
|
| 5 |
<link rel="stylesheet" href="index.css">
|
| 6 |
</head>
|
| 7 |
<body>
|
| 8 |
-
<h1>GPTed with suggestions</h1>
|
| 9 |
<div id="app"></div>
|
| 10 |
<script src="index.js"></script>
|
| 11 |
</body>
|
|
|
|
| 5 |
<link rel="stylesheet" href="index.css">
|
| 6 |
</head>
|
| 7 |
<body>
|
|
|
|
| 8 |
<div id="app"></div>
|
| 9 |
<script src="index.js"></script>
|
| 10 |
</body>
|
frontend/src/components/app.tsx
CHANGED
|
@@ -1,35 +1,64 @@
|
|
| 1 |
import React, { useState } from "react"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
export default function App() {
|
| 4 |
-
const [threshold, setThreshold] = useState(
|
| 5 |
const [context, setContext] = useState("")
|
| 6 |
const [wordlist, setWordlist] = useState("")
|
| 7 |
const [showWholePrompt, setShowWholePrompt] = useState(false)
|
| 8 |
-
const [checked, setChecked] = useState(false)
|
| 9 |
const [text, setText] = useState("")
|
|
|
|
|
|
|
| 10 |
|
| 11 |
-
const
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
let result
|
| 16 |
|
| 17 |
-
if (
|
| 18 |
-
result = <textarea value={text} />
|
| 19 |
} else {
|
| 20 |
-
result =
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
| 24 |
|
| 25 |
return (
|
| 26 |
-
|
| 27 |
<h1>GPTed</h1>
|
| 28 |
|
| 29 |
<details>
|
| 30 |
<summary>Advanced settings</summary>
|
| 31 |
<label>
|
| 32 |
-
<strong>Threshold:</strong> <input type="number" step="0.1" value={threshold} />
|
| 33 |
<small>
|
| 34 |
The <a href="https://en.wikipedia.org/wiki/Log_probability" target="_blank" rel="noreferrer">logprob</a> threshold.
|
| 35 |
Tokens with logprobs smaller than this will be marked red.
|
|
@@ -37,15 +66,15 @@ export default function App() {
|
|
| 37 |
</label>
|
| 38 |
<label>
|
| 39 |
<strong>Context:</strong> <small>Context for the text, which can help GPT3 better rank certain words.</small>
|
| 40 |
-
<textarea placeholder="A short essay about picnics" value={context} />
|
| 41 |
</label>
|
| 42 |
<label>
|
| 43 |
<strong>Dictionary:</strong>
|
| 44 |
<small>Known words or phrases. Helpful for uncommon or invented words and names.</small>
|
| 45 |
-
<textarea placeholder="jujubu eschaton Frodo Baggins" value={wordlist} />
|
| 46 |
</label>
|
| 47 |
<label>
|
| 48 |
-
<strong>Show whole prompt:</strong> <input type="checkbox" checked={showWholePrompt} />
|
| 49 |
<small>
|
| 50 |
Show the whole prompt in the token view, instead of just your text. Mostly useful for debugging or curiosity.
|
| 51 |
</small>
|
|
@@ -54,8 +83,8 @@ export default function App() {
|
|
| 54 |
|
| 55 |
<section id="inner">
|
| 56 |
{result}
|
| 57 |
-
<button onClick={
|
| 58 |
-
{
|
| 59 |
</button>
|
| 60 |
|
| 61 |
<p>
|
|
@@ -67,6 +96,6 @@ export default function App() {
|
|
| 67 |
</small>
|
| 68 |
</p>
|
| 69 |
</section>
|
| 70 |
-
|
| 71 |
)
|
| 72 |
}
|
|
|
|
| 1 |
import React, { useState } from "react"
|
| 2 |
+
import { TokenChip } from "./TokenChip"
|
| 3 |
+
|
| 4 |
+
interface Word {
|
| 5 |
+
text: string
|
| 6 |
+
logprob: number
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
async function checkText(text: string): Promise<Word[]> {
|
| 10 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 11 |
+
|
| 12 |
+
const words = text.split(/\b/)
|
| 13 |
+
return words.map(word => ({ text: word, logprob: -word.length }))
|
| 14 |
+
}
|
| 15 |
|
| 16 |
export default function App() {
|
| 17 |
+
const [threshold, setThreshold] = useState(-5.0)
|
| 18 |
const [context, setContext] = useState("")
|
| 19 |
const [wordlist, setWordlist] = useState("")
|
| 20 |
const [showWholePrompt, setShowWholePrompt] = useState(false)
|
|
|
|
| 21 |
const [text, setText] = useState("")
|
| 22 |
+
const [mode, setMode] = useState<"edit" | "check">("edit")
|
| 23 |
+
const [words, setWords] = useState<Word[]>([])
|
| 24 |
|
| 25 |
+
const toggleMode = async () => {
|
| 26 |
+
if (mode === "edit") {
|
| 27 |
+
const checkedWords = await checkText(text)
|
| 28 |
+
setWords(checkedWords)
|
| 29 |
+
setMode("check")
|
| 30 |
+
} else {
|
| 31 |
+
setMode("edit")
|
| 32 |
+
}
|
| 33 |
}
|
| 34 |
|
| 35 |
let result
|
| 36 |
|
| 37 |
+
if (mode === "edit") {
|
| 38 |
+
result = <textarea value={text} onChange={e => setText(e.target.value)} />
|
| 39 |
} else {
|
| 40 |
+
result = (
|
| 41 |
+
<div className="result">
|
| 42 |
+
{words.map((word, index) => (
|
| 43 |
+
<TokenChip
|
| 44 |
+
key={index}
|
| 45 |
+
token={word.text}
|
| 46 |
+
logprob={word.logprob}
|
| 47 |
+
threshold={threshold}
|
| 48 |
+
/>
|
| 49 |
+
))}
|
| 50 |
+
</div>
|
| 51 |
+
)
|
| 52 |
}
|
| 53 |
|
| 54 |
return (
|
| 55 |
+
<main>
|
| 56 |
<h1>GPTed</h1>
|
| 57 |
|
| 58 |
<details>
|
| 59 |
<summary>Advanced settings</summary>
|
| 60 |
<label>
|
| 61 |
+
<strong>Threshold:</strong> <input type="number" step="0.1" value={threshold} onChange={e => setThreshold(Number(e.target.value))} />
|
| 62 |
<small>
|
| 63 |
The <a href="https://en.wikipedia.org/wiki/Log_probability" target="_blank" rel="noreferrer">logprob</a> threshold.
|
| 64 |
Tokens with logprobs smaller than this will be marked red.
|
|
|
|
| 66 |
</label>
|
| 67 |
<label>
|
| 68 |
<strong>Context:</strong> <small>Context for the text, which can help GPT3 better rank certain words.</small>
|
| 69 |
+
<textarea placeholder="A short essay about picnics" value={context} onChange={e => setContext(e.target.value)} />
|
| 70 |
</label>
|
| 71 |
<label>
|
| 72 |
<strong>Dictionary:</strong>
|
| 73 |
<small>Known words or phrases. Helpful for uncommon or invented words and names.</small>
|
| 74 |
+
<textarea placeholder="jujubu eschaton Frodo Baggins" value={wordlist} onChange={e => setWordlist(e.target.value)} />
|
| 75 |
</label>
|
| 76 |
<label>
|
| 77 |
+
<strong>Show whole prompt:</strong> <input type="checkbox" checked={showWholePrompt} onChange={e => setShowWholePrompt(e.target.checked)} />
|
| 78 |
<small>
|
| 79 |
Show the whole prompt in the token view, instead of just your text. Mostly useful for debugging or curiosity.
|
| 80 |
</small>
|
|
|
|
| 83 |
|
| 84 |
<section id="inner">
|
| 85 |
{result}
|
| 86 |
+
<button onClick={toggleMode}>
|
| 87 |
+
{mode === "edit" ? "Check" : "Edit"}
|
| 88 |
</button>
|
| 89 |
|
| 90 |
<p>
|
|
|
|
| 96 |
</small>
|
| 97 |
</p>
|
| 98 |
</section>
|
| 99 |
+
</main>
|
| 100 |
)
|
| 101 |
}
|
frontend/src/index.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import { createRoot } from "react-dom/client"
|
| 2 |
import App from "./components/app"
|
| 3 |
|
|
@@ -5,4 +6,4 @@ const app = document.getElementById("app")
|
|
| 5 |
|
| 6 |
const root = createRoot(app!)
|
| 7 |
|
| 8 |
-
root.render(App
|
|
|
|
| 1 |
+
import React from "react"
|
| 2 |
import { createRoot } from "react-dom/client"
|
| 3 |
import App from "./components/app"
|
| 4 |
|
|
|
|
| 6 |
|
| 7 |
const root = createRoot(app!)
|
| 8 |
|
| 9 |
+
root.render(<App />)
|