Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 26 additions & 48 deletions packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useSpring } from "@opencode-ai/ui/motion-spring"
import { isWorkInFlightStatus } from "@opencode-ai/ui/util/session-status"
import { useNavigate, useParams } from "@solidjs/router"
import { createEffect, on, Component, For, Show, createMemo, createSignal } from "solid-js"
import { createStore } from "solid-js/store"
import { useLocal } from "@/context/local"
import { useFile } from "@/context/file"
import { DEFAULT_PROMPT, Prompt, usePrompt, ImageAttachmentPart } from "@/context/prompt"
import { DEFAULT_PROMPT, Prompt, usePrompt } from "@/context/prompt"
import { useSDK } from "@/context/sdk"
import { useSync } from "@/context/sync"
import { useComments } from "@/context/comments"
Expand Down Expand Up @@ -38,14 +37,14 @@ import { createPromptAttachments } from "./prompt-input/attachments"
import { pickAttachments } from "./prompt-input/pick-attachments"
import { ACCEPTED_FILE_TYPES } from "./prompt-input/files"
import { promptLength } from "./prompt-input/history"
import { createPromptDerivedState } from "./prompt-input/derived-state"
import type { PromptStore } from "./prompt-input/store-types"
import type { FollowupDraft } from "./prompt-input/followup-draft"
import { createPromptSubmit } from "./prompt-input/submit"
import { PromptPopover } from "./prompt-input/slash-popover"
import { PromptContextItems } from "./prompt-input/context-items"
import { PromptImageAttachments } from "./prompt-input/image-attachments"
import { PromptDragOverlay } from "./prompt-input/drag-overlay"
import { promptPlaceholder } from "./prompt-input/placeholder"
import { promptSendDisabled } from "./prompt-input/readiness"
import { ImagePreview } from "@opencode-ai/ui/image-preview"

Expand Down Expand Up @@ -110,20 +109,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {

const { recent, openComment } = createCommentRouting({ activeSessionID })

const info = createMemo(() => (activeSessionID() ? sync.session.get(activeSessionID()!) : undefined))
const status = createMemo(
() =>
sync.data.session_status[activeSessionID() ?? ""] ?? {
type: "idle",
},
)
const working = createMemo(() => isWorkInFlightStatus(status()))
const imageAttachments = createMemo(() =>
prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"),
)
const actionReady = createMemo(() => props.actionReady?.() ?? true)
const abortReady = createMemo(() => props.abortReady?.() ?? actionReady())

const [store, setStore] = createStore<PromptStore>({
popover: null,
historyIndex: -1,
Expand All @@ -133,6 +118,30 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
applyingHistory: false,
})

const {
info,
working,
Comment thread
Astro-Han marked this conversation as resolved.
imageAttachments,
actionReady,
abortReady,
commentCount,
blank,
stopping,
contextItems,
placeholder,
accepting,
} = createPromptDerivedState({
store,
prompt,
sync,
sdk,
permission,
language,
activeSessionID,
actionReadyProp: () => props.actionReady?.(),
abortReadyProp: () => props.abortReady?.(),
})

const buttonsSpring = useSpring(() => (store.mode === "normal" ? 1 : 0), { visualDuration: 0.2, bounce: 0 })
const motion = (value: number) => ({
opacity: value,
Expand All @@ -142,18 +151,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
const buttons = createMemo(() => motion(buttonsSpring()))

const commentCount = createMemo(() => {
if (store.mode === "shell") return 0
return prompt.context.items().filter((item) => !!item.comment?.trim()).length
})
const blank = createMemo(() => {
const text = prompt
.current()
.map((part) => ("content" in part ? part.content : ""))
.join("")
return text.trim().length === 0 && imageAttachments().length === 0 && commentCount() === 0
})
const stopping = createMemo(() => working() && blank())
const tip = () => {
if (stopping() && abortReady()) {
return (
Expand All @@ -172,12 +169,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
)
}

const contextItems = createMemo(() => {
const items = prompt.context.items()
if (store.mode !== "shell") return items
return items.filter((item) => !item.comment?.trim())
})

const { addToHistory, navigateHistory } = createHistoryNavigation({
store,
setStore,
Expand All @@ -195,14 +186,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
),
)

const placeholder = createMemo(() =>
promptPlaceholder({
mode: store.mode,
commentCount: commentCount(),
t: (key) => language.t(key as Parameters<typeof language.t>[0]),
}),
)

const pick = () => {
if (!actionReady()) return
const openFilePickerDialog = platform.openFilePickerDialog
Expand Down Expand Up @@ -387,11 +370,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
externalReady: actionReady,
})

const accepting = createMemo(() => {
const id = activeSessionID()
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
return permission.isAutoAccepting(id, sdk.directory)
})
const navigate = useNavigate()
const routeParams = useParams()

Expand Down
86 changes: 86 additions & 0 deletions packages/app/src/components/prompt-input/derived-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { createMemo, type Accessor } from "solid-js"
import { isWorkInFlightStatus } from "@opencode-ai/ui/util/session-status"
import { type ImageAttachmentPart, type usePrompt } from "@/context/prompt"
import type { useSync } from "@/context/sync"
import type { useSDK } from "@/context/sdk"
import type { usePermission } from "@/context/permission"
import type { useLanguage } from "@/context/language"
import { promptPlaceholder } from "./placeholder"
import type { PromptStore } from "./store-types"

export interface PromptDerivedStateDeps {
store: PromptStore
prompt: ReturnType<typeof usePrompt>
sync: ReturnType<typeof useSync>
sdk: ReturnType<typeof useSDK>
permission: ReturnType<typeof usePermission>
language: ReturnType<typeof useLanguage>
activeSessionID: Accessor<string | undefined>
actionReadyProp: () => boolean | undefined
abortReadyProp: () => boolean | undefined
}

export function createPromptDerivedState(deps: PromptDerivedStateDeps) {
const { store, prompt, sync, sdk, permission, language, activeSessionID, actionReadyProp, abortReadyProp } = deps

const info = createMemo(() => (activeSessionID() ? sync.session.get(activeSessionID()!) : undefined))
const status = createMemo(
() =>
sync.data.session_status[activeSessionID() ?? ""] ?? {
type: "idle",
},
)
const working = createMemo(() => isWorkInFlightStatus(status()))
const imageAttachments = createMemo(() =>
prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"),
)
const actionReady = createMemo(() => actionReadyProp() ?? true)
const abortReady = createMemo(() => abortReadyProp() ?? actionReady())

const commentCount = createMemo(() => {
if (store.mode === "shell") return 0
return prompt.context.items().filter((item) => !!item.comment?.trim()).length
})
const blank = createMemo(() => {
const text = prompt
.current()
.map((part) => ("content" in part ? part.content : ""))
.join("")
return text.trim().length === 0 && imageAttachments().length === 0 && commentCount() === 0
})
const stopping = createMemo(() => working() && blank())

const contextItems = createMemo(() => {
const items = prompt.context.items()
if (store.mode !== "shell") return items
return items.filter((item) => !item.comment?.trim())
})

const placeholder = createMemo(() =>
promptPlaceholder({
mode: store.mode,
commentCount: commentCount(),
t: (key) => language.t(key as Parameters<typeof language.t>[0]),
}),
)

const accepting = createMemo(() => {
const id = activeSessionID()
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
return permission.isAutoAccepting(id, sdk.directory)
})

return {
info,
working,
Comment thread
Astro-Han marked this conversation as resolved.
imageAttachments,
actionReady,
abortReady,
commentCount,
blank,
stopping,
contextItems,
placeholder,
accepting,
}
}
Loading