Commit
•
c32ec0d
1
Parent(s):
797cbb8
up
Browse files- public/layouts/layout0.jpg +0 -0
- public/layouts/layout0_hd.jpg +0 -0
- public/layouts/layout1.jpg +0 -0
- public/layouts/layout1_hd.jpg +0 -0
- public/layouts/layout2.jpg +0 -0
- public/layouts/layout2_hd.jpg +0 -0
- public/layouts/layout3 hd.jpg +0 -0
- public/layouts/layout3.jpg +0 -0
- src/app/globals.css +7 -1
- src/app/interface/panel/index.tsx +32 -2
- src/app/interface/top-menu/index.tsx +13 -0
- src/app/interface/zoom/index.tsx +1 -1
- src/app/main.tsx +5 -1
- src/app/queries/getStory.ts +19 -33
- src/app/queries/predict.ts +1 -1
- src/app/store/index.ts +17 -11
- src/lib/cleanJson.ts +16 -0
- src/lib/dirtyCaptionCleaner.ts +27 -2
- src/lib/dirtyLLMJsonParser.ts +20 -7
- src/lib/dirtyLLMResponseCleaner.ts +22 -1
- src/types.ts +1 -1
public/layouts/layout0.jpg
CHANGED
public/layouts/layout0_hd.jpg
ADDED
public/layouts/layout1.jpg
CHANGED
public/layouts/layout1_hd.jpg
ADDED
public/layouts/layout2.jpg
CHANGED
public/layouts/layout2_hd.jpg
ADDED
public/layouts/layout3 hd.jpg
ADDED
public/layouts/layout3.jpg
CHANGED
src/app/globals.css
CHANGED
@@ -30,4 +30,10 @@ body {
|
|
30 |
/* this is the trick to bypass the style={{}} attribute when printing */
|
31 |
@media print {
|
32 |
.comic-page[style] { width: 100vw !important; }
|
33 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
/* this is the trick to bypass the style={{}} attribute when printing */
|
31 |
@media print {
|
32 |
.comic-page[style] { width: 100vw !important; }
|
33 |
+
}
|
34 |
+
|
35 |
+
|
36 |
+
.render-to-image .comic-panel {
|
37 |
+
height: auto !important;
|
38 |
+
/* max-width: fit-content !important; */
|
39 |
+
}
|
src/app/interface/panel/index.tsx
CHANGED
@@ -35,7 +35,11 @@ export function Panel({
|
|
35 |
const panels = useStore(state => state.panels)
|
36 |
const prompt = panels[panel] || ""
|
37 |
|
|
|
|
|
|
|
38 |
const zoomLevel = useStore(state => state.zoomLevel)
|
|
|
39 |
|
40 |
// const setCaption = useStore(state => state.setCaption)
|
41 |
// const captions = useStore(state => state.captions)
|
@@ -179,6 +183,7 @@ export function Panel({
|
|
179 |
*/
|
180 |
|
181 |
const frameClassName = cn(
|
|
|
182 |
`w-full h-full`,
|
183 |
`border-stone-800`,
|
184 |
`transition-all duration-200 ease-in-out`,
|
@@ -214,7 +219,6 @@ export function Panel({
|
|
214 |
}, [rendered.assetUrl, ref.current])
|
215 |
*/
|
216 |
|
217 |
-
|
218 |
if (prompt && !rendered.assetUrl) {
|
219 |
return (
|
220 |
<div className={cn(
|
@@ -227,6 +231,7 @@ export function Panel({
|
|
227 |
)
|
228 |
}
|
229 |
|
|
|
230 |
return (
|
231 |
<div className={cn(
|
232 |
frameClassName,
|
@@ -240,8 +245,33 @@ export function Panel({
|
|
240 |
width={width}
|
241 |
height={height}
|
242 |
alt={rendered.alt}
|
243 |
-
className=
|
|
|
|
|
|
|
244 |
/>}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
</div>
|
246 |
)
|
247 |
}
|
|
|
35 |
const panels = useStore(state => state.panels)
|
36 |
const prompt = panels[panel] || ""
|
37 |
|
38 |
+
const captions = useStore(state => state.captions)
|
39 |
+
const caption = captions[panel] || ""
|
40 |
+
|
41 |
const zoomLevel = useStore(state => state.zoomLevel)
|
42 |
+
const showCaptions = useStore(state => state.showCaptions)
|
43 |
|
44 |
// const setCaption = useStore(state => state.setCaption)
|
45 |
// const captions = useStore(state => state.captions)
|
|
|
183 |
*/
|
184 |
|
185 |
const frameClassName = cn(
|
186 |
+
//`flex`,
|
187 |
`w-full h-full`,
|
188 |
`border-stone-800`,
|
189 |
`transition-all duration-200 ease-in-out`,
|
|
|
219 |
}, [rendered.assetUrl, ref.current])
|
220 |
*/
|
221 |
|
|
|
222 |
if (prompt && !rendered.assetUrl) {
|
223 |
return (
|
224 |
<div className={cn(
|
|
|
231 |
)
|
232 |
}
|
233 |
|
234 |
+
|
235 |
return (
|
236 |
<div className={cn(
|
237 |
frameClassName,
|
|
|
245 |
width={width}
|
246 |
height={height}
|
247 |
alt={rendered.alt}
|
248 |
+
className={cn(
|
249 |
+
`comic-panel w-full h-full object-cover max-w-max`,
|
250 |
+
// showCaptions ? `-mt-11` : ''
|
251 |
+
)}
|
252 |
/>}
|
253 |
+
{/*
|
254 |
+
<div className={cn(
|
255 |
+
`flex`,
|
256 |
+
`bg-stone-50`,
|
257 |
+
`border-stone-800`,
|
258 |
+
`transition-all duration-200 ease-in-out`,
|
259 |
+
zoomLevel > 140 ? `border-b-[2px] md:border-b-[4px]` :
|
260 |
+
zoomLevel > 120 ? `border-b-[1.5px] md:border-b-[3px]` :
|
261 |
+
zoomLevel > 90 ? `border-b-[1px] md:border-b-[2px]` :
|
262 |
+
zoomLevel > 40 ? `border-b-[0.5px] md:border-b-[1px]` :
|
263 |
+
`border-transparent md:border-b-[0.5px]`,
|
264 |
+
`print:border-b-[1.5px]`,
|
265 |
+
showCaptions ? `` : `hidden`,
|
266 |
+
`truncate`,
|
267 |
+
`h-11`,
|
268 |
+
`p-3`
|
269 |
+
)}
|
270 |
+
style={{
|
271 |
+
fontSize: zoomLevel * 0.2
|
272 |
+
}}
|
273 |
+
>{caption}</div>
|
274 |
+
*/}
|
275 |
</div>
|
276 |
)
|
277 |
}
|
src/app/interface/top-menu/index.tsx
CHANGED
@@ -25,6 +25,7 @@ import layoutPreview1 from "../../../../public/layouts/layout1.jpg"
|
|
25 |
import layoutPreview2 from "../../../../public/layouts/layout2.jpg"
|
26 |
import layoutPreview3 from "../../../../public/layouts/layout3.jpg"
|
27 |
import { StaticImageData } from "next/image"
|
|
|
28 |
|
29 |
const layoutIcons: Partial<Record<LayoutName, StaticImageData>> = {
|
30 |
Layout0: layoutPreview0,
|
@@ -41,6 +42,9 @@ export function TopMenu() {
|
|
41 |
const layout = useStore(state => state.layout)
|
42 |
const setLayout = useStore(state => state.setLayout)
|
43 |
|
|
|
|
|
|
|
44 |
const generate = useStore(state => state.generate)
|
45 |
|
46 |
const isGeneratingStory = useStore(state => state.isGeneratingStory)
|
@@ -200,6 +204,15 @@ export function TopMenu() {
|
|
200 |
>
|
201 |
Generate
|
202 |
</Button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
</div>
|
204 |
{/*
|
205 |
Let's add this feature later, because right now people
|
|
|
25 |
import layoutPreview2 from "../../../../public/layouts/layout2.jpg"
|
26 |
import layoutPreview3 from "../../../../public/layouts/layout3.jpg"
|
27 |
import { StaticImageData } from "next/image"
|
28 |
+
import { Switch } from "@/components/ui/switch"
|
29 |
|
30 |
const layoutIcons: Partial<Record<LayoutName, StaticImageData>> = {
|
31 |
Layout0: layoutPreview0,
|
|
|
42 |
const layout = useStore(state => state.layout)
|
43 |
const setLayout = useStore(state => state.setLayout)
|
44 |
|
45 |
+
const setShowCaptions = useStore(state => state.setShowCaptions)
|
46 |
+
const showCaptions = useStore(state => state.showCaptions)
|
47 |
+
|
48 |
const generate = useStore(state => state.generate)
|
49 |
|
50 |
const isGeneratingStory = useStore(state => state.isGeneratingStory)
|
|
|
204 |
>
|
205 |
Generate
|
206 |
</Button>
|
207 |
+
{/*
|
208 |
+
<Switch
|
209 |
+
checked={showCaptions}
|
210 |
+
onCheckedChange={setShowCaptions}
|
211 |
+
/>
|
212 |
+
<Label>
|
213 |
+
Caption
|
214 |
+
</Label>
|
215 |
+
*/}
|
216 |
</div>
|
217 |
{/*
|
218 |
Let's add this feature later, because right now people
|
src/app/interface/zoom/index.tsx
CHANGED
@@ -15,7 +15,7 @@ export function Zoom() {
|
|
15 |
`animation-all duration-300 ease-in-out`,
|
16 |
isGeneratingStory ? `scale-0 opacity-0` : ``,
|
17 |
)}>
|
18 |
-
<div className="font-mono text-xs pb-1 text-stone-700 bg-stone-50 rounded-
|
19 |
Zoom
|
20 |
</div>
|
21 |
<div className="w-2">
|
|
|
15 |
`animation-all duration-300 ease-in-out`,
|
16 |
isGeneratingStory ? `scale-0 opacity-0` : ``,
|
17 |
)}>
|
18 |
+
<div className="font-mono text-xs pb-1 text-stone-700 bg-stone-50 p-1 rounded-sm">
|
19 |
Zoom
|
20 |
</div>
|
21 |
<div className="w-2">
|
src/app/main.tsx
CHANGED
@@ -25,6 +25,7 @@ export default function Main() {
|
|
25 |
const setLayouts = useStore(state => state.setLayouts)
|
26 |
|
27 |
const setPanels = useStore(state => state.setPanels)
|
|
|
28 |
|
29 |
const zoomLevel = useStore(state => state.zoomLevel)
|
30 |
|
@@ -49,13 +50,16 @@ export default function Main() {
|
|
49 |
|
50 |
const nbPanels = 4
|
51 |
const newPanels: string[] = []
|
|
|
52 |
setWaitABitMore(true)
|
53 |
|
54 |
for (let p = 0; p < nbPanels; p++) {
|
55 |
-
|
|
|
56 |
newPanels.push(newPanel.map(chunk => chunk).join(", "))
|
57 |
}
|
58 |
console.log("newPanels:", newPanels)
|
|
|
59 |
setPanels(newPanels)
|
60 |
} catch (err) {
|
61 |
console.error(err)
|
|
|
25 |
const setLayouts = useStore(state => state.setLayouts)
|
26 |
|
27 |
const setPanels = useStore(state => state.setPanels)
|
28 |
+
const setCaptions = useStore(state => state.setCaptions)
|
29 |
|
30 |
const zoomLevel = useStore(state => state.zoomLevel)
|
31 |
|
|
|
50 |
|
51 |
const nbPanels = 4
|
52 |
const newPanels: string[] = []
|
53 |
+
const newCaptions: string[] = []
|
54 |
setWaitABitMore(true)
|
55 |
|
56 |
for (let p = 0; p < nbPanels; p++) {
|
57 |
+
newCaptions.push(llmResponse[p]?.caption || "...")
|
58 |
+
const newPanel = [panelPromptPrefix, llmResponse[p]?.instructions || ""]
|
59 |
newPanels.push(newPanel.map(chunk => chunk).join(", "))
|
60 |
}
|
61 |
console.log("newPanels:", newPanels)
|
62 |
+
setCaptions(newCaptions)
|
63 |
setPanels(newPanels)
|
64 |
} catch (err) {
|
65 |
console.error(err)
|
src/app/queries/getStory.ts
CHANGED
@@ -5,6 +5,8 @@ import { dirtyCaptionCleaner } from "@/lib/dirtyCaptionCleaner"
|
|
5 |
|
6 |
import { predict } from "./predict"
|
7 |
import { Preset } from "../engine/presets"
|
|
|
|
|
8 |
|
9 |
export const getStory = async ({
|
10 |
preset,
|
@@ -12,17 +14,17 @@ export const getStory = async ({
|
|
12 |
}: {
|
13 |
preset: Preset;
|
14 |
prompt: string;
|
15 |
-
}): Promise<
|
16 |
|
17 |
const query = createLlamaPrompt([
|
18 |
{
|
19 |
role: "system",
|
20 |
content: [
|
21 |
`You are a comic book author specialized in ${preset.llmPrompt}`,
|
22 |
-
`Please write detailed drawing instructions for the
|
23 |
-
`Give your response as a JSON array like this: \`Array<{ panel: number; caption: string}>\`.`,
|
24 |
// `Give your response as Markdown bullet points.`,
|
25 |
-
`Be brief in your
|
26 |
].filter(item => item).join("\n")
|
27 |
},
|
28 |
{
|
@@ -53,44 +55,28 @@ export const getStory = async ({
|
|
53 |
}
|
54 |
|
55 |
console.log("Raw response from LLM:", result)
|
56 |
-
const tmp =
|
57 |
|
58 |
-
let
|
59 |
|
60 |
try {
|
61 |
-
|
62 |
} catch (err) {
|
63 |
console.log(`failed to read LLM response: ${err}`)
|
64 |
|
65 |
-
// it is possible that the LLM has generated multiple JSON files like this:
|
66 |
-
|
67 |
-
/*
|
68 |
-
[ {
|
69 |
-
"panel": 1,
|
70 |
-
"caption": "A samurai stands at the edge of a bustling street in San Francisco, looking out of place among the hippies and beatniks."
|
71 |
-
} ]
|
72 |
-
|
73 |
-
[ {
|
74 |
-
"panel": 2,
|
75 |
-
"caption": "The samurai spots a group of young people playing music on the sidewalk. He approaches them, intrigued."
|
76 |
-
} ]
|
77 |
-
*/
|
78 |
-
try {
|
79 |
-
// in that case, we can try to repair it like so:
|
80 |
-
let strategy2 = `[${tmp.split("[").pop() || ""}`
|
81 |
-
strategy2.replaceAll("[", ",")
|
82 |
-
|
83 |
-
captions = dirtyLLMJsonParser(strategy2)
|
84 |
-
} catch (err2) {
|
85 |
// in case of failure here, it might be because the LLM hallucinated a completely different response,
|
86 |
// such as markdown. There is no real solution.. but we can try a fallback:
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
)
|
92 |
-
|
|
|
|
|
|
|
|
|
93 |
}
|
94 |
|
95 |
-
return
|
96 |
}
|
|
|
5 |
|
6 |
import { predict } from "./predict"
|
7 |
import { Preset } from "../engine/presets"
|
8 |
+
import { LLMResponse } from "@/types"
|
9 |
+
import { cleanJson } from "@/lib/cleanJson"
|
10 |
|
11 |
export const getStory = async ({
|
12 |
preset,
|
|
|
14 |
}: {
|
15 |
preset: Preset;
|
16 |
prompt: string;
|
17 |
+
}): Promise<LLMResponse> => {
|
18 |
|
19 |
const query = createLlamaPrompt([
|
20 |
{
|
21 |
role: "system",
|
22 |
content: [
|
23 |
`You are a comic book author specialized in ${preset.llmPrompt}`,
|
24 |
+
`Please write detailed drawing instructions and a one-sentence short caption for the 4 panels of a new silent comic book page.`,
|
25 |
+
`Give your response as a JSON array like this: \`Array<{ panel: number; instructions: string; caption: string}>\`.`,
|
26 |
// `Give your response as Markdown bullet points.`,
|
27 |
+
`Be brief in your 4 instructions and captions, don't add your own comments. Be straight to the point, and never reply things like "Sure, I can.." etc.`
|
28 |
].filter(item => item).join("\n")
|
29 |
},
|
30 |
{
|
|
|
55 |
}
|
56 |
|
57 |
console.log("Raw response from LLM:", result)
|
58 |
+
const tmp = cleanJson(result)
|
59 |
|
60 |
+
let llmResponse: LLMResponse = []
|
61 |
|
62 |
try {
|
63 |
+
llmResponse = dirtyLLMJsonParser(tmp)
|
64 |
} catch (err) {
|
65 |
console.log(`failed to read LLM response: ${err}`)
|
66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
// in case of failure here, it might be because the LLM hallucinated a completely different response,
|
68 |
// such as markdown. There is no real solution.. but we can try a fallback:
|
69 |
|
70 |
+
llmResponse = (
|
71 |
+
tmp.split("*")
|
72 |
+
.map(item => item.trim())
|
73 |
+
.map((cap, i) => ({
|
74 |
+
panel: i,
|
75 |
+
caption: cap,
|
76 |
+
instructions: cap,
|
77 |
+
}))
|
78 |
+
)
|
79 |
}
|
80 |
|
81 |
+
return llmResponse.map(res => dirtyCaptionCleaner(res))
|
82 |
}
|
src/app/queries/predict.ts
CHANGED
@@ -17,7 +17,7 @@ export async function predict(inputs: string) {
|
|
17 |
do_sample: true,
|
18 |
|
19 |
// hard limit for max_new_tokens is 1512
|
20 |
-
max_new_tokens:
|
21 |
return_full_text: false,
|
22 |
}
|
23 |
})) {
|
|
|
17 |
do_sample: true,
|
18 |
|
19 |
// hard limit for max_new_tokens is 1512
|
20 |
+
max_new_tokens: 330, // 1150,
|
21 |
return_full_text: false,
|
22 |
}
|
23 |
})) {
|
src/app/store/index.ts
CHANGED
@@ -13,7 +13,8 @@ export const useStore = create<{
|
|
13 |
preset: Preset
|
14 |
nbFrames: number
|
15 |
panels: string[]
|
16 |
-
captions:
|
|
|
17 |
layout: LayoutName
|
18 |
layouts: LayoutName[]
|
19 |
zoomLevel: number
|
@@ -26,9 +27,10 @@ export const useStore = create<{
|
|
26 |
setFont: (font: FontName) => void
|
27 |
setPreset: (preset: Preset) => void
|
28 |
setPanels: (panels: string[]) => void
|
|
|
29 |
setLayout: (layout: LayoutName) => void
|
30 |
setLayouts: (layouts: LayoutName[]) => void
|
31 |
-
|
32 |
setZoomLevel: (zoomLevel: number) => void
|
33 |
setPage: (page: HTMLDivElement) => void
|
34 |
setGeneratingStory: (isGeneratingStory: boolean) => void
|
@@ -40,11 +42,12 @@ export const useStore = create<{
|
|
40 |
}>((set, get) => ({
|
41 |
prompt: "",
|
42 |
font: "actionman",
|
43 |
-
preset:
|
44 |
nbFrames: 1,
|
45 |
panels: [],
|
46 |
-
captions:
|
47 |
-
|
|
|
48 |
layouts: getRandomLayoutNames(),
|
49 |
zoomLevel: 60,
|
50 |
page: undefined as unknown as HTMLDivElement,
|
@@ -81,12 +84,14 @@ export const useStore = create<{
|
|
81 |
})
|
82 |
},
|
83 |
setPanels: (panels: string[]) => set({ panels }),
|
84 |
-
|
85 |
set({
|
86 |
-
captions
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
90 |
})
|
91 |
},
|
92 |
setLayout: (layoutName: LayoutName) => {
|
@@ -125,6 +130,7 @@ export const useStore = create<{
|
|
125 |
const { page } = get()
|
126 |
if (!page) { return "" }
|
127 |
|
|
|
128 |
const canvas = await html2canvas(page)
|
129 |
console.log("canvas:", canvas)
|
130 |
|
@@ -154,7 +160,7 @@ export const useStore = create<{
|
|
154 |
set({
|
155 |
prompt,
|
156 |
panels: [],
|
157 |
-
captions:
|
158 |
preset: presetName === "random"
|
159 |
? getRandomPreset()
|
160 |
: getPreset(presetName),
|
|
|
13 |
preset: Preset
|
14 |
nbFrames: number
|
15 |
panels: string[]
|
16 |
+
captions: string[]
|
17 |
+
showCaptions: boolean
|
18 |
layout: LayoutName
|
19 |
layouts: LayoutName[]
|
20 |
zoomLevel: number
|
|
|
27 |
setFont: (font: FontName) => void
|
28 |
setPreset: (preset: Preset) => void
|
29 |
setPanels: (panels: string[]) => void
|
30 |
+
setShowCaptions: (showCaptions: boolean) => void
|
31 |
setLayout: (layout: LayoutName) => void
|
32 |
setLayouts: (layouts: LayoutName[]) => void
|
33 |
+
setCaptions: (captions: string[]) => void
|
34 |
setZoomLevel: (zoomLevel: number) => void
|
35 |
setPage: (page: HTMLDivElement) => void
|
36 |
setGeneratingStory: (isGeneratingStory: boolean) => void
|
|
|
42 |
}>((set, get) => ({
|
43 |
prompt: "",
|
44 |
font: "actionman",
|
45 |
+
preset: getRandomPreset(),
|
46 |
nbFrames: 1,
|
47 |
panels: [],
|
48 |
+
captions: [],
|
49 |
+
showCaptions: false,
|
50 |
+
layout: "random",
|
51 |
layouts: getRandomLayoutNames(),
|
52 |
zoomLevel: 60,
|
53 |
page: undefined as unknown as HTMLDivElement,
|
|
|
84 |
})
|
85 |
},
|
86 |
setPanels: (panels: string[]) => set({ panels }),
|
87 |
+
setCaptions: (captions: string[]) => {
|
88 |
set({
|
89 |
+
captions,
|
90 |
+
})
|
91 |
+
},
|
92 |
+
setShowCaptions: (showCaptions: boolean) => {
|
93 |
+
set({
|
94 |
+
showCaptions,
|
95 |
})
|
96 |
},
|
97 |
setLayout: (layoutName: LayoutName) => {
|
|
|
130 |
const { page } = get()
|
131 |
if (!page) { return "" }
|
132 |
|
133 |
+
|
134 |
const canvas = await html2canvas(page)
|
135 |
console.log("canvas:", canvas)
|
136 |
|
|
|
160 |
set({
|
161 |
prompt,
|
162 |
panels: [],
|
163 |
+
captions: [],
|
164 |
preset: presetName === "random"
|
165 |
? getRandomPreset()
|
166 |
: getPreset(presetName),
|
src/lib/cleanJson.ts
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { dirtyLLMResponseCleaner } from "./dirtyLLMResponseCleaner"
|
2 |
+
|
3 |
+
export function cleanJson(input: string) {
|
4 |
+
|
5 |
+
let tmp = dirtyLLMResponseCleaner(input)
|
6 |
+
|
7 |
+
// we only keep what's after the first [
|
8 |
+
tmp = `[${tmp.split("[").pop() || ""}`
|
9 |
+
|
10 |
+
// and before the first ]
|
11 |
+
tmp = `${tmp.split("]").shift() || ""}]`
|
12 |
+
|
13 |
+
tmp = dirtyLLMResponseCleaner(tmp)
|
14 |
+
|
15 |
+
return tmp
|
16 |
+
}
|
src/lib/dirtyCaptionCleaner.ts
CHANGED
@@ -1,3 +1,28 @@
|
|
1 |
-
export function dirtyCaptionCleaner(
|
2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
}
|
|
|
1 |
+
export function dirtyCaptionCleaner({
|
2 |
+
panel,
|
3 |
+
instructions,
|
4 |
+
caption
|
5 |
+
}: {
|
6 |
+
panel: number;
|
7 |
+
instructions: string;
|
8 |
+
caption: string
|
9 |
+
}) {
|
10 |
+
return {
|
11 |
+
panel,
|
12 |
+
instructions: (
|
13 |
+
// need to remove from LLM garbage here, too
|
14 |
+
(instructions.split(":").pop() || "")
|
15 |
+
.replaceAll("Show a", "")
|
16 |
+
.replaceAll("Show the", "")
|
17 |
+
.replaceAll("Opens with a", "")
|
18 |
+
.replaceAll("Opens with the", "")
|
19 |
+
.replaceAll("Opens with", "")
|
20 |
+
.replaceAll("Cut to a", "")
|
21 |
+
.replaceAll("Cut to the", "")
|
22 |
+
.replaceAll("Cut to", "")
|
23 |
+
.replaceAll("End with a", "")
|
24 |
+
.replaceAll("End with", "").trim() || ""
|
25 |
+
),
|
26 |
+
caption: caption.split(":").pop()?.trim() || "",
|
27 |
+
}
|
28 |
}
|
src/lib/dirtyLLMJsonParser.ts
CHANGED
@@ -1,15 +1,28 @@
|
|
1 |
import { LLMResponse } from "@/types"
|
|
|
2 |
|
3 |
-
export function dirtyLLMJsonParser(input: string):
|
4 |
-
// we only keep what's after the first [
|
5 |
-
let jsonOrNot = `[${input.split("[").pop() || ""}`
|
6 |
|
7 |
-
|
8 |
-
|
|
|
|
|
|
|
9 |
|
10 |
const jsonData = JSON.parse(jsonOrNot) as LLMResponse
|
11 |
|
12 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
-
return
|
15 |
}
|
|
|
1 |
import { LLMResponse } from "@/types"
|
2 |
+
import { cleanJson } from "./cleanJson"
|
3 |
|
4 |
+
export function dirtyLLMJsonParser(input: string): LLMResponse {
|
|
|
|
|
5 |
|
6 |
+
if (input.includes("```")) {
|
7 |
+
input = input.split("```")[0]
|
8 |
+
}
|
9 |
+
// we only keep what's after the first [
|
10 |
+
let jsonOrNot = cleanJson(input)
|
11 |
|
12 |
const jsonData = JSON.parse(jsonOrNot) as LLMResponse
|
13 |
|
14 |
+
const results = jsonData.map((item, i) => {
|
15 |
+
let panel = i
|
16 |
+
let caption = item.caption ? item.caption.trim() : ''
|
17 |
+
let instructions = item.instructions ? item.instructions.trim() : ''
|
18 |
+
if (!instructions && caption) {
|
19 |
+
instructions = caption
|
20 |
+
}
|
21 |
+
if (!caption && instructions) {
|
22 |
+
caption = instructions
|
23 |
+
}
|
24 |
+
return { panel, caption, instructions }
|
25 |
+
})
|
26 |
|
27 |
+
return results
|
28 |
}
|
src/lib/dirtyLLMResponseCleaner.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
export function dirtyLLMResponseCleaner(input: string) {
|
2 |
-
|
3 |
`${input || ""}`
|
4 |
// a summary of all the weird hallucinations I saw it make..
|
5 |
.replaceAll(`"]`, `"}]`)
|
@@ -10,6 +10,8 @@ export function dirtyLLMResponseCleaner(input: string) {
|
|
10 |
.replaceAll(`"\n ]`, `"}]`)
|
11 |
.replaceAll("}}", "}")
|
12 |
.replaceAll("]]", "]")
|
|
|
|
|
13 |
.replaceAll(",,", ",")
|
14 |
.replaceAll("[0]", "")
|
15 |
.replaceAll("[1]", "")
|
@@ -22,4 +24,23 @@ export function dirtyLLMResponseCleaner(input: string) {
|
|
22 |
.replaceAll("[panel 3]", "")
|
23 |
.replaceAll("[panel 4]", "")
|
24 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
}
|
|
|
1 |
export function dirtyLLMResponseCleaner(input: string) {
|
2 |
+
let str = (
|
3 |
`${input || ""}`
|
4 |
// a summary of all the weird hallucinations I saw it make..
|
5 |
.replaceAll(`"]`, `"}]`)
|
|
|
10 |
.replaceAll(`"\n ]`, `"}]`)
|
11 |
.replaceAll("}}", "}")
|
12 |
.replaceAll("]]", "]")
|
13 |
+
.replaceAll("[[", "[")
|
14 |
+
.replaceAll("{{", "{")
|
15 |
.replaceAll(",,", ",")
|
16 |
.replaceAll("[0]", "")
|
17 |
.replaceAll("[1]", "")
|
|
|
24 |
.replaceAll("[panel 3]", "")
|
25 |
.replaceAll("[panel 4]", "")
|
26 |
)
|
27 |
+
|
28 |
+
// repair missing end of JSON array
|
29 |
+
if (str.at(-1) === '}') {
|
30 |
+
str = str + "]"
|
31 |
+
}
|
32 |
+
|
33 |
+
if (str.at(-1) === '"') {
|
34 |
+
str = str + "}]"
|
35 |
+
}
|
36 |
+
|
37 |
+
if (str[0] === '{') {
|
38 |
+
str = "[" + str
|
39 |
+
}
|
40 |
+
|
41 |
+
if (str[0] === '"') {
|
42 |
+
str = "[{" + str
|
43 |
+
}
|
44 |
+
|
45 |
+
return str
|
46 |
}
|
src/types.ts
CHANGED
@@ -79,4 +79,4 @@ export interface ImageAnalysisResponse {
|
|
79 |
error?: string
|
80 |
}
|
81 |
|
82 |
-
export type LLMResponse = Array<{panel: number; caption: string }>
|
|
|
79 |
error?: string
|
80 |
}
|
81 |
|
82 |
+
export type LLMResponse = Array<{panel: number; instructions: string; caption: string }>
|