File size: 7,695 Bytes
4f64da5
 
 
 
f4af987
4f64da5
 
 
 
 
 
 
 
 
 
6896326
9802882
6896326
 
 
4f64da5
 
 
f4af987
 
 
 
 
 
6896326
 
 
 
 
 
 
 
9802882
6896326
9802882
 
 
 
6896326
9802882
6896326
9802882
 
 
6896326
 
 
 
 
 
 
 
9802882
 
6896326
9802882
 
 
 
 
 
 
6896326
9802882
6896326
 
 
9802882
 
 
 
 
 
4f64da5
 
6896326
 
 
9802882
 
 
 
6896326
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9802882
 
4f64da5
 
 
 
9802882
4f64da5
6896326
4f64da5
6896326
f4af987
 
 
 
 
 
4f64da5
 
 
 
 
6896326
 
4f64da5
 
 
 
6896326
 
 
 
 
 
 
 
f4af987
 
6896326
4f64da5
6896326
 
 
 
 
 
4f64da5
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
"use client"

import { useEffect, useRef, useState, useTransition } from "react"

import { ImageRenderer } from "@/components/business/image-renderer"

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

import { render } from "./render"

import { RenderedScene } from "./types"
import { predict } from "./predict"
import { GameType } from "./games/types"
import { defaultGame, games, getGame } from "./games"

