|
|
@@ -1,4 +1,4 @@
|
|
|
-import type { UserMessage } from "@opencode-ai/sdk/v2"
|
|
|
+import type { Project, UserMessage } from "@opencode-ai/sdk/v2"
|
|
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
|
import {
|
|
|
onCleanup,
|
|
|
@@ -20,11 +20,13 @@ import { createStore } from "solid-js/store"
|
|
|
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
|
|
import { Select } from "@opencode-ai/ui/select"
|
|
|
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
|
|
-import { Mark } from "@opencode-ai/ui/logo"
|
|
|
+import { Button } from "@opencode-ai/ui/button"
|
|
|
+import { showToast } from "@opencode-ai/ui/toast"
|
|
|
import { base64Encode, checksum } from "@opencode-ai/util/encode"
|
|
|
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
|
|
import { NewSessionView, SessionHeader } from "@/components/session"
|
|
|
import { useComments } from "@/context/comments"
|
|
|
+import { useGlobalSync } from "@/context/global-sync"
|
|
|
import { useLanguage } from "@/context/language"
|
|
|
import { useLayout } from "@/context/layout"
|
|
|
import { usePrompt } from "@/context/prompt"
|
|
|
@@ -41,6 +43,7 @@ import { TerminalPanel } from "@/pages/session/terminal-panel"
|
|
|
import { useSessionCommands } from "@/pages/session/use-session-commands"
|
|
|
import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
|
|
|
import { same } from "@/utils/same"
|
|
|
+import { formatServerError } from "@/utils/server-errors"
|
|
|
|
|
|
const emptyUserMessages: UserMessage[] = []
|
|
|
|
|
|
@@ -252,6 +255,7 @@ function createSessionHistoryWindow(input: SessionHistoryWindowInput) {
|
|
|
}
|
|
|
|
|
|
export default function Page() {
|
|
|
+ const globalSync = useGlobalSync()
|
|
|
const layout = useLayout()
|
|
|
const local = useLocal()
|
|
|
const file = useFile()
|
|
|
@@ -278,6 +282,7 @@ export default function Page() {
|
|
|
})
|
|
|
|
|
|
const [ui, setUi] = createStore({
|
|
|
+ git: false,
|
|
|
pendingMessage: undefined as string | undefined,
|
|
|
scrollGesture: 0,
|
|
|
scroll: {
|
|
|
@@ -494,6 +499,46 @@ export default function Page() {
|
|
|
return "session.review.noVcs"
|
|
|
})
|
|
|
|
|
|
+ function upsert(next: Project) {
|
|
|
+ const list = globalSync.data.project
|
|
|
+ sync.set("project", next.id)
|
|
|
+ const idx = list.findIndex((item) => item.id === next.id)
|
|
|
+ if (idx >= 0) {
|
|
|
+ globalSync.set(
|
|
|
+ "project",
|
|
|
+ list.map((item, i) => (i === idx ? { ...item, ...next } : item)),
|
|
|
+ )
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const at = list.findIndex((item) => item.id > next.id)
|
|
|
+ if (at >= 0) {
|
|
|
+ globalSync.set("project", [...list.slice(0, at), next, ...list.slice(at)])
|
|
|
+ return
|
|
|
+ }
|
|
|
+ globalSync.set("project", [...list, next])
|
|
|
+ }
|
|
|
+
|
|
|
+ function initGit() {
|
|
|
+ if (ui.git) return
|
|
|
+ setUi("git", true)
|
|
|
+ void sdk.client.project
|
|
|
+ .initGit()
|
|
|
+ .then((x) => {
|
|
|
+ if (!x.data) return
|
|
|
+ upsert(x.data)
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ showToast({
|
|
|
+ variant: "error",
|
|
|
+ title: language.t("common.requestFailed"),
|
|
|
+ description: formatServerError(err, language.t),
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ setUi("git", false)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
let inputRef!: HTMLDivElement
|
|
|
let promptDock: HTMLDivElement | undefined
|
|
|
let dockHeight = 0
|
|
|
@@ -727,23 +772,28 @@ export default function Page() {
|
|
|
const changesOptions = ["session", "turn"] as const
|
|
|
const changesOptionsList = [...changesOptions]
|
|
|
|
|
|
- const changesTitle = () => (
|
|
|
- <Select
|
|
|
- options={changesOptionsList}
|
|
|
- current={store.changes}
|
|
|
- label={(option) =>
|
|
|
- option === "session" ? language.t("ui.sessionReview.title") : language.t("ui.sessionReview.title.lastTurn")
|
|
|
- }
|
|
|
- onSelect={(option) => option && setStore("changes", option)}
|
|
|
- variant="ghost"
|
|
|
- size="small"
|
|
|
- valueClass="text-14-medium"
|
|
|
- />
|
|
|
- )
|
|
|
+ const changesTitle = () => {
|
|
|
+ if (!hasReview()) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Select
|
|
|
+ options={changesOptionsList}
|
|
|
+ current={store.changes}
|
|
|
+ label={(option) =>
|
|
|
+ option === "session" ? language.t("ui.sessionReview.title") : language.t("ui.sessionReview.title.lastTurn")
|
|
|
+ }
|
|
|
+ onSelect={(option) => option && setStore("changes", option)}
|
|
|
+ variant="ghost"
|
|
|
+ size="small"
|
|
|
+ valueClass="text-14-medium"
|
|
|
+ />
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
const emptyTurn = () => (
|
|
|
<div class="h-full pb-30 flex flex-col items-center justify-center text-center gap-6">
|
|
|
- <Mark class="w-14 opacity-10" />
|
|
|
<div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.noChanges")}</div>
|
|
|
</div>
|
|
|
)
|
|
|
@@ -809,9 +859,23 @@ export default function Page() {
|
|
|
empty={
|
|
|
store.changes === "turn" ? (
|
|
|
emptyTurn()
|
|
|
+ ) : reviewEmptyKey() === "session.review.noVcs" ? (
|
|
|
+ <div class={input.emptyClass}>
|
|
|
+ <div class="flex flex-col gap-3">
|
|
|
+ <div class="text-14-medium text-text-strong">Create a Git repository</div>
|
|
|
+ <div
|
|
|
+ class="text-14-regular text-text-base max-w-md"
|
|
|
+ style={{ "line-height": "var(--line-height-normal)" }}
|
|
|
+ >
|
|
|
+ Track, review, and undo changes in this project
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <Button size="large" disabled={ui.git} onClick={initGit}>
|
|
|
+ {ui.git ? "Creating Git repository..." : "Create Git repository"}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
) : (
|
|
|
<div class={input.emptyClass}>
|
|
|
- <Mark class="w-14 opacity-10" />
|
|
|
<div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div>
|
|
|
</div>
|
|
|
)
|