diff --git a/packages/app/src/components/dialog-manage-models.tsx b/packages/app/src/components/dialog-manage-models.tsx deleted file mode 100644 index 0e64387ce..000000000 --- a/packages/app/src/components/dialog-manage-models.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { Dialog } from "@opencode-ai/ui/dialog" -import { List } from "@opencode-ai/ui/list" -import { Switch } from "@opencode-ai/ui/switch" -import { Tooltip } from "@opencode-ai/ui/tooltip" -import { Button } from "@opencode-ai/ui/button" -import type { Component } from "solid-js" -import { useLocal } from "@/context/local" -import { popularProviders } from "@/hooks/use-providers" -import { useLanguage } from "@/context/language" -import { useDialog } from "@opencode-ai/ui/context/dialog" -import { DialogSelectProvider } from "./dialog-select-provider" -import { compareModelsForDisplay } from "@/utils/model-order" - -export const DialogManageModels: Component = () => { - const local = useLocal() - const language = useLanguage() - const dialog = useDialog() - - const handleConnectProvider = () => { - dialog.show(() => ) - } - const providerRank = (id: string) => popularProviders.indexOf(id) - const providerList = (providerID: string) => local.model.list().filter((x) => x.provider.id === providerID) - const providerVisible = (providerID: string) => - providerList(providerID).every((x) => local.model.visible({ modelID: x.id, providerID: x.provider.id })) - const setProviderVisibility = (providerID: string, checked: boolean) => { - providerList(providerID).forEach((x) => { - local.model.setVisibility({ modelID: x.id, providerID: x.provider.id }, checked) - }) - } - - return ( - - {language.t("command.provider.connect")} - - } - > - `${x?.provider?.id}:${x?.id}`} - items={local.model.list()} - filterKeys={["provider.name", "name", "id"]} - sortBy={compareModelsForDisplay} - groupBy={(x) => x.provider.id} - groupHeader={(group) => { - const provider = group.items[0].provider - return ( - <> - {provider.name} - - setProviderVisibility(provider.id, checked)} - hideLabel - > - {provider.name} - - - - ) - }} - sortGroupsBy={(a, b) => { - const aRank = providerRank(a.items[0].provider.id) - const bRank = providerRank(b.items[0].provider.id) - const aPopular = aRank >= 0 - const bPopular = bRank >= 0 - if (aPopular && !bPopular) return -1 - if (!aPopular && bPopular) return 1 - return aRank - bRank - }} - onSelect={(x) => { - if (!x) return - const key = { modelID: x.id, providerID: x.provider.id } - local.model.setVisibility(key, !local.model.visible(key)) - }} - > - {(i) => ( -
- {i.name} -
e.stopPropagation()}> - { - local.model.setVisibility({ modelID: i.id, providerID: i.provider.id }, checked) - }} - /> -
-
- )} -
-
- ) -} diff --git a/packages/app/src/components/titlebar-history.ts b/packages/app/src/components/titlebar-history.ts index 44dbbfa3a..698c5dee9 100644 --- a/packages/app/src/components/titlebar-history.ts +++ b/packages/app/src/components/titlebar-history.ts @@ -1,4 +1,4 @@ -export const MAX_TITLEBAR_HISTORY = 100 +const MAX_TITLEBAR_HISTORY = 100 export type TitlebarAction = "back" | "forward" | undefined @@ -25,13 +25,13 @@ export function applyPath(state: TitlebarHistory, current: string, max = MAX_TIT return pushPath(state, current, max) } -export function pushPath(state: TitlebarHistory, path: string, max = MAX_TITLEBAR_HISTORY): TitlebarHistory { +function pushPath(state: TitlebarHistory, path: string, max = MAX_TITLEBAR_HISTORY): TitlebarHistory { const stack = state.stack.slice(0, state.index + 1).concat(path) const next = trimHistory(stack, stack.length - 1, max) return { ...state, ...next, action: undefined } } -export function trimHistory(stack: string[], index: number, max = MAX_TITLEBAR_HISTORY) { +function trimHistory(stack: string[], index: number, max = MAX_TITLEBAR_HISTORY) { if (stack.length <= max) return { stack, index } const cut = stack.length - max return { diff --git a/packages/app/src/context/file/path.ts b/packages/app/src/context/file/path.ts index 53f072b6c..25fc88a22 100644 --- a/packages/app/src/context/file/path.ts +++ b/packages/app/src/context/file/path.ts @@ -1,4 +1,4 @@ -export function stripFileProtocol(input: string) { +function stripFileProtocol(input: string) { if (!input.startsWith("file://")) return input return input.slice("file://".length) } @@ -72,7 +72,7 @@ export function unquoteGitPath(input: string) { return new TextDecoder().decode(new Uint8Array(bytes)) } -export function decodeFilePath(input: string) { +function decodeFilePath(input: string) { try { return decodeURIComponent(input) } catch { diff --git a/packages/app/src/context/highlights.tsx b/packages/app/src/context/highlights.tsx index 49fc6961b..a8dcaf727 100644 --- a/packages/app/src/context/highlights.tsx +++ b/packages/app/src/context/highlights.tsx @@ -313,7 +313,7 @@ function buildToastDescription(summaries: ReleaseSummary[], currentTag: string) .join("\n\n") } -export const { use: useHighlights, provider: HighlightsProvider } = createSimpleContext({ +export const { provider: HighlightsProvider } = createSimpleContext({ name: "Highlights", gate: false, init: () => { diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index d9ffebebb..af190a3c5 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -40,7 +40,7 @@ export function clampRightPanelWidth(raw: number | undefined): number { } export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number] -export function getAvatarColors(key?: string) { +function getAvatarColors(key?: string) { if (key && AVATAR_COLOR_KEYS.includes(key as AvatarColorKey)) { return { background: `var(--avatar-background-${key})`, diff --git a/packages/app/src/custom-elements.d.ts b/packages/app/src/custom-elements.d.ts deleted file mode 120000 index e4ea0d6ce..000000000 --- a/packages/app/src/custom-elements.d.ts +++ /dev/null @@ -1 +0,0 @@ -../../ui/src/custom-elements.d.ts \ No newline at end of file diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index f3d0f4f3e..be77cf8fe 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -1,23 +1,5 @@ -import { useParams } from "@solidjs/router" -import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "solid-js" -import { createStore } from "solid-js/store" -import { createSortable } from "@thisbeyond/solid-dnd" -import { createMediaQuery } from "@solid-primitives/media" -import { base64Encode } from "@opencode-ai/util/encode" -import { getFilename } from "@opencode-ai/util/path" -import { Button } from "@opencode-ai/ui/button" -import { Collapsible } from "@opencode-ai/ui/collapsible" -import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" -import { Icon } from "@opencode-ai/ui/icon" -import { IconButton } from "@opencode-ai/ui/icon-button" -import { Spinner } from "@opencode-ai/ui/spinner" -import { Tooltip } from "@opencode-ai/ui/tooltip" +import { type Accessor, type JSX } from "solid-js" import { type Session } from "@opencode-ai/sdk/v2/client" -import { type LocalProject } from "@/context/layout" -import { useGlobalSync } from "@/context/global-sync" -import { useLanguage } from "@/context/language" -import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items" -import { sortedRootSessions, workspaceKey } from "./helpers" type InlineEditorComponent = (props: { id: string @@ -50,413 +32,3 @@ export type WorkspaceSidebarContext = { showDeleteWorkspaceDialog: (root: string, directory: string) => void setScrollContainerRef: (el: HTMLDivElement | undefined) => void } - -export const WorkspaceDragOverlay = (props: { - sidebarProject: Accessor - activeWorkspace: Accessor - workspaceLabel: (directory: string, branch?: string, projectId?: string) => string -}): JSX.Element => { - const globalSync = useGlobalSync() - const language = useLanguage() - const label = createMemo(() => { - const project = props.sidebarProject() - if (!project) return - const directory = props.activeWorkspace() - if (!directory) return - - const [workspaceStore] = globalSync.child(directory, { bootstrap: false }) - const kind = - directory === project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox") - const name = props.workspaceLabel(directory, workspaceStore.vcs?.branch, project.id) - return `${kind} : ${name}` - }) - - return ( - - {(value) =>
{value()}
} -
- ) -} - -const WorkspaceHeader = (props: { - local: Accessor - busy: Accessor - open: Accessor - directory: string - language: ReturnType - branch: Accessor - workspaceValue: Accessor - workspaceEditActive: Accessor - InlineEditor: WorkspaceSidebarContext["InlineEditor"] - renameWorkspace: WorkspaceSidebarContext["renameWorkspace"] - setEditor: WorkspaceSidebarContext["setEditor"] - projectId?: string -}): JSX.Element => ( -
-
- }> - - -
- - {props.local() ? props.language.t("workspace.type.local") : props.language.t("workspace.type.sandbox")} : - - - {props.branch() ?? getFilename(props.directory)} - - } - > - { - const trimmed = next.trim() - if (!trimmed) return - props.renameWorkspace(props.directory, trimmed, props.projectId, props.branch()) - props.setEditor("value", props.workspaceValue()) - }} - class="text-13-medium text-fg-base min-w-0 truncate" - displayClass="text-13-medium text-fg-base min-w-0 truncate" - editing={props.workspaceEditActive()} - stopPropagation={false} - openOnDblClick={false} - /> - -
- -
-
-) - -const WorkspaceActions = (props: { - directory: string - local: Accessor - busy: Accessor - menuOpen: Accessor - pendingRename: Accessor - setMenuOpen: (open: boolean) => void - setPendingRename: (value: boolean) => void - touch: Accessor - language: ReturnType - workspaceValue: Accessor - openEditor: WorkspaceSidebarContext["openEditor"] - showResetWorkspaceDialog: WorkspaceSidebarContext["showResetWorkspaceDialog"] - showDeleteWorkspaceDialog: WorkspaceSidebarContext["showDeleteWorkspaceDialog"] - root: string - openNewSession: () => void -}): JSX.Element => ( -
- props.setMenuOpen(open)} - > - - - - - { - if (!props.pendingRename()) return - event.preventDefault() - props.setPendingRename(false) - props.openEditor(`workspace:${props.directory}`, props.workspaceValue()) - }} - > - { - props.setPendingRename(true) - props.setMenuOpen(false) - }} - > - {props.language.t("common.rename")} - - props.showResetWorkspaceDialog(props.root, props.directory)} - > - {props.language.t("common.reset")} - - props.showDeleteWorkspaceDialog(props.root, props.directory)} - > - {props.language.t("common.delete")} - - - - - - - { - event.preventDefault() - event.stopPropagation() - props.openNewSession() - }} - /> - - -
-) - -const WorkspaceSessionList = (props: { - slug: Accessor - directory: string - ctx: WorkspaceSidebarContext - showNew: Accessor - loading: Accessor - sessions: Accessor - hasMore: Accessor - loadMore: () => Promise - language: ReturnType -}): JSX.Element => ( - -) - -export const SortableWorkspace = (props: { - ctx: WorkspaceSidebarContext - directory: string - project: LocalProject -}): JSX.Element => { - const params = useParams() - const globalSync = useGlobalSync() - const language = useLanguage() - const sortable = createSortable(props.directory) - const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory, { bootstrap: false }) - const [menu, setMenu] = createStore({ - open: false, - pendingRename: false, - }) - const slug = createMemo(() => base64Encode(props.directory)) - const sessions = createMemo(() => sortedRootSessions(workspaceStore)) - const local = createMemo(() => props.directory === props.project.worktree) - const active = createMemo(() => workspaceKey(props.ctx.currentDir()) === workspaceKey(props.directory)) - const workspaceValue = createMemo(() => { - const branch = workspaceStore.vcs?.branch - const name = branch ?? getFilename(props.directory) - return props.ctx.workspaceName(props.directory, props.project.id, branch) ?? name - }) - const open = createMemo(() => props.ctx.workspaceExpanded(props.directory, local())) - const boot = createMemo(() => open() || active()) - const booted = createMemo((prev) => prev || workspaceStore.status === "complete", false) - const count = createMemo(() => sessions()?.length ?? 0) - const hasMore = createMemo(() => workspaceStore.sessionTotal > count()) - const busy = createMemo(() => props.ctx.isBusy(props.directory)) - const wasBusy = createMemo((prev) => prev || busy(), false) - const loading = createMemo(() => open() && !booted() && count() === 0 && !wasBusy()) - const touch = createMediaQuery("(hover: none)") - const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id))) - const loadMore = async () => { - setWorkspaceStore("limit", (limit) => (limit ?? 0) + 5) - await globalSync.project.loadSessions(props.directory) - } - - const workspaceEditActive = createMemo(() => props.ctx.editorOpen(`workspace:${props.directory}`)) - const header = () => ( - workspaceStore.vcs?.branch} - workspaceValue={workspaceValue} - workspaceEditActive={workspaceEditActive} - InlineEditor={props.ctx.InlineEditor} - renameWorkspace={props.ctx.renameWorkspace} - setEditor={props.ctx.setEditor} - projectId={props.project.id} - /> - ) - - const openWrapper = (value: boolean) => { - props.ctx.setWorkspaceExpanded(props.directory, value) - if (value) return - if (props.ctx.editorOpen(`workspace:${props.directory}`)) props.ctx.closeEditor() - } - - createEffect(() => { - if (!boot()) return - globalSync.child(props.directory, { bootstrap: true }) - }) - - return ( -
- -
-
-
- - {header()} - - } - > -
- {header()} -
-
- menu.open} - pendingRename={() => menu.pendingRename} - setMenuOpen={(open) => setMenu("open", open)} - setPendingRename={(value) => setMenu("pendingRename", value)} - touch={touch} - language={language} - workspaceValue={workspaceValue} - openEditor={props.ctx.openEditor} - showResetWorkspaceDialog={props.ctx.showResetWorkspaceDialog} - showDeleteWorkspaceDialog={props.ctx.showDeleteWorkspaceDialog} - root={props.project.worktree} - openNewSession={() => props.ctx.openNewSession(props.directory)} - /> -
-
-
- - - - -
-
- ) -} - -export const LocalWorkspace = (props: { - ctx: WorkspaceSidebarContext - project: LocalProject -}): JSX.Element => { - const globalSync = useGlobalSync() - const language = useLanguage() - const workspace = createMemo(() => { - const [store, setStore] = globalSync.child(props.project.worktree) - return { store, setStore } - }) - const slug = createMemo(() => base64Encode(props.project.worktree)) - const sessions = createMemo(() => sortedRootSessions(workspace().store)) - const booted = createMemo((prev) => prev || workspace().store.status === "complete", false) - const count = createMemo(() => sessions()?.length ?? 0) - const loading = createMemo(() => !booted() && count() === 0) - const hasMore = createMemo(() => workspace().store.sessionTotal > count()) - const loadMore = async () => { - workspace().setStore("limit", (limit) => (limit ?? 0) + 5) - await globalSync.project.loadSessions(props.project.worktree) - } - - return ( -
props.ctx.setScrollContainerRef(el)} - class="size-full flex flex-col py-2 overflow-y-auto no-scrollbar [overflow-anchor:none]" - > - false} - loading={loading} - sessions={sessions} - hasMore={hasMore} - loadMore={loadMore} - language={language} - /> -
- ) -} diff --git a/packages/app/src/pages/session/helpers.ts b/packages/app/src/pages/session/helpers.ts index 9710adca8..b1017093b 100644 --- a/packages/app/src/pages/session/helpers.ts +++ b/packages/app/src/pages/session/helpers.ts @@ -18,8 +18,6 @@ type TabsInput = { hasReview?: Accessor } -export const getSessionKey = (dir: string | undefined, id: string | undefined) => `${dir ?? ""}${id ? `/${id}` : ""}` - export const createSessionTabs = (input: TabsInput) => { const review = input.review ?? (() => false) const hasReview = input.hasReview ?? (() => false) diff --git a/packages/app/src/utils/agent.ts b/packages/app/src/utils/agent.ts index a7a87cae0..2c75e5ed9 100644 --- a/packages/app/src/utils/agent.ts +++ b/packages/app/src/utils/agent.ts @@ -26,7 +26,7 @@ function tone(name: string) { return palette[hash % palette.length] } -export function agentColor(name: string, custom?: string) { +function agentColor(name: string, custom?: string) { if (custom) return custom return defaults[name] ?? defaults[name.toLowerCase()] ?? tone(name.toLowerCase()) } diff --git a/packages/app/src/utils/solid-dnd.tsx b/packages/app/src/utils/solid-dnd.tsx index 8e30a033a..e37182783 100644 --- a/packages/app/src/utils/solid-dnd.tsx +++ b/packages/app/src/utils/solid-dnd.tsx @@ -44,6 +44,4 @@ const createAxisConstraint = (axis: "x" | "y", transformerId: string) => (): JSX return null } -export const ConstrainDragXAxis = createAxisConstraint("x", "constrain-x-axis") - export const ConstrainDragYAxis = createAxisConstraint("y", "constrain-y-axis") diff --git a/packages/app/src/utils/sound.ts b/packages/app/src/utils/sound.ts index 78e5a0c56..d19f22d81 100644 --- a/packages/app/src/utils/sound.ts +++ b/packages/app/src/utils/sound.ts @@ -75,7 +75,7 @@ function getLoads() { const cache = new Map>() -export function soundSrc(id: string | undefined) { +function soundSrc(id: string | undefined) { const loads = getLoads() if (!id || !(id in loads)) return Promise.resolve(undefined) const key = id as SoundID @@ -86,7 +86,7 @@ export function soundSrc(id: string | undefined) { return next } -export function playSound(src: string | undefined) { +function playSound(src: string | undefined) { if (typeof Audio === "undefined") return if (!src) return const audio = new Audio(src) diff --git a/packages/ui/src/components/apply-patch-file.ts b/packages/ui/src/components/apply-patch-file.ts index 8e0c54082..e00f55d8f 100644 --- a/packages/ui/src/components/apply-patch-file.ts +++ b/packages/ui/src/components/apply-patch-file.ts @@ -35,7 +35,7 @@ function status(type: Kind): "added" | "deleted" | "modified" { return "modified" } -export function patchFile(raw: unknown): ApplyPatchFile | undefined { +function patchFile(raw: unknown): ApplyPatchFile | undefined { if (!raw || typeof raw !== "object") return const value = raw as Raw diff --git a/packages/ui/src/components/message-part/session-link.ts b/packages/ui/src/components/message-part/session-link.ts index c784a4343..ff5b7ca11 100644 --- a/packages/ui/src/components/message-part/session-link.ts +++ b/packages/ui/src/components/message-part/session-link.ts @@ -13,6 +13,10 @@ export function urls(text: string | undefined) { }) } +function currentSession(path: string) { + return path.match(/\/session\/([^/?#]+)/)?.[1] +} + export function sessionLink(id: string | undefined, path: string, href?: (id: string) => string | undefined) { if (!id) return @@ -24,10 +28,6 @@ export function sessionLink(id: string | undefined, path: string, href?: (id: st return `${path.slice(0, idx)}/session/${id}` } -export function currentSession(path: string) { - return path.match(/\/session\/([^/?#]+)/)?.[1] -} - export function taskSession( input: Record, path: string, diff --git a/packages/ui/src/components/tool-info.ts b/packages/ui/src/components/tool-info.ts index 5f51d615d..75bab843f 100644 --- a/packages/ui/src/components/tool-info.ts +++ b/packages/ui/src/components/tool-info.ts @@ -13,12 +13,12 @@ function pickString(value: unknown): string | undefined { return typeof value === "string" && value ? value : undefined } -export function enterWorktreeOwnerProject(metadata: Record = {}): string | undefined { +function enterWorktreeOwnerProject(metadata: Record = {}): string | undefined { const owner = pickString(metadata.ownerDirectory) return owner ? getFilename(owner) : undefined } -export function enterWorktreeTarget( +function enterWorktreeTarget( input: Record = {}, metadata: Record = {}, ): string | undefined { @@ -42,12 +42,12 @@ export function enterWorktreeSubtitle( return target || project } -export function exitWorktreeProjectName(metadata: Record = {}): string | undefined { +function exitWorktreeProjectName(metadata: Record = {}): string | undefined { const dest = pickString(metadata.activeDirectory) return dest ? getFilename(dest) : undefined } -export function exitWorktreePreviousLabel(metadata: Record = {}): string | undefined { +function exitWorktreePreviousLabel(metadata: Record = {}): string | undefined { return pickString(metadata.previousBranch) || pickString(metadata.previousSlug) } @@ -59,7 +59,7 @@ export function exitWorktreeSubtitle(metadata: Record, i18n: UiI18n return previous } -export function agentTitle(i18n: UiI18n, type?: string) { +function agentTitle(i18n: UiI18n, type?: string) { if (!type) return i18n.t("ui.tool.agent.default") return i18n.t("ui.tool.agent", { type }) } diff --git a/packages/ui/src/storybook/fixtures.ts b/packages/ui/src/storybook/fixtures.ts index 59d412970..931144591 100644 --- a/packages/ui/src/storybook/fixtures.ts +++ b/packages/ui/src/storybook/fixtures.ts @@ -1,34 +1,3 @@ -export const diff = { - before: { - name: "src/greet.ts", - contents: `export function greet(name: string) { - return \`Hello, \${name}!\` -} -`, - }, - after: { - name: "src/greet.ts", - contents: `export function greet(name: string, excited = false) { - const message = \`Hello, \${name}!\` - return excited ? \`\${message}!!\` : message -} -`, - }, -} - -export const code = { - name: "src/calc.ts", - contents: `export function sum(values: number[]) { - return values.reduce((total, value) => total + value, 0) -} - -export function average(values: number[]) { - if (values.length === 0) return 0 - return sum(values) / values.length -} -`, -} - export const markdown = [ "# Markdown", "",