-
- {/*
*/}
-
- {i.name}
-
-
- {DateTime.fromFormat("unknown", "yyyy-MM-dd").toFormat("LLL yyyy")}
-
-
-
-
+
)}
diff --git a/packages/ui/index.html b/packages/ui/index.html
deleted file mode 100644
index 7697a5f96..000000000
--- a/packages/ui/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
OpenCode UI
-
-
-
-
-
-
-
diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx
index 8c83b41ce..97f2e8eab 100644
--- a/packages/ui/src/components/icon.tsx
+++ b/packages/ui/src/components/icon.tsx
@@ -1,171 +1,44 @@
import { splitProps, type ComponentProps } from "solid-js"
-// prettier-ignore
const icons = {
- close: '
',
- menu: '
',
- "chevron-right": '
',
- "chevron-left": '
',
- "chevron-down": '
',
- "chevron-up": '
',
- "chevron-down-square": '
',
- "chevron-up-square": '
',
- "chevron-right-square": '
',
- "chevron-left-square": '
',
- settings: '
',
- globe: '
',
- github: '
',
- hammer: '
',
- "avatar-square": '
',
- slash: '
',
- robot: '
',
- cloud: '
',
- "file-text": '
',
- file: '
',
- "file-checkmark": '
',
- "file-code": '
',
- "file-important": '
',
- "file-minus": '
',
- "file-plus": '
',
- files: '
',
- "file-zip": '
',
- jpg: '
',
- pdf: '
',
- png: '
',
- gif: '
',
- archive: '
',
- sun: '
',
- moon: '
',
- monitor: '
',
- command: '
',
- link: '
',
- share: '
',
- branch: '
',
- logout: '
',
- login: '
',
- keys: '
',
- key: '
',
- info: '
',
- warning: '
',
- checkmark: '
',
- "checkmark-square": '
',
- plus: '
',
- minus: '
',
- undo: '
',
- merge: '
',
- redo: '
',
- refresh: '
',
- rotate: '
',
- "arrow-left": '
',
- "arrow-down": '
',
- "arrow-right": '
',
- "arrow-up": '
',
- enter: '
',
- trash: '
',
- package: '
',
- box: '
',
- lock: '
',
- unlocked: '
',
- activity: '
',
- asterisk: '
',
- bell: '
',
- "bell-off": '
',
- bolt: '
',
- bookmark: '
',
- brain: '
',
- browser: '
',
- "browser-cursor": '
',
- bug: '
',
- "carat-down": '
',
- "carat-left": '
',
- "carat-right": '
',
- "carat-up": '
',
- cards: '
',
- chart: '
',
- "check-circle": '
',
- checklist: '
',
- "checklist-cards": '
',
- lab: '
',
- circle: '
',
- "circle-dotted": '
',
- clipboard: '
',
- clock: '
',
- "close-circle": '
',
- terminal: '
',
- code: '
',
- components: '
',
- copy: '
',
- cpu: '
',
- dashboard: '
',
- transfer: '
',
- devices: '
',
- diamond: '
',
- dice: '
',
- discord: '
',
- dots: '
',
- expand: '
',
- droplet: '
',
- "chevron-double-down": '
',
- "chevron-double-left": '
',
- "chevron-double-right": '
',
- "chevron-double-up": '
',
- "speech-bubble": '
',
- message: '
',
- annotation: '
',
- square: '
',
- "pull-request": '
',
- pencil: '
',
- sparkles: '
',
- photo: '
',
- columns: '
',
- "open-pane": '
',
- "close-pane": '
',
- "file-search": '
',
- "folder-search": '
',
- search: '
',
- "web-search": '
',
- loading: '
',
- mic: '
',
-} as const
-
-const newIcons = {
- "circle-x": `
`,
- "magnifying-glass": `
`,
- "plus-small": `
`,
- "chevron-down": `
`,
- "chevron-right": `
`,
+ "align-right": `
`,
"arrow-up": `
`,
+ "bubble-5": `
`,
+ "bullet-list": `
`,
"check-small": `
`,
+ "chevron-down": `
`,
+ "chevron-right": `
`,
+ "chevron-grabber-vertical": `
`,
+ "circle-x": `
`,
+ close: `
`,
+ checklist: `
`,
+ console: `
`,
+ expand: `
`,
+ collapse: `
`,
+ "code-lines": `
`,
+ "circle-ban-sign": `
`,
"edit-small-2": `
`,
+ enter: `
`,
folder: `
`,
+ "magnifying-glass": `
`,
+ "plus-small": `
`,
"pencil-line": `
`,
- "chevron-grabber-vertical": `
`,
mcp: `
`,
glasses: `
`,
- "bullet-list": `
`,
"magnifying-glass-menu": `
`,
"window-cursor": `
`,
task: `
`,
- checklist: `
`,
- console: `
`,
- "code-lines": `
`,
- "square-arrow-top-right": `
`,
- "circle-ban-sign": `
`,
stop: `
`,
- enter: `
`,
"layout-left": `
`,
"layout-left-partial": `
`,
"layout-left-full": `
`,
"layout-right": `
`,
"layout-right-partial": `
`,
"layout-right-full": `
`,
+ "square-arrow-top-right": `
`,
"speech-bubble": `
`,
- "align-right": `
`,
- expand: `
`,
- collapse: `
`,
"folder-add-left": `
`,
"settings-gear": `
`,
- "bubble-5": `
`,
github: `
`,
discord: `
`,
"layout-bottom": `
`,
@@ -175,32 +48,12 @@ const newIcons = {
}
export interface IconProps extends ComponentProps<"svg"> {
- name: keyof typeof icons | keyof typeof newIcons
+ name: keyof typeof icons
size?: "small" | "normal" | "large"
}
export function Icon(props: IconProps) {
const [local, others] = splitProps(props, ["name", "size", "class", "classList"])
-
- if (local.name in newIcons) {
- return (
-
-
-
- )
- }
-
return (
-
+
0}
fallback={
--
cgit v1.2.3
From 91d743ef9a5c346fe17bb857db68dca92a6e9ba1 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Wed, 10 Dec 2025 12:48:08 -0600
Subject: wip(desktop): progress
---
packages/desktop/src/components/prompt-input.tsx | 5 ++
packages/desktop/src/context/global-sync.tsx | 73 +++++++-----------------
packages/desktop/src/context/layout.tsx | 67 +++++++++++++++++-----
packages/desktop/src/pages/layout.tsx | 38 ++++++++++++
packages/tauri/src-tauri/Cargo.lock | 34 +++++------
packages/tauri/src-tauri/Cargo.toml | 2 +-
packages/ui/src/components/avatar.tsx | 7 ++-
packages/ui/src/components/button.css | 18 ++++--
packages/ui/src/components/select-dialog.css | 1 -
packages/ui/src/components/select-dialog.tsx | 6 +-
10 files changed, 156 insertions(+), 95 deletions(-)
(limited to 'packages/ui/src/components')
diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx
index 8579647da..97d27ee1e 100644
--- a/packages/desktop/src/components/prompt-input.tsx
+++ b/packages/desktop/src/components/prompt-input.tsx
@@ -483,6 +483,11 @@ export const PromptInput: Component = (props) => {
}
+ actions={
+
+ }
>
{(i) => (
diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/desktop/src/context/global-sync.tsx
index 58fc8c9cd..3e2b6bf7d 100644
--- a/packages/desktop/src/context/global-sync.tsx
+++ b/packages/desktop/src/context/global-sync.tsx
@@ -18,41 +18,9 @@ import { Binary } from "@opencode-ai/util/binary"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSDK } from "./global-sdk"
-const PASTEL_COLORS = [
- "#FCEAFD", // pastel pink
- "#FFDFBA", // pastel peach
- "#FFFFBA", // pastel yellow
- "#BAFFC9", // pastel green
- "#EAF6FD", // pastel blue
- "#EFEAFD", // pastel lavender
- "#FEC8D8", // pastel rose
- "#D4F0F0", // pastel cyan
- "#FDF0EA", // pastel coral
- "#C1E1C1", // pastel mint
-]
-
-function pickAvailableColor(usedColors: Set
) {
- const available = PASTEL_COLORS.filter((c) => !usedColors.has(c))
- if (available.length === 0) return PASTEL_COLORS[Math.floor(Math.random() * PASTEL_COLORS.length)]
- return available[Math.floor(Math.random() * available.length)]
-}
-
-async function ensureProjectColor(
- project: Project,
- sdk: ReturnType,
- usedColors: Set,
-): Promise {
- if (project.icon?.color) return project
- const color = pickAvailableColor(usedColors)
- usedColors.add(color)
- const updated = { ...project, icon: { ...project.icon, color } }
- sdk.client.project.update({ projectID: project.id, icon: { color } })
- return updated
-}
-
type State = {
ready: boolean
- provider: Provider[]
+ // provider: Provider[]
agent: Agent[]
project: string
config: Config
@@ -84,10 +52,12 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
const [globalStore, setGlobalStore] = createStore<{
ready: boolean
projects: Project[]
+ providers: Provider[]
children: Record
}>({
ready: false,
projects: [],
+ providers: [],
children: {},
})
@@ -100,7 +70,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
path: { state: "", config: "", worktree: "", directory: "", home: "" },
ready: false,
agent: [],
- provider: [],
+ // provider: [],
session: [],
session_status: {},
session_diff: {},
@@ -124,20 +94,17 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
if (directory === "global") {
switch (event.type) {
case "project.updated": {
- const usedColors = new Set(globalStore.projects.map((p) => p.icon?.color).filter(Boolean) as string[])
- ensureProjectColor(event.properties, sdk, usedColors).then((project) => {
- const result = Binary.search(globalStore.projects, project.id, (s) => s.id)
- if (result.found) {
- setGlobalStore("projects", result.index, reconcile(project))
- return
- }
- setGlobalStore(
- "projects",
- produce((draft) => {
- draft.splice(result.index, 0, project)
- }),
- )
- })
+ const result = Binary.search(globalStore.projects, event.properties.id, (s) => s.id)
+ if (result.found) {
+ setGlobalStore("projects", result.index, reconcile(event.properties))
+ return
+ }
+ setGlobalStore(
+ "projects",
+ produce((draft) => {
+ draft.splice(result.index, 0, event.properties)
+ }),
+ )
break
}
}
@@ -216,14 +183,16 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple
Promise.all([
sdk.client.project.list().then(async (x) => {
- const filtered = x.data!.filter((p) => !p.worktree.includes("opencode-test") && p.vcs)
- const usedColors = new Set(filtered.map((p) => p.icon?.color).filter(Boolean) as string[])
- const projects = await Promise.all(filtered.map((p) => ensureProjectColor(p, sdk, usedColors)))
setGlobalStore(
"projects",
- projects.sort((a, b) => a.id.localeCompare(b.id)),
+ x
+ .data!.filter((p) => !p.worktree.includes("opencode-test") && p.vcs)
+ .sort((a, b) => a.id.localeCompare(b.id)),
)
}),
+ sdk.client.provider.list().then((x) => {
+ setGlobalStore("providers", x.data ?? [])
+ }),
]).then(() => setGlobalStore("ready", true))
return {
diff --git a/packages/desktop/src/context/layout.tsx b/packages/desktop/src/context/layout.tsx
index 05a47c4eb..13c4679d6 100644
--- a/packages/desktop/src/context/layout.tsx
+++ b/packages/desktop/src/context/layout.tsx
@@ -4,6 +4,20 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
import { makePersisted } from "@solid-primitives/storage"
import { useGlobalSync } from "./global-sync"
import { useGlobalSDK } from "./global-sdk"
+import { Project } from "@opencode-ai/sdk/v2"
+
+const PASTEL_COLORS = [
+ "#FCEAFD", // pastel pink
+ "#FFDFBA", // pastel peach
+ "#FFFFBA", // pastel yellow
+ "#BAFFC9", // pastel green
+ "#EAF6FD", // pastel blue
+ "#EFEAFD", // pastel lavender
+ "#FEC8D8", // pastel rose
+ "#D4F0F0", // pastel cyan
+ "#FDF0EA", // pastel coral
+ "#C1E1C1", // pastel mint
+]
export const { use: useLayout, provider: LayoutProvider } = createSimpleContext({
name: "Layout",
@@ -30,6 +44,42 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
)
+ function pickAvailableColor() {
+ const available = PASTEL_COLORS.filter((c) => !colors().has(c))
+ if (available.length === 0) return PASTEL_COLORS[Math.floor(Math.random() * PASTEL_COLORS.length)]
+ return available[Math.floor(Math.random() * available.length)]
+ }
+
+ function enrich(project: { worktree: string; expanded: boolean }) {
+ const metadata = globalSync.data.projects.find((x) => x.worktree === project.worktree)
+ if (!metadata) return []
+ return [
+ {
+ ...project,
+ ...metadata,
+ },
+ ]
+ }
+
+ function colorize(project: Project & { expanded: boolean }) {
+ if (project.icon?.color) return project
+ const color = pickAvailableColor()
+ project.icon = { ...project.icon, color }
+ globalSdk.client.project.update({ projectID: project.id, icon: { color } })
+ return project
+ }
+
+ const enriched = createMemo(() => store.projects.flatMap(enrich))
+ const list = createMemo(() => enriched().flatMap(colorize))
+ const colors = createMemo(
+ () =>
+ new Set(
+ list()
+ .map((p) => p.icon?.color)
+ .filter(Boolean),
+ ),
+ )
+
async function loadProjectSessions(directory: string) {
const [, setStore] = globalSync.child(directory)
globalSdk.client.session.list({ directory }).then((x) => {
@@ -43,26 +93,15 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
onMount(() => {
Promise.all(
- store.projects.map(({ worktree }) => {
- return loadProjectSessions(worktree)
+ store.projects.map((project) => {
+ return loadProjectSessions(project.worktree)
}),
)
})
- function enrich(project: { worktree: string; expanded: boolean }) {
- const metadata = globalSync.data.projects.find((x) => x.worktree === project.worktree)
- if (!metadata) return []
- return [
- {
- ...project,
- ...metadata,
- },
- ]
- }
-
return {
projects: {
- list: createMemo(() => store.projects.flatMap(enrich)),
+ list,
open(directory: string) {
if (store.projects.find((x) => x.worktree === directory)) return
loadProjectSessions(directory)
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx
index 4a17d01bd..3e0094756 100644
--- a/packages/desktop/src/pages/layout.tsx
+++ b/packages/desktop/src/pages/layout.tsx
@@ -29,6 +29,8 @@ import {
useDragDropContext,
} from "@thisbeyond/solid-dnd"
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
+import { SelectDialog } from "@opencode-ai/ui/select-dialog"
+import { Tag } from "@opencode-ai/ui/tag"
export default function Layout(props: ParentProps) {
const [store, setStore] = createStore({
@@ -44,11 +46,16 @@ export default function Layout(props: ParentProps) {
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
const sessions = createMemo(() => globalSync.child(currentDirectory())[0].session ?? [])
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
+ const providers = createMemo(() => globalSync.data.providers)
const hasProviders = createMemo(() => {
const [projectStore] = globalSync.child(currentDirectory())
return projectStore.provider.filter((p) => p.id !== "opencode").length > 0
})
+ createEffect(() => {
+ console.log(providers())
+ })
+
function navigateToProject(directory: string | undefined) {
if (!directory) return
const lastSession = store.lastSession[directory]
@@ -550,6 +557,37 @@ export default function Layout(props: ParentProps) {