Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43369bd9fc | |||
| 8f65610c15 |
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
"@dnd-kit/dom": "0.5.0",
|
||||
@@ -93,7 +93,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"bin": {
|
||||
"lildax": "./bin/lildax.cjs",
|
||||
},
|
||||
@@ -141,7 +141,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -155,7 +155,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -191,7 +191,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -218,7 +218,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -240,7 +240,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -264,7 +264,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
@@ -284,7 +284,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -378,7 +378,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"effect": "catalog:",
|
||||
@@ -432,7 +432,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -446,7 +446,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -458,7 +458,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -490,7 +490,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -506,7 +506,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||
@@ -537,7 +537,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
@@ -556,7 +556,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -687,7 +687,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -763,7 +763,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -778,7 +778,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
@@ -793,7 +793,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -837,7 +837,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -850,7 +850,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@opencode-ai/stats-core": "workspace:*",
|
||||
@@ -883,7 +883,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -902,7 +902,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -943,7 +943,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
@@ -970,7 +970,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -1021,7 +1021,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -86,7 +86,6 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
||||
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
|
||||
const state = { cleanup: undefined as (() => void) | void, committed: false }
|
||||
const filesOnly = () => props.filesOnly?.() ?? false
|
||||
const filesEnabled = () => filesOnly() || layout.route().type === "session"
|
||||
|
||||
const allowedCommands = createMemo(() => {
|
||||
if (filesOnly()) return []
|
||||
@@ -115,7 +114,6 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
||||
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
||||
})
|
||||
const recentFileEntries = createMemo(() => {
|
||||
if (!filesEnabled()) return []
|
||||
const all = tabState.openedTabs()
|
||||
const active = tabState.activeFileTab()
|
||||
const order = active ? [active, ...all.filter((item) => item !== active)] : all
|
||||
@@ -132,7 +130,6 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
||||
.map((path) => createCommandPaletteFileEntry(path, category))
|
||||
})
|
||||
const rootFileEntries = createMemo(() => {
|
||||
if (!filesEnabled()) return []
|
||||
const category = language.t("palette.group.files")
|
||||
return file.tree
|
||||
.children("")
|
||||
@@ -214,7 +211,6 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
||||
preferredCommandEntries,
|
||||
recentFileEntries,
|
||||
rootFileEntries,
|
||||
searchFiles: (query: string) => (filesEnabled() ? file.searchFiles(query) : Promise.resolve([])),
|
||||
sessions,
|
||||
highlight,
|
||||
select,
|
||||
|
||||
@@ -37,7 +37,7 @@ export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => v
|
||||
const q = text.trim()
|
||||
if (!q) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
|
||||
|
||||
const [files, nextSessions] = await Promise.all([palette.searchFiles(q), Promise.resolve(palette.sessions(q))])
|
||||
const [files, nextSessions] = await Promise.all([palette.file.searchFiles(q), Promise.resolve(palette.sessions(q))])
|
||||
const category = palette.language.t("palette.group.files")
|
||||
return [
|
||||
...palette.commandEntries().filter((entry) => matchesEntry(entry, q)),
|
||||
|
||||
@@ -92,13 +92,13 @@ function DialogSelectFileLegacy(props: { filesOnly: () => boolean; onOpenFile?:
|
||||
if (!query) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
|
||||
|
||||
if (props.filesOnly()) {
|
||||
const files = await palette.searchFiles(query)
|
||||
const files = await palette.file.searchFiles(query)
|
||||
const category = palette.language.t("palette.group.files")
|
||||
return files.map((path) => createCommandPaletteFileEntry(path, category))
|
||||
}
|
||||
|
||||
const [files, nextSessions] = await Promise.all([
|
||||
palette.searchFiles(query),
|
||||
palette.file.searchFiles(query),
|
||||
Promise.resolve(palette.sessions(query)),
|
||||
])
|
||||
const category = palette.language.t("palette.group.files")
|
||||
|
||||
@@ -520,7 +520,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const setMode = (mode: "normal" | "shell") => {
|
||||
setStore("mode", mode)
|
||||
setStore("popover", null)
|
||||
setStore({ popover: null, slashMenu: false, slashMenuQuery: "" })
|
||||
requestAnimationFrame(() => editorRef?.focus())
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
},
|
||||
])
|
||||
|
||||
const closePopover = () => setStore("popover", null)
|
||||
const closePopover = () => setStore({ popover: null, slashMenu: false, slashMenuQuery: "" })
|
||||
|
||||
const resetHistoryNavigation = (force = false) => {
|
||||
if (!force && (store.historyIndex < 0 || store.applyingHistory)) return
|
||||
@@ -800,17 +800,30 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const handleSlashSelect = (cmd: SlashCommand | undefined) => {
|
||||
if (!cmd) return
|
||||
const menu = store.slashMenu
|
||||
closePopover()
|
||||
const images = imageAttachments()
|
||||
|
||||
if (cmd.type === "custom") {
|
||||
const text = `/${cmd.trigger} `
|
||||
if (menu) {
|
||||
editorRef.focus()
|
||||
setCursorPosition(editorRef, 0)
|
||||
addPart({ type: "text", content: text, start: 0, end: text.length })
|
||||
focusEditorEnd()
|
||||
return
|
||||
}
|
||||
setEditorText(text)
|
||||
prompt.set([{ type: "text", content: text, start: 0, end: text.length }, ...images], text.length)
|
||||
focusEditorEnd()
|
||||
return
|
||||
}
|
||||
|
||||
if (menu) {
|
||||
command.trigger(cmd.id, "slash")
|
||||
return
|
||||
}
|
||||
|
||||
clearEditor()
|
||||
prompt.set([...DEFAULT_PROMPT, ...images], 0)
|
||||
command.trigger(cmd.id, "slash")
|
||||
@@ -1072,10 +1085,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
if (atMatch) {
|
||||
atOnInput(atMatch[1])
|
||||
setStore("popover", "at")
|
||||
setStore({ popover: "at", slashMenu: false, slashMenuQuery: "" })
|
||||
} else if (slashMatch) {
|
||||
slashOnInput(slashMatch[1])
|
||||
setStore("popover", "slash")
|
||||
setStore({ popover: "slash", slashMenu: false, slashMenuQuery: "" })
|
||||
} else {
|
||||
closePopover()
|
||||
}
|
||||
@@ -1171,6 +1184,28 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
return true
|
||||
}
|
||||
|
||||
const openCommands = () => {
|
||||
const populated = prompt.dirty() || commentCount() > 0
|
||||
requestAnimationFrame(() => {
|
||||
if (!populated) {
|
||||
if (!addPart({ type: "text", content: "/", start: 0, end: 0 })) return
|
||||
slashOnInput("")
|
||||
setStore({ popover: "slash", slashMenu: false, slashMenuQuery: "" })
|
||||
return
|
||||
}
|
||||
slashOnInput("")
|
||||
setStore({ popover: "slash", slashMenu: true, slashMenuQuery: "" })
|
||||
})
|
||||
}
|
||||
|
||||
const openContext = () => {
|
||||
requestAnimationFrame(() => {
|
||||
if (!addPart({ type: "text", content: "@", start: 0, end: 0 })) return
|
||||
atOnInput("")
|
||||
setStore({ popover: "at", slashMenu: false, slashMenuQuery: "" })
|
||||
})
|
||||
}
|
||||
|
||||
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
|
||||
history.add(prompt, mode, mode === "shell" ? [] : historyComments())
|
||||
}
|
||||
@@ -1199,7 +1234,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
setStore("mode", "normal")
|
||||
setStore("popover", null)
|
||||
closePopover()
|
||||
setStore("historyIndex", -1)
|
||||
setStore("savedPrompt", null)
|
||||
prompt.set(edit.prompt, promptLength(edit.prompt))
|
||||
@@ -1286,7 +1321,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
resetHistoryNavigation(true)
|
||||
},
|
||||
setMode: (mode) => setStore("mode", mode),
|
||||
setPopover: (popover) => setStore("popover", popover),
|
||||
setPopover: (popover) => {
|
||||
if (!popover) return closePopover()
|
||||
setStore({ popover, slashMenu: false, slashMenuQuery: "" })
|
||||
},
|
||||
newSessionWorktree: () => props.newSessionWorktree,
|
||||
onNewSessionWorktreeReset: props.onNewSessionWorktreeReset,
|
||||
shouldQueue: props.shouldQueue,
|
||||
@@ -1325,7 +1363,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
if (cursorPosition === 0) {
|
||||
setStore("mode", "shell")
|
||||
setStore("popover", null)
|
||||
closePopover()
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
@@ -1460,6 +1498,29 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSlashMenuKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
closePopover()
|
||||
requestAnimationFrame(() => editorRef.focus())
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "Tab") {
|
||||
selectPopoverActive()
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
const ctrl = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey
|
||||
const nav = event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter"
|
||||
const ctrlNav = ctrl && (event.key === "n" || event.key === "p")
|
||||
if (!nav && !ctrlNav) return
|
||||
slashOnKeyDown(event)
|
||||
if (event.key === "ArrowUp" || event.key === "ArrowDown" || ctrlNav) scrollSlashActiveIntoView()
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const agentsLoading = () => props.controls.agents.loading
|
||||
const agentsShouldFadeIn = createMemo<boolean>((prev) => prev ?? agentsLoading())
|
||||
const providersLoading = () => props.controls.model.loading
|
||||
@@ -1527,6 +1588,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
slashActive={slashActive() ?? undefined}
|
||||
setSlashActive={setSlashActive}
|
||||
onSlashSelect={handleSlashSelect}
|
||||
slashMenu={store.slashMenu}
|
||||
slashMenuQuery={store.slashMenuQuery}
|
||||
onSlashMenuInput={(value) => {
|
||||
setStore("slashMenuQuery", value)
|
||||
slashOnInput(value)
|
||||
}}
|
||||
onSlashMenuKeyDown={handleSlashMenuKeyDown}
|
||||
commandKeybind={command.keybind}
|
||||
commandKeybindParts={command.keybindParts}
|
||||
newLayoutDesigns={props.controls.newLayoutDesigns}
|
||||
@@ -1625,23 +1693,45 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
placement="top"
|
||||
value={
|
||||
<>
|
||||
{language.t("prompt.action.attachFile")}
|
||||
{language.t("prompt.menu.addImagesAndFiles")}
|
||||
<KeybindV2 keys={command.keybindParts("file.attach")} variant="neutral" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
data-action="prompt-attach"
|
||||
type="button"
|
||||
icon="plus"
|
||||
variant="ghost"
|
||||
class="size-7 rounded-md p-[6px] text-v2-icon-icon-muted"
|
||||
style={buttons()}
|
||||
onClick={pick}
|
||||
disabled={store.mode !== "normal"}
|
||||
tabIndex={store.mode === "normal" ? undefined : -1}
|
||||
aria-label={language.t("prompt.action.attachFile")}
|
||||
/>
|
||||
<MenuV2 gutter={6} modal={false} placement="top-start">
|
||||
<MenuV2.Trigger
|
||||
as={IconButton}
|
||||
data-action="prompt-attach"
|
||||
type="button"
|
||||
icon="plus"
|
||||
variant="ghost"
|
||||
class="size-7 rounded-md p-[6px] text-v2-icon-icon-muted"
|
||||
style={buttons()}
|
||||
disabled={store.mode !== "normal"}
|
||||
tabIndex={store.mode === "normal" ? undefined : -1}
|
||||
aria-label={language.t("prompt.menu.addImagesAndFiles")}
|
||||
/>
|
||||
<MenuV2.Portal>
|
||||
<MenuV2.Content
|
||||
class="[&_[data-slot=menu-v2-item-shortcut]]:w-5 [&_[data-slot=menu-v2-item-shortcut]]:justify-center"
|
||||
style={{ "min-width": "180px" }}
|
||||
>
|
||||
<MenuV2.Item onSelect={pick} shortcut={command.keybind("file.attach")}>
|
||||
{language.t("prompt.menu.imagesAndFiles")}
|
||||
</MenuV2.Item>
|
||||
<MenuV2.Separator />
|
||||
<MenuV2.Item onSelect={openCommands} shortcut="/">
|
||||
{language.t("prompt.menu.commands")}
|
||||
</MenuV2.Item>
|
||||
<MenuV2.Item onSelect={openContext} shortcut="@">
|
||||
{language.t("prompt.menu.context")}
|
||||
</MenuV2.Item>
|
||||
<MenuV2.Item onSelect={() => setMode("shell")} shortcut="!">
|
||||
{language.t("prompt.menu.shellCommand")}
|
||||
</MenuV2.Item>
|
||||
</MenuV2.Content>
|
||||
</MenuV2.Portal>
|
||||
</MenuV2>
|
||||
</TooltipV2>
|
||||
<Show when={showAgentControl()}>
|
||||
<ComposerAgentControl state={agentControlState()} />
|
||||
|
||||
@@ -41,6 +41,10 @@ type PromptPopoverProps = {
|
||||
slashActive?: string
|
||||
setSlashActive: (id: string) => void
|
||||
onSlashSelect: (item: SlashCommand) => void
|
||||
slashMenu: boolean
|
||||
slashMenuQuery: string
|
||||
onSlashMenuInput: (value: string) => void
|
||||
onSlashMenuKeyDown: (event: KeyboardEvent) => void
|
||||
commandKeybind: (id: string) => string | undefined
|
||||
commandKeybindParts: (id: string) => string[]
|
||||
newLayoutDesigns: boolean
|
||||
@@ -254,6 +258,20 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={props.popover === "slash"}>
|
||||
<Show when={props.slashMenu}>
|
||||
<div class="px-2 py-1">
|
||||
<input
|
||||
ref={(el) => requestAnimationFrame(() => el.focus())}
|
||||
value={props.slashMenuQuery}
|
||||
onInput={(event) => props.onSlashMenuInput(event.currentTarget.value)}
|
||||
onKeyDown={props.onSlashMenuKeyDown}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
aria-label={props.t("prompt.menu.commands")}
|
||||
placeholder="/"
|
||||
class="w-full bg-transparent outline-none text-[13px] leading-5 text-v2-text-text-base placeholder:text-v2-text-text-faint"
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
<Show
|
||||
when={props.slashFlat.length > 0}
|
||||
fallback={
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { PromptHistoryEntry } from "./history"
|
||||
|
||||
export type PromptInputTransientState = {
|
||||
popover: "at" | "slash" | null
|
||||
slashMenu: boolean
|
||||
slashMenuQuery: string
|
||||
historyIndex: number
|
||||
savedPrompt: PromptHistoryEntry | null
|
||||
placeholder: number
|
||||
@@ -16,6 +18,8 @@ export type PromptInputTransientState = {
|
||||
function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTransientState>) {
|
||||
setStore({
|
||||
popover: null,
|
||||
slashMenu: false,
|
||||
slashMenuQuery: "",
|
||||
historyIndex: -1,
|
||||
savedPrompt: null,
|
||||
draggingType: null,
|
||||
@@ -28,6 +32,8 @@ function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTr
|
||||
export function createPromptInputTransientState(identity: Accessor<unknown>, placeholder: number) {
|
||||
const [store, setStore] = createStore<PromptInputTransientState>({
|
||||
popover: null,
|
||||
slashMenu: false,
|
||||
slashMenuQuery: "",
|
||||
historyIndex: -1,
|
||||
savedPrompt: null,
|
||||
placeholder,
|
||||
|
||||
@@ -255,6 +255,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "إزالة الملف النشط من السياق",
|
||||
"prompt.context.removeFile": "إزالة الملف من السياق",
|
||||
"prompt.action.attachFile": "إرفاق ملف",
|
||||
"prompt.menu.addImagesAndFiles": "إضافة ملفات والمزيد",
|
||||
"prompt.menu.imagesAndFiles": "الصور والملفات",
|
||||
"prompt.menu.commands": "الأوامر",
|
||||
"prompt.menu.context": "السياق",
|
||||
"prompt.menu.shellCommand": "أمر shell",
|
||||
"prompt.attachment.remove": "إزالة المرفق",
|
||||
"prompt.action.send": "إرسال",
|
||||
"prompt.action.stop": "توقف",
|
||||
|
||||
@@ -255,6 +255,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Remover arquivo ativo do contexto",
|
||||
"prompt.context.removeFile": "Remover arquivo do contexto",
|
||||
"prompt.action.attachFile": "Anexar arquivo",
|
||||
"prompt.menu.addImagesAndFiles": "Adicionar arquivos e mais",
|
||||
"prompt.menu.imagesAndFiles": "Imagens e arquivos",
|
||||
"prompt.menu.commands": "Comandos",
|
||||
"prompt.menu.context": "Contexto",
|
||||
"prompt.menu.shellCommand": "Comando shell",
|
||||
"prompt.attachment.remove": "Remover anexo",
|
||||
"prompt.action.send": "Enviar",
|
||||
"prompt.action.stop": "Parar",
|
||||
|
||||
@@ -275,6 +275,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Ukloni aktivnu datoteku iz konteksta",
|
||||
"prompt.context.removeFile": "Ukloni datoteku iz konteksta",
|
||||
"prompt.action.attachFile": "Priloži datoteku",
|
||||
"prompt.menu.addImagesAndFiles": "Dodaj datoteke i više",
|
||||
"prompt.menu.imagesAndFiles": "Slike i datoteke",
|
||||
"prompt.menu.commands": "Komande",
|
||||
"prompt.menu.context": "Kontekst",
|
||||
"prompt.menu.shellCommand": "Shell naredba",
|
||||
"prompt.attachment.remove": "Ukloni prilog",
|
||||
"prompt.action.send": "Pošalji",
|
||||
"prompt.action.stop": "Zaustavi",
|
||||
|
||||
@@ -273,6 +273,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
|
||||
"prompt.context.removeFile": "Fjern fil fra kontekst",
|
||||
"prompt.action.attachFile": "Vedhæft fil",
|
||||
"prompt.menu.addImagesAndFiles": "Tilføj filer og mere",
|
||||
"prompt.menu.imagesAndFiles": "Billeder og filer",
|
||||
"prompt.menu.commands": "Kommandoer",
|
||||
"prompt.menu.context": "Kontekst",
|
||||
"prompt.menu.shellCommand": "Shell-kommando",
|
||||
"prompt.attachment.remove": "Fjern vedhæftning",
|
||||
"prompt.action.send": "Send",
|
||||
"prompt.action.stop": "Stop",
|
||||
|
||||
@@ -260,6 +260,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Aktive Datei aus dem Kontext entfernen",
|
||||
"prompt.context.removeFile": "Datei aus dem Kontext entfernen",
|
||||
"prompt.action.attachFile": "Datei anhängen",
|
||||
"prompt.menu.addImagesAndFiles": "Dateien und mehr hinzufügen",
|
||||
"prompt.menu.imagesAndFiles": "Bilder und Dateien",
|
||||
"prompt.menu.commands": "Befehle",
|
||||
"prompt.menu.context": "Kontext",
|
||||
"prompt.menu.shellCommand": "Shell-Befehl",
|
||||
"prompt.attachment.remove": "Anhang entfernen",
|
||||
"prompt.action.send": "Senden",
|
||||
"prompt.action.stop": "Stopp",
|
||||
|
||||
@@ -287,6 +287,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Remove active file from context",
|
||||
"prompt.context.removeFile": "Remove file from context",
|
||||
"prompt.action.attachFile": "Add files",
|
||||
"prompt.menu.addImagesAndFiles": "Add files and more",
|
||||
"prompt.menu.imagesAndFiles": "Images and files",
|
||||
"prompt.menu.commands": "Commands",
|
||||
"prompt.menu.context": "Context",
|
||||
"prompt.menu.shellCommand": "Shell command",
|
||||
"prompt.attachment.remove": "Remove attachment",
|
||||
"prompt.action.send": "Send",
|
||||
"prompt.action.stop": "Stop",
|
||||
|
||||
@@ -274,6 +274,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Eliminar archivo activo del contexto",
|
||||
"prompt.context.removeFile": "Eliminar archivo del contexto",
|
||||
"prompt.action.attachFile": "Adjuntar archivo",
|
||||
"prompt.menu.addImagesAndFiles": "Añadir archivos y más",
|
||||
"prompt.menu.imagesAndFiles": "Imágenes y archivos",
|
||||
"prompt.menu.commands": "Comandos",
|
||||
"prompt.menu.context": "Contexto",
|
||||
"prompt.menu.shellCommand": "Comando de shell",
|
||||
"prompt.attachment.remove": "Eliminar adjunto",
|
||||
"prompt.action.send": "Enviar",
|
||||
"prompt.action.stop": "Detener",
|
||||
|
||||
@@ -255,6 +255,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Retirer le fichier actif du contexte",
|
||||
"prompt.context.removeFile": "Retirer le fichier du contexte",
|
||||
"prompt.action.attachFile": "Joindre un fichier",
|
||||
"prompt.menu.addImagesAndFiles": "Ajouter des fichiers et plus encore",
|
||||
"prompt.menu.imagesAndFiles": "Images et fichiers",
|
||||
"prompt.menu.commands": "Commandes",
|
||||
"prompt.menu.context": "Contexte",
|
||||
"prompt.menu.shellCommand": "Commande shell",
|
||||
"prompt.attachment.remove": "Supprimer la pièce jointe",
|
||||
"prompt.action.send": "Envoyer",
|
||||
"prompt.action.stop": "Arrêter",
|
||||
|
||||
@@ -254,6 +254,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "コンテキストからアクティブなファイルを削除",
|
||||
"prompt.context.removeFile": "コンテキストからファイルを削除",
|
||||
"prompt.action.attachFile": "ファイルを添付",
|
||||
"prompt.menu.addImagesAndFiles": "ファイルなどを追加",
|
||||
"prompt.menu.imagesAndFiles": "画像とファイル",
|
||||
"prompt.menu.commands": "コマンド",
|
||||
"prompt.menu.context": "コンテキスト",
|
||||
"prompt.menu.shellCommand": "シェルコマンド",
|
||||
"prompt.attachment.remove": "添付ファイルを削除",
|
||||
"prompt.action.send": "送信",
|
||||
"prompt.action.stop": "停止",
|
||||
|
||||
@@ -254,6 +254,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "컨텍스트에서 활성 파일 제거",
|
||||
"prompt.context.removeFile": "컨텍스트에서 파일 제거",
|
||||
"prompt.action.attachFile": "파일 첨부",
|
||||
"prompt.menu.addImagesAndFiles": "파일 및 기타 항목 추가",
|
||||
"prompt.menu.imagesAndFiles": "이미지 및 파일",
|
||||
"prompt.menu.commands": "명령어",
|
||||
"prompt.menu.context": "컨텍스트",
|
||||
"prompt.menu.shellCommand": "셸 명령",
|
||||
"prompt.attachment.remove": "첨부 파일 제거",
|
||||
"prompt.action.send": "전송",
|
||||
"prompt.action.stop": "중지",
|
||||
|
||||
@@ -277,6 +277,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Fjern aktiv fil fra kontekst",
|
||||
"prompt.context.removeFile": "Fjern fil fra kontekst",
|
||||
"prompt.action.attachFile": "Legg ved fil",
|
||||
"prompt.menu.addImagesAndFiles": "Legg til filer og mer",
|
||||
"prompt.menu.imagesAndFiles": "Bilder og filer",
|
||||
"prompt.menu.commands": "Kommandoer",
|
||||
"prompt.menu.context": "Kontekst",
|
||||
"prompt.menu.shellCommand": "Shell-kommando",
|
||||
"prompt.attachment.remove": "Fjern vedlegg",
|
||||
"prompt.action.send": "Send",
|
||||
"prompt.action.stop": "Stopp",
|
||||
|
||||
@@ -256,6 +256,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Usuń aktywny plik z kontekstu",
|
||||
"prompt.context.removeFile": "Usuń plik z kontekstu",
|
||||
"prompt.action.attachFile": "Załącz plik",
|
||||
"prompt.menu.addImagesAndFiles": "Dodaj pliki i inne elementy",
|
||||
"prompt.menu.imagesAndFiles": "Obrazy i pliki",
|
||||
"prompt.menu.commands": "Polecenia",
|
||||
"prompt.menu.context": "Kontekst",
|
||||
"prompt.menu.shellCommand": "Polecenie powłoki",
|
||||
"prompt.attachment.remove": "Usuń załącznik",
|
||||
"prompt.action.send": "Wyślij",
|
||||
"prompt.action.stop": "Zatrzymaj",
|
||||
|
||||
@@ -274,6 +274,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Удалить активный файл из контекста",
|
||||
"prompt.context.removeFile": "Удалить файл из контекста",
|
||||
"prompt.action.attachFile": "Прикрепить файл",
|
||||
"prompt.menu.addImagesAndFiles": "Добавить файлы и другое",
|
||||
"prompt.menu.imagesAndFiles": "Изображения и файлы",
|
||||
"prompt.menu.commands": "Команды",
|
||||
"prompt.menu.context": "Контекст",
|
||||
"prompt.menu.shellCommand": "Команда оболочки",
|
||||
"prompt.attachment.remove": "Удалить вложение",
|
||||
"prompt.action.send": "Отправить",
|
||||
"prompt.action.stop": "Остановить",
|
||||
|
||||
@@ -274,6 +274,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "เอาไฟล์ที่ใช้งานอยู่ออกจากบริบท",
|
||||
"prompt.context.removeFile": "เอาไฟล์ออกจากบริบท",
|
||||
"prompt.action.attachFile": "แนบไฟล์",
|
||||
"prompt.menu.addImagesAndFiles": "เพิ่มไฟล์และอื่น ๆ",
|
||||
"prompt.menu.imagesAndFiles": "รูปภาพและไฟล์",
|
||||
"prompt.menu.commands": "คำสั่ง",
|
||||
"prompt.menu.context": "บริบท",
|
||||
"prompt.menu.shellCommand": "คำสั่งเชลล์",
|
||||
"prompt.attachment.remove": "เอาไฟล์แนบออก",
|
||||
"prompt.action.send": "ส่ง",
|
||||
"prompt.action.stop": "หยุด",
|
||||
|
||||
@@ -279,6 +279,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Aktif dosyayı bağlamdan çıkar",
|
||||
"prompt.context.removeFile": "Dosyayı bağlamdan çıkar",
|
||||
"prompt.action.attachFile": "Dosya ekle",
|
||||
"prompt.menu.addImagesAndFiles": "Dosya ve daha fazlasını ekle",
|
||||
"prompt.menu.imagesAndFiles": "Görseller ve dosyalar",
|
||||
"prompt.menu.commands": "Komutlar",
|
||||
"prompt.menu.context": "Bağlam",
|
||||
"prompt.menu.shellCommand": "Kabuk komutu",
|
||||
"prompt.attachment.remove": "Eki kaldır",
|
||||
"prompt.action.send": "Gönder",
|
||||
"prompt.action.stop": "Durdur",
|
||||
|
||||
@@ -281,6 +281,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "Видалити активний файл з контексту",
|
||||
"prompt.context.removeFile": "Видалити файл з контексту",
|
||||
"prompt.action.attachFile": "Додати файли",
|
||||
"prompt.menu.addImagesAndFiles": "Додати файли та інше",
|
||||
"prompt.menu.imagesAndFiles": "Зображення та файли",
|
||||
"prompt.menu.commands": "Команди",
|
||||
"prompt.menu.context": "Контекст",
|
||||
"prompt.menu.shellCommand": "Команда оболонки",
|
||||
"prompt.attachment.remove": "Видалити вкладення",
|
||||
"prompt.action.send": "Надіслати",
|
||||
"prompt.action.stop": "Зупинити",
|
||||
|
||||
@@ -294,6 +294,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "从上下文移除活动文件",
|
||||
"prompt.context.removeFile": "从上下文移除文件",
|
||||
"prompt.action.attachFile": "附加文件",
|
||||
"prompt.menu.addImagesAndFiles": "添加文件及更多内容",
|
||||
"prompt.menu.imagesAndFiles": "图片和文件",
|
||||
"prompt.menu.commands": "命令",
|
||||
"prompt.menu.context": "上下文",
|
||||
"prompt.menu.shellCommand": "shell 命令",
|
||||
"prompt.attachment.remove": "移除附件",
|
||||
"prompt.action.send": "发送",
|
||||
"prompt.action.stop": "停止",
|
||||
|
||||
@@ -274,6 +274,11 @@ export const dict = {
|
||||
"prompt.context.removeActiveFile": "從上下文移除目前檔案",
|
||||
"prompt.context.removeFile": "從上下文移除檔案",
|
||||
"prompt.action.attachFile": "附加檔案",
|
||||
"prompt.menu.addImagesAndFiles": "新增檔案及更多內容",
|
||||
"prompt.menu.imagesAndFiles": "圖片和檔案",
|
||||
"prompt.menu.commands": "命令",
|
||||
"prompt.menu.context": "上下文",
|
||||
"prompt.menu.shellCommand": "shell 命令",
|
||||
"prompt.attachment.remove": "移除附件",
|
||||
"prompt.action.send": "傳送",
|
||||
"prompt.action.stop": "停止",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Show, createEffect, createMemo, createResource, onCleanup, untrack } from "solid-js"
|
||||
import { Show, createEffect, createMemo, createResource, untrack } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Portal } from "solid-js/web"
|
||||
import { useSearchParams } from "@solidjs/router"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { NewSessionDesignView } from "@/components/session"
|
||||
import { PromptInput } from "@/components/prompt-input"
|
||||
import { StatusPopoverV2 } from "@/components/status-popover"
|
||||
@@ -20,7 +19,6 @@ import { useSync } from "@/context/sync"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useSettings } from "@/context/settings"
|
||||
import { useCommand } from "@/context/command"
|
||||
import { createPromptInputController, createPromptProjectControls } from "@/pages/session/composer"
|
||||
import { useSessionKey } from "@/pages/session/session-layout"
|
||||
import { useComposerCommands } from "@/pages/session/use-composer-commands"
|
||||
@@ -43,34 +41,11 @@ export default function NewSessionPage() {
|
||||
const comments = useComments()
|
||||
const language = useLanguage()
|
||||
const settings = useSettings()
|
||||
const command = useCommand()
|
||||
const dialog = useDialog()
|
||||
const route = useSessionKey()
|
||||
const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
|
||||
|
||||
useComposerCommands()
|
||||
useSettingsCommand()
|
||||
let paletteRun = 0
|
||||
let paletteDead = false
|
||||
onCleanup(() => {
|
||||
paletteDead = true
|
||||
})
|
||||
command.register("draft-palette", () => [
|
||||
{
|
||||
id: "file.open",
|
||||
title: language.t("command.file.open"),
|
||||
description: language.t("palette.search.placeholder"),
|
||||
category: language.t("command.category.file"),
|
||||
keybind: "mod+k,mod+p",
|
||||
onSelect: () => {
|
||||
const run = ++paletteRun
|
||||
void import("@/components/dialog-select-file").then((module) => {
|
||||
if (paletteDead || run !== paletteRun) return
|
||||
dialog.show(() => <module.DialogSelectFile />)
|
||||
})
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
let inputRef: HTMLDivElement | undefined
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ test("resets transient prompt input state when the prompt session changes", () =
|
||||
const [state, setState] = createPromptInputTransientState(identity, 3)
|
||||
setState({
|
||||
popover: "slash",
|
||||
slashMenu: true,
|
||||
slashMenuQuery: "compact",
|
||||
historyIndex: 2,
|
||||
savedPrompt: {
|
||||
prompt: [{ type: "text", content: "draft-A", start: 0, end: 7 }],
|
||||
@@ -23,6 +25,8 @@ test("resets transient prompt input state when the prompt session changes", () =
|
||||
|
||||
expect(state).toMatchObject({
|
||||
popover: null,
|
||||
slashMenu: false,
|
||||
slashMenuQuery: "",
|
||||
historyIndex: -1,
|
||||
savedPrompt: null,
|
||||
placeholder: 3,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -11,11 +11,6 @@ import { FSUtil } from "../../fs-util"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ConfigAgentV1 } from "../../v1/config/agent"
|
||||
import { ConfigMigrateV1 } from "../../v1/config/migrate"
|
||||
import { Global } from "../../global"
|
||||
import { PermissionV2 } from "../../permission"
|
||||
import type { LocationMutation } from "../../location-mutation"
|
||||
import type { ReadTool } from "../../tool/read"
|
||||
import type { EditTool } from "../../tool/edit"
|
||||
|
||||
const legacySources = [
|
||||
{ pattern: "{agent,agents}/**/*.md", primary: false },
|
||||
@@ -24,11 +19,6 @@ const legacySources = [
|
||||
const decodeAgent = Schema.decodeUnknownOption(ConfigAgent.Info)
|
||||
const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.Info)
|
||||
const decodeConfig = Schema.decodeUnknownOption(Config.Info)
|
||||
type PathAction =
|
||||
| LocationMutation.ExternalDirectoryAuthorization["action"]
|
||||
| typeof ReadTool.name
|
||||
| typeof EditTool.name
|
||||
const pathActions = ["external_directory", "read", "edit"] as const satisfies readonly PathAction[]
|
||||
const agentKeys = new Set([
|
||||
"model",
|
||||
"variant",
|
||||
@@ -48,7 +38,6 @@ export const Plugin = define({
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
const config = yield* Config.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
const global = yield* Global.Service
|
||||
yield* ctx.agent.transform(
|
||||
Effect.fn(function* (draft) {
|
||||
const documents = yield* Effect.forEach(yield* config.entries(), (entry) => {
|
||||
@@ -67,14 +56,11 @@ export const Plugin = define({
|
||||
)
|
||||
})
|
||||
}).pipe(Effect.map((documents) => documents.flat()))
|
||||
const permissions = expandPermissions(
|
||||
documents.flatMap((document) => document.info.permissions ?? []),
|
||||
global.home,
|
||||
)
|
||||
const global = documents.flatMap((document) => document.info.permissions ?? [])
|
||||
const configuredDefault = Config.latest(documents, "default_agent")
|
||||
if (configuredDefault !== undefined) draft.default(AgentV2.ID.make(configuredDefault))
|
||||
for (const current of draft.list()) {
|
||||
draft.update(current.id, (agent) => agent.permissions.push(...permissions))
|
||||
draft.update(current.id, (agent) => agent.permissions.push(...global))
|
||||
}
|
||||
|
||||
for (const document of documents) {
|
||||
@@ -87,7 +73,7 @@ export const Plugin = define({
|
||||
|
||||
const exists = draft.get(agentID) !== undefined
|
||||
draft.update(agentID, (agent) => {
|
||||
if (!exists) agent.permissions.push(...permissions)
|
||||
if (!exists) agent.permissions.push(...global)
|
||||
if (item.model !== undefined) {
|
||||
const model = ModelV2.parse(item.model)
|
||||
agent.model = { id: model.modelID, providerID: model.providerID, variant: agent.model?.variant }
|
||||
@@ -105,9 +91,7 @@ export const Plugin = define({
|
||||
if (item.hidden !== undefined) agent.hidden = item.hidden
|
||||
if (item.color !== undefined) agent.color = item.color
|
||||
if (item.steps !== undefined) agent.steps = item.steps
|
||||
if (item.permissions !== undefined) {
|
||||
agent.permissions.push(...expandPermissions(item.permissions, global.home))
|
||||
}
|
||||
if (item.permissions !== undefined) agent.permissions.push(...item.permissions)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -116,27 +100,6 @@ export const Plugin = define({
|
||||
}),
|
||||
})
|
||||
|
||||
function expandPermissions(rules: PermissionV2.Ruleset, home: string): PermissionV2.Ruleset {
|
||||
// Expand only resources tools resolve as filesystem paths. Bash resources are raw shell text:
|
||||
// rewriting `$HOME/private/**` would miss `$HOME/private/key`, and safe expansion needs shell-aware parsing.
|
||||
return rules.map((rule) =>
|
||||
isPathAction(rule.action) ? { ...rule, resource: expandHome(rule.resource, home) } : rule,
|
||||
)
|
||||
}
|
||||
|
||||
function isPathAction(action: string): action is PathAction {
|
||||
return pathActions.some((item) => item === action)
|
||||
}
|
||||
|
||||
function expandHome(resource: string, home: string) {
|
||||
if (resource.startsWith("~/")) return home + resource.slice(1)
|
||||
if (resource === "~") return home
|
||||
if (resource === "$HOME") return home
|
||||
if (resource.startsWith("$HOME/")) return home + resource.slice(5)
|
||||
if (resource.startsWith("$HOME\\")) return home + resource.slice(5)
|
||||
return resource
|
||||
}
|
||||
|
||||
function discover(fs: FSUtil.Interface, directory: string) {
|
||||
return Effect.forEach(legacySources, (source) =>
|
||||
fs
|
||||
|
||||
@@ -22,27 +22,20 @@ const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <te
|
||||
- [constraints/preferences, decisions and why, important facts/assumptions, exact context needed to continue, or "(none)"]
|
||||
|
||||
## Work State
|
||||
### Completed
|
||||
- [finished work, verified facts, or changes made; otherwise "(none)"]
|
||||
|
||||
### Active
|
||||
- [current work, partial changes, or investigation state; otherwise "(none)"]
|
||||
|
||||
### Blocked
|
||||
- [blockers, failing commands, or unknowns; otherwise "(none)"]
|
||||
- Completed: [finished work, verified facts, or changes made; otherwise "(none)"]
|
||||
- Active: [current work, partial changes, or investigation state; otherwise "(none)"]
|
||||
- Blocked: [blockers, failing commands, or unknowns; otherwise "(none)"]
|
||||
|
||||
## Next Move
|
||||
1. [immediate concrete action, or "(none)"]
|
||||
2. [next action if known, or "(none)"]
|
||||
|
||||
## Relevant Files
|
||||
- [file or directory path: why it matters, or "(none)"]
|
||||
</template>
|
||||
|
||||
Rules:
|
||||
- Keep every section, even when empty.
|
||||
- Use terse bullets, not prose paragraphs.
|
||||
- Preserve exact file paths, symbols, commands, error strings, URLs, and identifiers when known.
|
||||
- Put relevant files and symbols inside the section where they matter; do not add extra sections.
|
||||
- Do not mention the summary process or that context was compacted.`
|
||||
|
||||
type Entry = {
|
||||
|
||||
@@ -8,43 +8,16 @@ import { ConfigAgentPlugin } from "@opencode-ai/core/config/plugin/agent"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate"
|
||||
import { tmpdir } from "../fixture/tmpdir"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { agentHost, host } from "../plugin/host"
|
||||
|
||||
const it = testEffect(AppNodeBuilder.build(LayerNode.group([AgentV2.node, FSUtil.node, Global.node])))
|
||||
const it = testEffect(AppNodeBuilder.build(LayerNode.group([AgentV2.node, FSUtil.node])))
|
||||
const decode = Schema.decodeUnknownSync(Config.Info)
|
||||
|
||||
describe("ConfigAgentPlugin.Plugin", () => {
|
||||
it.effect("matches POSIX paths against home-relative permissions", () =>
|
||||
Effect.gen(function* () {
|
||||
const permissions = yield* loadHomePermissions("/home/test")
|
||||
expect(PermissionV2.evaluate("external_directory", "/home/test/p/opencode/src/*", permissions).effect).toBe(
|
||||
"allow",
|
||||
)
|
||||
expect(PermissionV2.evaluate("external_directory", "/home/test/cache/files/*", permissions).effect).toBe("deny")
|
||||
expect(PermissionV2.evaluate("external_directory", "/some/~/path", permissions).effect).toBe("deny")
|
||||
expect(PermissionV2.evaluate("external_directory", "$HOMELESS/private/*", permissions).effect).toBe("deny")
|
||||
expect(PermissionV2.evaluate("bash", "$HOME/private/key", permissions).effect).toBe("deny")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("matches Windows paths against home-relative permissions", () =>
|
||||
Effect.gen(function* () {
|
||||
const permissions = yield* loadHomePermissions("C:\\Users\\test")
|
||||
expect(
|
||||
PermissionV2.evaluate("external_directory", "C:\\Users\\test\\p\\opencode\\src\\*", permissions).effect,
|
||||
).toBe("allow")
|
||||
expect(PermissionV2.evaluate("external_directory", "C:\\Users\\test\\cache\\files\\*", permissions).effect).toBe(
|
||||
"deny",
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("applies all global permissions before agent-specific permissions", () =>
|
||||
Effect.gen(function* () {
|
||||
const agents = yield* AgentV2.Service
|
||||
@@ -299,51 +272,3 @@ Use native v2 fields.`,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
function loadHomePermissions(home: string) {
|
||||
return Effect.gen(function* () {
|
||||
const agents = yield* AgentV2.Service
|
||||
const build = AgentV2.ID.make("build")
|
||||
yield* agents.transform((editor) => editor.update(build, () => {}))
|
||||
const config = Config.Service.of({
|
||||
entries: () =>
|
||||
Effect.succeed([
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
info: decode(
|
||||
ConfigMigrateV1.migrate({
|
||||
permission: {
|
||||
external_directory: {
|
||||
"~/p/**": "allow",
|
||||
"/some/~/path": "deny",
|
||||
"$HOMELESS/**": "deny",
|
||||
},
|
||||
bash: {
|
||||
"$HOME/private/**": "deny",
|
||||
},
|
||||
},
|
||||
agent: {
|
||||
build: {
|
||||
permission: {
|
||||
external_directory: {
|
||||
"$HOME/cache/**": "deny",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
|
||||
yield* ConfigAgentPlugin.Plugin.effect(host({ agent: agentHost(agents) })).pipe(
|
||||
Effect.provideService(Config.Service, config),
|
||||
Effect.provideService(Global.Service, Global.Service.of({ ...Global.make(), home })),
|
||||
)
|
||||
|
||||
const agent = yield* agents.get(build)
|
||||
if (!agent) throw new Error("expected configured build agent")
|
||||
return agent.permissions
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
||||
|
||||
test("compaction prompt preserves detailed work state and relevant files", () => {
|
||||
const prompt = SessionCompaction.buildPrompt({ context: ["conversation history"] })
|
||||
|
||||
expect(prompt).toContain("## Work State\n### Completed")
|
||||
expect(prompt).toContain("### Active")
|
||||
expect(prompt).toContain("### Blocked")
|
||||
expect(prompt).toContain("## Relevant Files")
|
||||
})
|
||||
|
||||
test("compaction describes tool media without embedding base64", () => {
|
||||
const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
|
||||
const serialized = SessionCompaction.serializeToolContent([
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"name": "@opencode-ai/llm",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -6,7 +6,6 @@ const patterns = [
|
||||
/input is too long for requested model/i,
|
||||
/exceeds the context window/i,
|
||||
/input token count.*exceeds the maximum/i,
|
||||
/tokens in request more than max tokens allowed/i,
|
||||
/maximum prompt length is \d+/i,
|
||||
/reduce the length of the messages/i,
|
||||
/maximum context length is \d+ tokens/i,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { isContextOverflow } from "../src"
|
||||
|
||||
describe("provider error classification", () => {
|
||||
test("classifies Z.AI GLM token limit messages as context overflow", () => {
|
||||
expect(isContextOverflow("tokens in request more than max tokens allowed")).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1448,7 +1448,6 @@ describe("session.message-v2.fromError", () => {
|
||||
"prompt is too long: 213462 tokens > 200000 maximum",
|
||||
"Your input exceeds the context window of this model",
|
||||
"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)",
|
||||
"tokens in request more than max tokens allowed",
|
||||
"Please reduce the length of the messages or completion",
|
||||
"400 status code (no body)",
|
||||
"413 status code (no body)",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -218,22 +218,17 @@ const en = {
|
||||
"model.noUsersTitle": "No user data",
|
||||
"model.noUsersDescription": "No user-bearing rows landed in the current window.",
|
||||
"model.dailyUserChart": "Daily unique user chart",
|
||||
"model.efficiencyDescription": "Cost, cache behavior, and average session shape.",
|
||||
"model.efficiencyDescription": "Cost, cache behavior, and average OpenCode Go session shape.",
|
||||
"model.noEfficiencyTitle": "No efficiency data",
|
||||
"model.noEfficiencyDescription": "Efficiency data appears after usage lands.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.cost": "Cost",
|
||||
"model.totalSpend": "total spend",
|
||||
"model.costPerMillion": "Cost / 1M",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.inputOutput": "input / output",
|
||||
"model.observedTokens": "observed all tokens",
|
||||
"model.costSession": "Cost / Session",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.average": "average",
|
||||
"model.tokensSession": "Tokens / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cacheRatio": "Cache Ratio",
|
||||
"model.inputTokens": "input tokens",
|
||||
"model.geoDescription": "OpenCode Go model tokens used by country.",
|
||||
|
||||
@@ -202,11 +202,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "التكلفة، وسلوك التخزين المؤقت، ومتوسط شكل جلسة OpenCode Go.",
|
||||
"model.noEfficiencyTitle": "لا توجد بيانات كفاءة",
|
||||
"model.noEfficiencyDescription": "تظهر بيانات الكفاءة بعد وصول الاستخدام.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "التكلفة",
|
||||
"model.totalSpend": "إجمالي الإنفاق",
|
||||
"model.costPerMillion": "التكلفة / 1M",
|
||||
|
||||
@@ -204,11 +204,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Custo, comportamento de cache e formato médio da sessão do OpenCode Go.",
|
||||
"model.noEfficiencyTitle": "Sem dados de eficiência",
|
||||
"model.noEfficiencyDescription": "Os dados de eficiência aparecem depois que o uso chega.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Custo",
|
||||
"model.totalSpend": "gasto total",
|
||||
"model.costPerMillion": "Custo / 1M",
|
||||
|
||||
@@ -203,11 +203,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Omkostning, cacheadfærd og gennemsnitlig OpenCode Go-sessionsform.",
|
||||
"model.noEfficiencyTitle": "Ingen effektivitetsdata",
|
||||
"model.noEfficiencyDescription": "Effektivitetsdata vises, når brug lander.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Omkostning",
|
||||
"model.totalSpend": "samlet forbrug",
|
||||
"model.costPerMillion": "Omkostning / 1 mio.",
|
||||
|
||||
@@ -204,11 +204,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Kosten, Cache-Verhalten und durchschnittliche OpenCode Go-Sitzungsform.",
|
||||
"model.noEfficiencyTitle": "Keine Effizienzdaten",
|
||||
"model.noEfficiencyDescription": "Effizienzdaten erscheinen, nachdem Nutzung eingegangen ist.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Kosten",
|
||||
"model.totalSpend": "Gesamtausgaben",
|
||||
"model.costPerMillion": "Kosten / 1 Mio.",
|
||||
|
||||
@@ -203,11 +203,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Coste, comportamiento de caché y forma media de sesión de OpenCode Go.",
|
||||
"model.noEfficiencyTitle": "Sin datos de eficiencia",
|
||||
"model.noEfficiencyDescription": "Los datos de eficiencia aparecen después de que llegue uso.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Coste",
|
||||
"model.totalSpend": "gasto total",
|
||||
"model.costPerMillion": "Coste / 1 M",
|
||||
|
||||
@@ -205,11 +205,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Coût, comportement du cache et forme moyenne des sessions OpenCode Go.",
|
||||
"model.noEfficiencyTitle": "Aucune donnée d'efficacité",
|
||||
"model.noEfficiencyDescription": "Les données d'efficacité apparaissent après l'arrivée de l'utilisation.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Coût",
|
||||
"model.totalSpend": "dépense totale",
|
||||
"model.costPerMillion": "Coût / 1 M",
|
||||
|
||||
@@ -204,11 +204,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Costi, comportamento della cache e forma media della sessione OpenCode Go.",
|
||||
"model.noEfficiencyTitle": "Nessun dato di efficienza",
|
||||
"model.noEfficiencyDescription": "I dati di efficienza appaiono dopo l'arrivo dell'utilizzo.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Costo",
|
||||
"model.totalSpend": "spesa totale",
|
||||
"model.costPerMillion": "Costo / 1 M",
|
||||
|
||||
@@ -205,11 +205,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "コスト、キャッシュ動作、平均的なOpenCode Goセッションの形。",
|
||||
"model.noEfficiencyTitle": "効率データがありません",
|
||||
"model.noEfficiencyDescription": "使用量が届くと効率データが表示されます。",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "コスト",
|
||||
"model.totalSpend": "総支出",
|
||||
"model.costPerMillion": "100万あたりのコスト",
|
||||
|
||||
@@ -205,11 +205,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "비용, 캐시 동작, 평균 OpenCode Go 세션 형태입니다.",
|
||||
"model.noEfficiencyTitle": "효율 데이터 없음",
|
||||
"model.noEfficiencyDescription": "사용량이 들어오면 효율 데이터가 표시됩니다.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "비용",
|
||||
"model.totalSpend": "총 지출",
|
||||
"model.costPerMillion": "100만당 비용",
|
||||
|
||||
@@ -203,11 +203,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Kostnad, cacheatferd og gjennomsnittlig OpenCode Go-øktform.",
|
||||
"model.noEfficiencyTitle": "Ingen effektivitetsdata",
|
||||
"model.noEfficiencyDescription": "Effektivitetsdata vises etter at bruk lander.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Kostnad",
|
||||
"model.totalSpend": "totalt forbruk",
|
||||
"model.costPerMillion": "Kostnad / 1 mill.",
|
||||
|
||||
@@ -202,11 +202,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Koszt, zachowanie cache i średni kształt sesji OpenCode Go.",
|
||||
"model.noEfficiencyTitle": "Brak danych wydajności",
|
||||
"model.noEfficiencyDescription": "Dane wydajności pojawią się po nadejściu użycia.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Koszt",
|
||||
"model.totalSpend": "łączne wydatki",
|
||||
"model.costPerMillion": "Koszt / 1 mln",
|
||||
|
||||
@@ -204,11 +204,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Стоимость, поведение кэша и средняя форма сеанса OpenCode Go.",
|
||||
"model.noEfficiencyTitle": "Нет данных эффективности",
|
||||
"model.noEfficiencyDescription": "Данные эффективности появятся после использования.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Стоимость",
|
||||
"model.totalSpend": "общие расходы",
|
||||
"model.costPerMillion": "Стоимость / 1 млн",
|
||||
|
||||
@@ -204,11 +204,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "ต้นทุน พฤติกรรมแคช และรูปแบบเซสชัน OpenCode Go โดยเฉลี่ย",
|
||||
"model.noEfficiencyTitle": "ไม่มีข้อมูลประสิทธิภาพ",
|
||||
"model.noEfficiencyDescription": "ข้อมูลประสิทธิภาพจะแสดงหลังจากมีการใช้งานเข้ามา",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "ต้นทุน",
|
||||
"model.totalSpend": "ยอดใช้จ่ายรวม",
|
||||
"model.costPerMillion": "ต้นทุน / 1 ล้าน",
|
||||
|
||||
@@ -204,11 +204,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Maliyet, önbellek davranışı ve ortalama OpenCode Go oturum yapısı.",
|
||||
"model.noEfficiencyTitle": "Verimlilik verisi yok",
|
||||
"model.noEfficiencyDescription": "Verimlilik verileri kullanım geldikten sonra görünür.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Maliyet",
|
||||
"model.totalSpend": "toplam harcama",
|
||||
"model.costPerMillion": "Maliyet / 1M",
|
||||
|
||||
@@ -204,11 +204,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "Вартість, поведінка кешу та середня форма сеансу OpenCode Go.",
|
||||
"model.noEfficiencyTitle": "Немає даних ефективності",
|
||||
"model.noEfficiencyDescription": "Дані ефективності з'являться після використання.",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "Вартість",
|
||||
"model.totalSpend": "загальні витрати",
|
||||
"model.costPerMillion": "Вартість / 1 млн",
|
||||
|
||||
@@ -203,11 +203,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "成本、缓存行为和平均 OpenCode Go 会话形态。",
|
||||
"model.noEfficiencyTitle": "无效率数据",
|
||||
"model.noEfficiencyDescription": "使用量到达后会显示效率数据。",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "成本",
|
||||
"model.totalSpend": "总支出",
|
||||
"model.costPerMillion": "每 100 万成本",
|
||||
|
||||
@@ -203,11 +203,6 @@ export const dict = {
|
||||
"model.efficiencyDescription": "成本、快取行為和平均 OpenCode Go 工作階段形態。",
|
||||
"model.noEfficiencyTitle": "無效率數據",
|
||||
"model.noEfficiencyDescription": "使用量到達後會顯示效率數據。",
|
||||
"model.totalSpendLabel": "Total spend",
|
||||
"model.costInput": "Cost input",
|
||||
"model.costOutput": "Cost output",
|
||||
"model.averageCostSession": "Average cost / Session",
|
||||
"model.averageTokensSession": "Average tokens / session",
|
||||
"model.cost": "成本",
|
||||
"model.totalSpend": "總支出",
|
||||
"model.costPerMillion": "每 100 萬成本",
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
formatCatalogLabName,
|
||||
getModelCatalog,
|
||||
type ModelCatalog,
|
||||
type ModelCatalogCost,
|
||||
type ModelCatalogEntry,
|
||||
} from "../model-catalog"
|
||||
import { SectionHeading } from "../section-heading"
|
||||
@@ -880,32 +881,35 @@ function ModelEfficiencySection(props: { data: StatsModelData | null; catalog: M
|
||||
}
|
||||
>
|
||||
{(data) => (
|
||||
<>
|
||||
<div data-slot="model-efficiency-pattern" aria-hidden="true" />
|
||||
<div data-component="model-efficiency-grid">
|
||||
<MetricCard label={i18n.t("model.totalSpendLabel")} value={formatMoney(data().totals.cost)} />
|
||||
<MetricCard
|
||||
label={i18n.t("model.costInput")}
|
||||
value={formatCatalogUnitPrice(props.catalog?.cost?.input)}
|
||||
/>
|
||||
<MetricCard
|
||||
label={i18n.t("model.costOutput")}
|
||||
value={formatCatalogUnitPrice(props.catalog?.cost?.output)}
|
||||
/>
|
||||
<MetricCard
|
||||
label={i18n.t("model.averageCostSession")}
|
||||
value={formatSessionCost(data().totals.costPerSession)}
|
||||
/>
|
||||
<MetricCard
|
||||
label={i18n.t("model.averageTokensSession")}
|
||||
value={formatTokens(data().totals.tokensPerSession)}
|
||||
/>
|
||||
<MetricCard
|
||||
label={i18n.t("model.cacheRatio")}
|
||||
value={`${formatPercent(data().totals.cacheRatio)} ${i18n.t("model.inputTokens")}`}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div data-component="model-metric-grid" data-variant="dense">
|
||||
<MetricCard
|
||||
label={i18n.t("model.cost")}
|
||||
value={formatMoney(data().totals.cost)}
|
||||
detail={i18n.t("model.totalSpend")}
|
||||
/>
|
||||
<MetricCard
|
||||
label={i18n.t("model.costPerMillion")}
|
||||
value={
|
||||
props.catalog?.cost ? formatCatalogPrice(props.catalog.cost) : formatMoney(data().totals.costPerMillion)
|
||||
}
|
||||
detail={props.catalog?.cost ? i18n.t("model.inputOutput") : i18n.t("model.observedTokens")}
|
||||
/>
|
||||
<MetricCard
|
||||
label={i18n.t("model.costSession")}
|
||||
value={formatSessionCost(data().totals.costPerSession)}
|
||||
detail={i18n.t("model.average")}
|
||||
/>
|
||||
<MetricCard
|
||||
label={i18n.t("model.tokensSession")}
|
||||
value={formatTokens(data().totals.tokensPerSession)}
|
||||
detail={i18n.t("model.average")}
|
||||
/>
|
||||
<MetricCard
|
||||
label={i18n.t("model.cacheRatio")}
|
||||
value={formatPercent(data().totals.cacheRatio)}
|
||||
detail={i18n.t("model.inputTokens")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</section>
|
||||
@@ -1125,12 +1129,12 @@ function ModelPeersSection(props: { data: StatsModelData | null }) {
|
||||
)
|
||||
}
|
||||
|
||||
function MetricCard(props: { label: string; value: string; detail?: string; state?: "positive" | "negative" }) {
|
||||
function MetricCard(props: { label: string; value: string; detail: string; state?: "positive" | "negative" }) {
|
||||
return (
|
||||
<article data-component="model-metric" data-state={props.state}>
|
||||
<span>{props.label}</span>
|
||||
<strong>{props.value}</strong>
|
||||
<Show when={props.detail}>{(detail) => <p>{detail()}</p>}</Show>
|
||||
<p>{props.detail}</p>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
@@ -1143,16 +1147,10 @@ function PeerRow(props: { peer: ModelPeerEntry; active: boolean }) {
|
||||
href={language.route(`${import.meta.env.BASE_URL}${providerSlug(props.peer.provider)}/${props.peer.slug}`)}
|
||||
data-active={props.active ? "true" : undefined}
|
||||
>
|
||||
<span data-slot="model-peer-rank" aria-label={props.active ? `Rank ${props.peer.rank}` : undefined}>
|
||||
<Show when={!props.active}>{String(props.peer.rank).padStart(2, "0")}</Show>
|
||||
</span>
|
||||
<span data-slot="model-peer-avatar">
|
||||
<ProviderIcon aria-hidden="true" id={getProviderIconId(props.peer.author)} />
|
||||
</span>
|
||||
<span data-slot="model-peer-copy">
|
||||
<strong>{props.peer.model}</strong>
|
||||
<em>{props.peer.author}</em>
|
||||
</span>
|
||||
<span>{String(props.peer.rank).padStart(2, "0")}</span>
|
||||
<ProviderIcon aria-hidden="true" id={getProviderIconId(props.peer.author)} />
|
||||
<strong>{props.peer.model}</strong>
|
||||
<em>{props.peer.author}</em>
|
||||
<b>{formatTokens(props.peer.tokens)}</b>
|
||||
</a>
|
||||
</li>
|
||||
@@ -1519,9 +1517,8 @@ function formatMoney(value: number) {
|
||||
return `$${value.toFixed(value >= 10 ? 0 : 2)}`
|
||||
}
|
||||
|
||||
function formatCatalogUnitPrice(value: number | undefined) {
|
||||
if (value === undefined) return "-"
|
||||
return `${formatModelPrice(value)} / 1M`
|
||||
function formatCatalogPrice(value: ModelCatalogCost) {
|
||||
return `${formatModelPrice(value.input)} / ${formatModelPrice(value.output)}`
|
||||
}
|
||||
|
||||
function formatModelPrice(value: number) {
|
||||
|
||||
@@ -3913,50 +3913,6 @@
|
||||
color: #b82d35;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-efficiency-pattern"] {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
margin-bottom: 40px;
|
||||
overflow: hidden;
|
||||
background: color-mix(in srgb, var(--stats-text) 10%, transparent);
|
||||
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
|
||||
mask-position: center top;
|
||||
mask-repeat: repeat;
|
||||
mask-size: 6px 6px;
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
|
||||
-webkit-mask-position: center top;
|
||||
-webkit-mask-repeat: repeat;
|
||||
-webkit-mask-size: 6px 6px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-efficiency-grid"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
gap: 0;
|
||||
height: 120px;
|
||||
min-height: 120px;
|
||||
padding: 16px;
|
||||
background: var(--stats-layer);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] span {
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] strong {
|
||||
margin-top: 32px;
|
||||
font-size: 28px;
|
||||
line-height: 42px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-section="model-panel"][data-variant="lab-models"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -5159,10 +5115,7 @@
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: minmax(0, 1fr);
|
||||
grid-template-rows: repeat(5, 64px);
|
||||
gap: 12px 40px;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
@@ -5170,12 +5123,11 @@
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] a {
|
||||
display: grid;
|
||||
grid-template-columns: 32px 32px minmax(0, 1fr) minmax(64px, auto);
|
||||
gap: 16px;
|
||||
grid-template-columns: 38px 24px minmax(0, 1fr) minmax(96px, auto) minmax(80px, auto);
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
height: 64px;
|
||||
padding: 15px 16px;
|
||||
min-height: 44px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--stats-line);
|
||||
background: var(--stats-layer);
|
||||
color: var(--stats-text);
|
||||
@@ -5184,56 +5136,25 @@
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] a:hover,
|
||||
[data-page="stats"] [data-component="model-peer-list"] a[data-active="true"] {
|
||||
border-color: var(--stats-text);
|
||||
border-color: var(--stats-line-strong);
|
||||
background: var(--stats-bg);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-peer-rank"],
|
||||
[data-page="stats"] [data-component="model-peer-list"] span,
|
||||
[data-page="stats"] [data-component="model-peer-list"] em,
|
||||
[data-page="stats"] [data-component="model-peer-list"] b {
|
||||
color: var(--stats-muted);
|
||||
font-size: 13px;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-peer-rank"] {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] a[data-active="true"] [data-slot="model-peer-rank"]::before {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
content: "";
|
||||
background: var(--stats-text);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-peer-avatar"] {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: var(--stats-muted);
|
||||
background: var(--stats-bg);
|
||||
box-shadow: inset 0 0 0 1px var(--stats-line);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-peer-avatar"] svg {
|
||||
[data-page="stats"] [data-component="model-peer-list"] svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-peer-copy"] {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
color: var(--stats-muted);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] strong {
|
||||
@@ -5242,16 +5163,11 @@
|
||||
color: var(--stats-text);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] b {
|
||||
justify-self: end;
|
||||
color: var(--stats-text);
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"],
|
||||
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) {
|
||||
color-scheme: dark;
|
||||
@@ -5783,18 +5699,16 @@
|
||||
|
||||
[data-page="stats"] [data-component="model-metric-grid"],
|
||||
[data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
|
||||
[data-page="stats"] [data-component="model-efficiency-grid"],
|
||||
[data-page="stats"] [data-component="lab-model-grid"] {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] a {
|
||||
grid-template-columns: 32px 32px minmax(0, 1fr) minmax(64px, auto);
|
||||
gap: 12px;
|
||||
grid-template-columns: 32px 22px minmax(0, 1fr) minmax(72px, auto);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] em {
|
||||
display: block;
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="chart-footer"] {
|
||||
@@ -6211,7 +6125,6 @@
|
||||
|
||||
[data-page="stats"] [data-component="model-metric-grid"],
|
||||
[data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
|
||||
[data-page="stats"] [data-component="model-efficiency-grid"],
|
||||
[data-page="stats"] [data-component="lab-model-grid"] {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -6220,15 +6133,6 @@
|
||||
min-height: 112px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] {
|
||||
height: 112px;
|
||||
min-height: 112px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] strong {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"] {
|
||||
--model-usage-mobile-bar-width: 16px;
|
||||
--model-usage-mobile-edge-space: 48px;
|
||||
@@ -6345,20 +6249,12 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] {
|
||||
grid-auto-flow: row;
|
||||
grid-auto-columns: auto;
|
||||
grid-template-rows: none;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] a {
|
||||
grid-template-columns: 28px 32px minmax(0, 1fr) minmax(56px, auto);
|
||||
gap: 12px;
|
||||
grid-template-columns: 30px minmax(0, 1fr) minmax(64px, auto);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-peer-rank"] {
|
||||
width: 28px;
|
||||
[data-page="stats"] [data-component="model-peer-list"] svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -646,8 +646,8 @@ function buildModelTokenMix(aggregate: ModelAggregate): ModelMixEntry[] {
|
||||
}
|
||||
|
||||
function buildModelPeers(peers: ModelAggregate[], rank: number, totalTokens: number): ModelPeerEntry[] {
|
||||
const start = Math.max(0, Math.min(rank - 5, Math.max(peers.length - 10, 0)))
|
||||
return peers.slice(start, start + 10).map((item, index) => ({
|
||||
const start = Math.max(0, Math.min(rank - 4, Math.max(peers.length - 7, 0)))
|
||||
return peers.slice(start, start + 7).map((item, index) => ({
|
||||
model: item.model,
|
||||
provider: item.provider,
|
||||
author: formatProvider(item.provider),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.14",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user