mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
add fullscreen view to permission prompt
This commit is contained in:
parent
29bf731d47
commit
c86c2acf4c
@ -1,4 +1,4 @@
|
|||||||
- To test opencode in the `packages/opencode` directory you can run `bun dev`
|
- To test opencode in `packages/opencode`, run `bun dev`.
|
||||||
- To regenerate the javascript SDK, run ./packages/sdk/js/script/build.ts
|
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
||||||
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
||||||
- the default branch in this repo is `dev`
|
- The default branch in this repo is `dev`.
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
## Style Guide
|
## Style Guide
|
||||||
|
|
||||||
- Try to keep things in one function unless composable or reusable
|
- Keep things in one function unless composable or reusable
|
||||||
- AVOID unnecessary destructuring of variables. instead of doing `const { a, b }
|
- Avoid unnecessary destructuring. Instead of `const { a, b } = obj`, use `obj.a` and `obj.b` to preserve context
|
||||||
= obj` just reference it as obj.a and obj.b. this preserves context
|
- Avoid `try`/`catch` where possible
|
||||||
- AVOID `try`/`catch` where possible
|
- Avoid using the `any` type
|
||||||
- AVOID using `any` type
|
- Prefer single word variable names where possible
|
||||||
- PREFER single word variable names where possible
|
- Use Bun APIs when possible, like `Bun.file()`
|
||||||
- Use as many bun apis as possible like Bun.file()
|
|
||||||
|
|
||||||
# Avoid let statements
|
# Avoid let statements
|
||||||
|
|
||||||
we don't like let statements, especially combined with if/else statements.
|
We don't like `let` statements, especially combined with if/else statements.
|
||||||
prefer const
|
Prefer `const`.
|
||||||
|
|
||||||
This is bad:
|
|
||||||
|
|
||||||
Good:
|
Good:
|
||||||
|
|
||||||
@ -32,7 +29,7 @@ else foo = 2
|
|||||||
|
|
||||||
# Avoid else statements
|
# Avoid else statements
|
||||||
|
|
||||||
Prefer early returns or even using `iife` to avoid else statements
|
Prefer early returns or using an `iife` to avoid else statements.
|
||||||
|
|
||||||
Good:
|
Good:
|
||||||
|
|
||||||
|
|||||||
@ -563,25 +563,27 @@ export function Prompt(props: PromptProps) {
|
|||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
sdk.client.session.prompt({
|
sdk.client.session
|
||||||
sessionID,
|
.prompt({
|
||||||
...selectedModel,
|
sessionID,
|
||||||
messageID,
|
...selectedModel,
|
||||||
agent: local.agent.current().name,
|
messageID,
|
||||||
model: selectedModel,
|
agent: local.agent.current().name,
|
||||||
variant,
|
model: selectedModel,
|
||||||
parts: [
|
variant,
|
||||||
{
|
parts: [
|
||||||
id: Identifier.ascending("part"),
|
{
|
||||||
type: "text",
|
id: Identifier.ascending("part"),
|
||||||
text: inputText,
|
type: "text",
|
||||||
},
|
text: inputText,
|
||||||
...nonTextParts.map((x) => ({
|
},
|
||||||
id: Identifier.ascending("part"),
|
...nonTextParts.map((x) => ({
|
||||||
...x,
|
id: Identifier.ascending("part"),
|
||||||
})),
|
...x,
|
||||||
],
|
})),
|
||||||
})
|
],
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
history.append({
|
history.append({
|
||||||
...store.prompt,
|
...store.prompt,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createMemo, For, Match, Show, Switch } from "solid-js"
|
import { createMemo, For, Match, Show, Switch } from "solid-js"
|
||||||
import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid"
|
import { Portal, useKeyboard, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||||
import type { TextareaRenderable } from "@opentui/core"
|
import type { TextareaRenderable } from "@opentui/core"
|
||||||
import { useKeybind } from "../../context/keybind"
|
import { useKeybind } from "../../context/keybind"
|
||||||
import { useTheme, selectedForeground } from "../../context/theme"
|
import { useTheme, selectedForeground } from "../../context/theme"
|
||||||
@ -11,6 +11,7 @@ import { useSync } from "../../context/sync"
|
|||||||
import { useTextareaKeybindings } from "../../component/textarea-keybindings"
|
import { useTextareaKeybindings } from "../../component/textarea-keybindings"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
|
||||||
|
import { Keybind } from "@/util/keybind"
|
||||||
import { Locale } from "@/util/locale"
|
import { Locale } from "@/util/locale"
|
||||||
|
|
||||||
type PermissionStage = "permission" | "always" | "reject"
|
type PermissionStage = "permission" | "always" | "reject"
|
||||||
@ -32,7 +33,9 @@ function filetype(input?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function EditBody(props: { request: PermissionRequest }) {
|
function EditBody(props: { request: PermissionRequest }) {
|
||||||
const { theme, syntax } = useTheme()
|
const themeState = useTheme()
|
||||||
|
const theme = themeState.theme
|
||||||
|
const syntax = themeState.syntax
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
|
|
||||||
@ -54,7 +57,7 @@ function EditBody(props: { request: PermissionRequest }) {
|
|||||||
<text fg={theme.textMuted}>Edit {normalizePath(filepath())}</text>
|
<text fg={theme.textMuted}>Edit {normalizePath(filepath())}</text>
|
||||||
</box>
|
</box>
|
||||||
<Show when={diff()}>
|
<Show when={diff()}>
|
||||||
<box maxHeight={Math.floor(dimensions().height / 4)} overflow="scroll">
|
<scrollbox height="100%">
|
||||||
<diff
|
<diff
|
||||||
diff={diff()}
|
diff={diff()}
|
||||||
view={view()}
|
view={view()}
|
||||||
@ -74,7 +77,7 @@ function EditBody(props: { request: PermissionRequest }) {
|
|||||||
addedLineNumberBg={theme.diffAddedLineNumberBg}
|
addedLineNumberBg={theme.diffAddedLineNumberBg}
|
||||||
removedLineNumberBg={theme.diffRemovedLineNumberBg}
|
removedLineNumberBg={theme.diffRemovedLineNumberBg}
|
||||||
/>
|
/>
|
||||||
</box>
|
</scrollbox>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
@ -172,86 +175,95 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
|
|||||||
message: message || undefined,
|
message: message || undefined,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
onCancel={() => setStore("stage", "permission")}
|
onCancel={() => {
|
||||||
|
setStore("stage", "permission")
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={store.stage === "permission"}>
|
<Match when={store.stage === "permission"}>
|
||||||
<Prompt
|
{(() => {
|
||||||
title="Permission required"
|
const body = (
|
||||||
body={
|
<Prompt
|
||||||
<Switch>
|
title="Permission required"
|
||||||
<Match when={props.request.permission === "edit"}>
|
body={
|
||||||
<EditBody request={props.request} />
|
<Switch>
|
||||||
</Match>
|
<Match when={props.request.permission === "edit"}>
|
||||||
<Match when={props.request.permission === "read"}>
|
<EditBody request={props.request} />
|
||||||
<TextBody icon="→" title={`Read ` + normalizePath(input().filePath as string)} />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "read"}>
|
||||||
<Match when={props.request.permission === "glob"}>
|
<TextBody icon="→" title={`Read ` + normalizePath(input().filePath as string)} />
|
||||||
<TextBody icon="✱" title={`Glob "` + (input().pattern ?? "") + `"`} />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "glob"}>
|
||||||
<Match when={props.request.permission === "grep"}>
|
<TextBody icon="✱" title={`Glob "` + (input().pattern ?? "") + `"`} />
|
||||||
<TextBody icon="✱" title={`Grep "` + (input().pattern ?? "") + `"`} />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "grep"}>
|
||||||
<Match when={props.request.permission === "list"}>
|
<TextBody icon="✱" title={`Grep "` + (input().pattern ?? "") + `"`} />
|
||||||
<TextBody icon="→" title={`List ` + normalizePath(input().path as string)} />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "list"}>
|
||||||
<Match when={props.request.permission === "bash"}>
|
<TextBody icon="→" title={`List ` + normalizePath(input().path as string)} />
|
||||||
<TextBody
|
</Match>
|
||||||
icon="#"
|
<Match when={props.request.permission === "bash"}>
|
||||||
title={(input().description as string) ?? ""}
|
<TextBody
|
||||||
description={("$ " + input().command) as string}
|
icon="#"
|
||||||
/>
|
title={(input().description as string) ?? ""}
|
||||||
</Match>
|
description={("$ " + input().command) as string}
|
||||||
<Match when={props.request.permission === "task"}>
|
/>
|
||||||
<TextBody
|
</Match>
|
||||||
icon="#"
|
<Match when={props.request.permission === "task"}>
|
||||||
title={`${Locale.titlecase((input().subagent_type as string) ?? "Unknown")} Task`}
|
<TextBody
|
||||||
description={"◉ " + input().description}
|
icon="#"
|
||||||
/>
|
title={`${Locale.titlecase((input().subagent_type as string) ?? "Unknown")} Task`}
|
||||||
</Match>
|
description={"◉ " + input().description}
|
||||||
<Match when={props.request.permission === "webfetch"}>
|
/>
|
||||||
<TextBody icon="%" title={`WebFetch ` + (input().url ?? "")} />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "webfetch"}>
|
||||||
<Match when={props.request.permission === "websearch"}>
|
<TextBody icon="%" title={`WebFetch ` + (input().url ?? "")} />
|
||||||
<TextBody icon="◈" title={`Exa Web Search "` + (input().query ?? "") + `"`} />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "websearch"}>
|
||||||
<Match when={props.request.permission === "codesearch"}>
|
<TextBody icon="◈" title={`Exa Web Search "` + (input().query ?? "") + `"`} />
|
||||||
<TextBody icon="◇" title={`Exa Code Search "` + (input().query ?? "") + `"`} />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "codesearch"}>
|
||||||
<Match when={props.request.permission === "external_directory"}>
|
<TextBody icon="◇" title={`Exa Code Search "` + (input().query ?? "") + `"`} />
|
||||||
<TextBody icon="←" title={`Access external directory ` + normalizePath(input().path as string)} />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "external_directory"}>
|
||||||
<Match when={props.request.permission === "doom_loop"}>
|
<TextBody icon="←" title={`Access external directory ` + normalizePath(input().path as string)} />
|
||||||
<TextBody icon="⟳" title="Continue after repeated failures" />
|
</Match>
|
||||||
</Match>
|
<Match when={props.request.permission === "doom_loop"}>
|
||||||
<Match when={true}>
|
<TextBody icon="⟳" title="Continue after repeated failures" />
|
||||||
<TextBody icon="⚙" title={`Call tool ` + props.request.permission} />
|
</Match>
|
||||||
</Match>
|
<Match when={true}>
|
||||||
</Switch>
|
<TextBody icon="⚙" title={`Call tool ` + props.request.permission} />
|
||||||
}
|
</Match>
|
||||||
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
|
</Switch>
|
||||||
escapeKey="reject"
|
|
||||||
onSelect={(option) => {
|
|
||||||
if (option === "always") {
|
|
||||||
setStore("stage", "always")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (option === "reject") {
|
|
||||||
if (session()?.parentID) {
|
|
||||||
setStore("stage", "reject")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
sdk.client.permission.reply({
|
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
|
||||||
reply: "reject",
|
escapeKey="reject"
|
||||||
requestID: props.request.id,
|
fullscreen
|
||||||
})
|
onSelect={(option) => {
|
||||||
}
|
if (option === "always") {
|
||||||
sdk.client.permission.reply({
|
setStore("stage", "always")
|
||||||
reply: "once",
|
return
|
||||||
requestID: props.request.id,
|
}
|
||||||
})
|
if (option === "reject") {
|
||||||
}}
|
if (session()?.parentID) {
|
||||||
/>
|
setStore("stage", "reject")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sdk.client.permission.reply({
|
||||||
|
reply: "reject",
|
||||||
|
requestID: props.request.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sdk.client.permission.reply({
|
||||||
|
reply: "once",
|
||||||
|
requestID: props.request.id,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return body
|
||||||
|
})()}
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
@ -327,14 +339,18 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||||||
body: JSX.Element
|
body: JSX.Element
|
||||||
options: T
|
options: T
|
||||||
escapeKey?: keyof T
|
escapeKey?: keyof T
|
||||||
|
fullscreen?: boolean
|
||||||
onSelect: (option: keyof T) => void
|
onSelect: (option: keyof T) => void
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const keybind = useKeybind()
|
const keybind = useKeybind()
|
||||||
|
const dimensions = useTerminalDimensions()
|
||||||
const keys = Object.keys(props.options) as (keyof T)[]
|
const keys = Object.keys(props.options) as (keyof T)[]
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
selected: keys[0],
|
selected: keys[0],
|
||||||
|
expanded: false,
|
||||||
})
|
})
|
||||||
|
const diffKey = Keybind.parse("ctrl+f")[0]
|
||||||
|
|
||||||
useKeyboard((evt) => {
|
useKeyboard((evt) => {
|
||||||
if (evt.name === "left" || evt.name == "h") {
|
if (evt.name === "left" || evt.name == "h") {
|
||||||
@ -360,17 +376,36 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
props.onSelect(props.escapeKey)
|
props.onSelect(props.escapeKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.fullscreen && diffKey && Keybind.match(diffKey, keybind.parse(evt))) {
|
||||||
|
evt.preventDefault()
|
||||||
|
evt.stopPropagation()
|
||||||
|
setStore("expanded", (v) => !v)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
const hint = createMemo(() => (store.expanded ? "minimize" : "fullscreen"))
|
||||||
|
const renderer = useRenderer()
|
||||||
|
|
||||||
|
const content = () => (
|
||||||
<box
|
<box
|
||||||
backgroundColor={theme.backgroundPanel}
|
backgroundColor={theme.backgroundPanel}
|
||||||
border={["left"]}
|
border={["left"]}
|
||||||
borderColor={theme.warning}
|
borderColor={theme.warning}
|
||||||
customBorderChars={SplitBorder.customBorderChars}
|
customBorderChars={SplitBorder.customBorderChars}
|
||||||
|
{...(store.expanded
|
||||||
|
? { top: dimensions().height * -1 + 1, bottom: 1, left: 2, right: 2, position: "absolute" }
|
||||||
|
: {
|
||||||
|
top: 0,
|
||||||
|
maxHeight: 15,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
position: "relative",
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<box gap={1} paddingLeft={1} paddingRight={3} paddingTop={1} paddingBottom={1}>
|
<box gap={1} paddingLeft={1} paddingRight={3} paddingTop={1} paddingBottom={1} flexGrow={1}>
|
||||||
<box flexDirection="row" gap={1} paddingLeft={1}>
|
<box flexDirection="row" gap={1} paddingLeft={1} flexShrink={0}>
|
||||||
<text fg={theme.warning}>{"△"}</text>
|
<text fg={theme.warning}>{"△"}</text>
|
||||||
<text fg={theme.text}>{props.title}</text>
|
<text fg={theme.text}>{props.title}</text>
|
||||||
</box>
|
</box>
|
||||||
@ -403,6 +438,11 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||||||
</For>
|
</For>
|
||||||
</box>
|
</box>
|
||||||
<box flexDirection="row" gap={2}>
|
<box flexDirection="row" gap={2}>
|
||||||
|
<Show when={props.fullscreen}>
|
||||||
|
<text fg={theme.text}>
|
||||||
|
{"ctrl+f"} <span style={{ fg: theme.textMuted }}>{hint()}</span>
|
||||||
|
</text>
|
||||||
|
</Show>
|
||||||
<text fg={theme.text}>
|
<text fg={theme.text}>
|
||||||
{"⇆"} <span style={{ fg: theme.textMuted }}>select</span>
|
{"⇆"} <span style={{ fg: theme.textMuted }}>select</span>
|
||||||
</text>
|
</text>
|
||||||
@ -413,4 +453,10 @@ function Prompt<const T extends Record<string, string>>(props: {
|
|||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={!store.expanded} fallback={<Portal>{content()}</Portal>}>
|
||||||
|
{content()}
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user