export default function Main() {
  const [isPending, startTransition] = useTransition()
  const [rendered, setRendered] = useState<RenderedScene>({
    assetUrl: "", 
    error: "",
    maskBase64: "",
    segments:[]
  })
  const ref = useRef<GameType>(defaultGame)
  const [situation, setSituation] = useState("")
  const [scene, setScene] = useState("")
  const [dialogue, setDialogue] = useState("")
  const [hoveredActionnable, setHoveredActionnable] = useState("")
  const [isLoading, setLoading] = useState(true)

  const loadNextScene = async (nextSituation?: string, nextActionnables?: string[]) => {
    // console.log(`update view..`)
    setLoading(true)

    await startTransition(async () => {

      // console.log(`getting agent..`)
      // note: we use a ref so that it can be changed in the background
      const type = ref?.current
      const game = getGame(type)

      // console.log(`rendering scene..`)
      const newRendered = await render(
        // SCENE PROMPT
        [...game.getScenePrompt(nextSituation)].join(", "),

        // ACTIONNABLES
        (Array.isArray(nextActionnables) && nextActionnables.length
          ? nextActionnables
          : game.initialActionnables
        ).slice(0, 6) // too many can slow us down it seems
      )

      // detect if something changed in the background
      if (type !== ref?.current) {
        console.log("agent type changed! reloading scene")
        setTimeout(() => { loadNextScene() }, 0)
        return
      } 

      if (newRendered.assetUrl) {
        // console.log(`got a new url: ${newRendered.assetUrl}`)
        setScene(scene)

        setRendered(newRendered)
        setLoading(false)
      }
    })
  }

  useEffect(() => {
    loadNextScene()
  }, [])

  const handleUserAction = async (actionnable: string) => {
    console.log("user actionnable:", actionnable)
 
    // TODO: ask Llama2 what to do about it
    // we need a frame and some actionnables,
    // perhaps even some music or sound effects

    await startTransition(async () => {
 
      setLoading(true)

      const game = getGame(ref.current)
      const initialPrompt = [...game.getScenePrompt()].join(", ")

      const currentPrompt = situation
        ? [...game.getScenePrompt(situation)].join(", ")
        : initialPrompt

      try {
        const basePrompt = [
          `QUESTION: You are the AI game master of a role video game.`,
          initialPrompt !== currentPrompt ? `The initial scene of the game was this: "${initialPrompt}".` : '',
          `The player is currently in this scene: "${currentPrompt}".`,
          `The player has just clicked on "${actionnable}".`
        ]

        console.log("ask the LLM to invent next steps..")

        const rawSituation = await predict([
          ...basePrompt,
          `Please describe the new scene to display in intricate details: the environment, lights, era, characters, objects, textures, light etc. You must include important objects, that the user can click on (eg. characters, doors, vehicles, useful objects).\nANSWER:`
        ].join(" "))

        console.log(`rawSituation: `, rawSituation)

        if (!rawSituation) {
          throw new Error("failed to generate the situation")
        }
        const newSituation = `${rawSituation.split("QUESTION:")[0] || ""}`
        if (!newSituation) {
          throw new Error("failed to parse the situation")
        }

        console.log(`newSituation: `, newSituation)

        const rawActionnables = await predict([
          ...basePrompt,
          `Here are the 4 most important objects visible in this scene, that the user can click on. The list is in JSON (list of strings). You must list basic name of things (eg. "parrot", "chest", "spaceship", "glass", "door", "person", "window", "light", "knob", "button" etc..) \nJSON = [`
        ].join(" "))
        console.log(`rawActionnables: `, rawActionnables)

 
        if (!rawActionnables) {
          throw new Error("failed to generate the actionnables")
        }

        let newActionnables = []
        try {
          newActionnables = (JSON.parse(
            `[${rawActionnables.split("]")[0] || ""}]`
          ) as string[]).map(item =>
            // clean the words to remove any punctuation
            item.replace(/\W/g, '').trim()
          )

          if (!newActionnables.length) {
            throw new Error("no actionnables")
          }
        } catch (err) {
          throw new Error("failed to parse the actionnables")
        }
 
        console.log(`newActionnables: `, newActionnables)

 
        const rawDialogue = await predict([
          ...basePrompt,
          `As a game master, what should you say next? (Only reply with 2 sentences, please).\nANSWER:`
        ].join(" "))
        console.log(`rawDialogue: `, rawDialogue)

        if (!rawDialogue) {
          throw new Error("failed to generate the dialogue")
        }
        const newDialogue = `${rawDialogue.split("QUESTION:")[0] || ""}`
        if (!newDialogue) {
            throw new Error("failed to parse the dialogue")
        }
        console.log(`newDialogue: `, newDialogue)


        setDialogue(newDialogue)
        setSituation(newSituation)

        console.log("loading next scene..")
        await loadNextScene(newSituation, newActionnables)

        // todo we could also use useEffect
      } catch (err) {
       console.error(err)
      }
    })
  }

  return (
    <div className="flex flex-col w-full pt-4">
      <div className="flex flex-col space-y-3 px-2">
        <div className="flex flex-row items-center space-x-3">
          <label className="flex">Select a story:</label>
          <Select
            defaultValue={defaultGame}
            onValueChange={(value) => {
              ref.current = value as GameType
              setRendered({
                assetUrl: "", 
                error: "",
                maskBase64: "",
                segments:[]
              })
            }}>
            <SelectTrigger className="w-[180px]">
              <SelectValue placeholder="Type" />
            </SelectTrigger>
            <SelectContent>
              {Object.entries(games).map(([key, game]) =>
              <SelectItem key={key} value={key}>{game.title}</SelectItem>
              )}
            </SelectContent>
          </Select>
        </div>
        <p className="text-xl">The server is blowing up! Loading a panel may take a few minutes.</p>
        <p className="text-xl">{dialogue}</p>
        <div className="flex flex-row">
          <div className="text-xl mr-2">🔎 Possible items:</div>
          {rendered.segments.map((segment, i) => 
          <div key={i} className="flex flex-row text-xl mr-2">
            <div className="">{segment.label}</div>
            {i < (rendered.segments.length - 1) ? <div>,</div> : null}
          </div>)}
        </div>
        <p className="text-xl font-normal">You may be looking at.. <span className="font-bold">{hoveredActionnable || "nothing"}</span></p>
      </div>
      <ImageRenderer
        rendered={rendered}
        onUserAction={handleUserAction}
        onUserHover={setHoveredActionnable}
        isLoading={isLoading}
      />
    </div>
  )
}