diff options
| author | Adam <[email protected]> | 2025-12-22 19:38:50 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-22 19:39:00 -0600 |
| commit | 794fe8f381c846f5241800363023d892c12cf495 (patch) | |
| tree | bff98689edfa635a2a9f39cb4ea61639b97f5b2d /packages/desktop/src/utils | |
| parent | a4eebf9f08262f6bf63017710e2e6d9672ec6708 (diff) | |
| download | opencode-794fe8f381c846f5241800363023d892c12cf495.tar.gz opencode-794fe8f381c846f5241800363023d892c12cf495.zip | |
chore: rename packages/desktop -> packages/app
Diffstat (limited to 'packages/desktop/src/utils')
| -rw-r--r-- | packages/desktop/src/utils/dom.ts | 51 | ||||
| -rw-r--r-- | packages/desktop/src/utils/id.ts | 99 | ||||
| -rw-r--r-- | packages/desktop/src/utils/index.ts | 1 | ||||
| -rw-r--r-- | packages/desktop/src/utils/persist.ts | 26 | ||||
| -rw-r--r-- | packages/desktop/src/utils/prompt.ts | 47 | ||||
| -rw-r--r-- | packages/desktop/src/utils/solid-dnd.tsx | 55 | ||||
| -rw-r--r-- | packages/desktop/src/utils/speech.ts | 302 |
7 files changed, 0 insertions, 581 deletions
diff --git a/packages/desktop/src/utils/dom.ts b/packages/desktop/src/utils/dom.ts deleted file mode 100644 index 4f3724c7c..000000000 --- a/packages/desktop/src/utils/dom.ts +++ /dev/null @@ -1,51 +0,0 @@ -export function getCharacterOffsetInLine(lineElement: Element, targetNode: Node, offset: number): number { - const r = document.createRange() - r.selectNodeContents(lineElement) - r.setEnd(targetNode, offset) - return r.toString().length -} - -export function getNodeOffsetInLine(lineElement: Element, charIndex: number): { node: Node; offset: number } | null { - const walker = document.createTreeWalker(lineElement, NodeFilter.SHOW_TEXT, null) - let remaining = Math.max(0, charIndex) - let lastText: Node | null = null - let lastLen = 0 - let node: Node | null - while ((node = walker.nextNode())) { - const len = node.textContent?.length || 0 - lastText = node - lastLen = len - if (remaining <= len) return { node, offset: remaining } - remaining -= len - } - if (lastText) return { node: lastText, offset: lastLen } - if (lineElement.firstChild) return { node: lineElement.firstChild, offset: 0 } - return null -} - -export function getSelectionInContainer( - container: HTMLElement, -): { sl: number; sch: number; el: number; ech: number } | null { - const s = window.getSelection() - if (!s || s.rangeCount === 0) return null - const r = s.getRangeAt(0) - const sc = r.startContainer - const ec = r.endContainer - const getLineElement = (n: Node) => - (n.nodeType === Node.TEXT_NODE ? (n.parentElement as Element) : (n as Element))?.closest(".line") - const sle = getLineElement(sc) - const ele = getLineElement(ec) - if (!sle || !ele) return null - if (!container.contains(sle as Node) || !container.contains(ele as Node)) return null - const cc = container.querySelector("code") as HTMLElement | null - if (!cc) return null - const lines = Array.from(cc.querySelectorAll(".line")) - const sli = lines.indexOf(sle as Element) - const eli = lines.indexOf(ele as Element) - if (sli === -1 || eli === -1) return null - const sl = sli + 1 - const el = eli + 1 - const sch = getCharacterOffsetInLine(sle as Element, sc, r.startOffset) - const ech = getCharacterOffsetInLine(ele as Element, ec, r.endOffset) - return { sl, sch, el, ech } -} diff --git a/packages/desktop/src/utils/id.ts b/packages/desktop/src/utils/id.ts deleted file mode 100644 index fa27cf4c5..000000000 --- a/packages/desktop/src/utils/id.ts +++ /dev/null @@ -1,99 +0,0 @@ -import z from "zod" - -const prefixes = { - session: "ses", - message: "msg", - permission: "per", - user: "usr", - part: "prt", - pty: "pty", -} as const - -const LENGTH = 26 -let lastTimestamp = 0 -let counter = 0 - -type Prefix = keyof typeof prefixes -export namespace Identifier { - export function schema(prefix: Prefix) { - return z.string().startsWith(prefixes[prefix]) - } - - export function ascending(prefix: Prefix, given?: string) { - return generateID(prefix, false, given) - } - - export function descending(prefix: Prefix, given?: string) { - return generateID(prefix, true, given) - } -} - -function generateID(prefix: Prefix, descending: boolean, given?: string): string { - if (!given) { - return create(prefix, descending) - } - - if (!given.startsWith(prefixes[prefix])) { - throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`) - } - - return given -} - -function create(prefix: Prefix, descending: boolean, timestamp?: number): string { - const currentTimestamp = timestamp ?? Date.now() - - if (currentTimestamp !== lastTimestamp) { - lastTimestamp = currentTimestamp - counter = 0 - } - - counter += 1 - - let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter) - - if (descending) { - now = ~now - } - - const timeBytes = new Uint8Array(6) - for (let i = 0; i < 6; i += 1) { - timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff)) - } - - return prefixes[prefix] + "_" + bytesToHex(timeBytes) + randomBase62(LENGTH - 12) -} - -function bytesToHex(bytes: Uint8Array): string { - let hex = "" - for (let i = 0; i < bytes.length; i += 1) { - hex += bytes[i].toString(16).padStart(2, "0") - } - return hex -} - -function randomBase62(length: number): string { - const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - const bytes = getRandomBytes(length) - let result = "" - for (let i = 0; i < length; i += 1) { - result += chars[bytes[i] % 62] - } - return result -} - -function getRandomBytes(length: number): Uint8Array { - const bytes = new Uint8Array(length) - const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : undefined - - if (cryptoObj && typeof cryptoObj.getRandomValues === "function") { - cryptoObj.getRandomValues(bytes) - return bytes - } - - for (let i = 0; i < length; i += 1) { - bytes[i] = Math.floor(Math.random() * 256) - } - - return bytes -} diff --git a/packages/desktop/src/utils/index.ts b/packages/desktop/src/utils/index.ts deleted file mode 100644 index d87053269..000000000 --- a/packages/desktop/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./dom" diff --git a/packages/desktop/src/utils/persist.ts b/packages/desktop/src/utils/persist.ts deleted file mode 100644 index 12b334f9f..000000000 --- a/packages/desktop/src/utils/persist.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { usePlatform } from "@/context/platform" -import { makePersisted } from "@solid-primitives/storage" -import { createResource, type Accessor } from "solid-js" -import type { SetStoreFunction, Store } from "solid-js/store" - -type InitType = Promise<string> | string | null -type PersistedWithReady<T> = [Store<T>, SetStoreFunction<T>, InitType, Accessor<boolean>] - -export function persisted<T>(key: string, store: [Store<T>, SetStoreFunction<T>]): PersistedWithReady<T> { - const platform = usePlatform() - const [state, setState, init] = makePersisted(store, { name: key, storage: platform.storage?.() ?? localStorage }) - - // Create a resource that resolves when the store is initialized - // This integrates with Suspense and provides a ready signal - const isAsync = init instanceof Promise - const [ready] = createResource( - () => init, - async (initValue) => { - if (initValue instanceof Promise) await initValue - return true - }, - { initialValue: !isAsync }, - ) - - return [state, setState, init, () => ready() === true] -} diff --git a/packages/desktop/src/utils/prompt.ts b/packages/desktop/src/utils/prompt.ts deleted file mode 100644 index 45c5ce1f3..000000000 --- a/packages/desktop/src/utils/prompt.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Part, TextPart, FilePart } from "@opencode-ai/sdk/v2" -import type { Prompt, FileAttachmentPart } from "@/context/prompt" - -/** - * Extract prompt content from message parts for restoring into the prompt input. - * This is used by undo to restore the original user prompt. - */ -export function extractPromptFromParts(parts: Part[]): Prompt { - const result: Prompt = [] - let position = 0 - - for (const part of parts) { - if (part.type === "text") { - const textPart = part as TextPart - if (!textPart.synthetic && textPart.text) { - result.push({ - type: "text", - content: textPart.text, - start: position, - end: position + textPart.text.length, - }) - position += textPart.text.length - } - } else if (part.type === "file") { - const filePart = part as FilePart - if (filePart.source?.type === "file") { - const path = filePart.source.path - const content = "@" + path - const attachment: FileAttachmentPart = { - type: "file", - path, - content, - start: position, - end: position + content.length, - } - result.push(attachment) - position += content.length - } - } - } - - if (result.length === 0) { - result.push({ type: "text", content: "", start: 0, end: 0 }) - } - - return result -} diff --git a/packages/desktop/src/utils/solid-dnd.tsx b/packages/desktop/src/utils/solid-dnd.tsx deleted file mode 100644 index a634be4b4..000000000 --- a/packages/desktop/src/utils/solid-dnd.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useDragDropContext } from "@thisbeyond/solid-dnd" -import { JSXElement } from "solid-js" -import type { Transformer } from "@thisbeyond/solid-dnd" - -export const getDraggableId = (event: unknown): string | undefined => { - if (typeof event !== "object" || event === null) return undefined - if (!("draggable" in event)) return undefined - const draggable = (event as { draggable?: { id?: unknown } }).draggable - if (!draggable) return undefined - return typeof draggable.id === "string" ? draggable.id : undefined -} - -export const ConstrainDragXAxis = (): JSXElement => { - const context = useDragDropContext() - if (!context) return <></> - const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context - const transformer: Transformer = { - id: "constrain-x-axis", - order: 100, - callback: (transform) => ({ ...transform, x: 0 }), - } - onDragStart((event) => { - const id = getDraggableId(event) - if (!id) return - addTransformer("draggables", id, transformer) - }) - onDragEnd((event) => { - const id = getDraggableId(event) - if (!id) return - removeTransformer("draggables", id, transformer.id) - }) - return <></> -} - -export const ConstrainDragYAxis = (): JSXElement => { - const context = useDragDropContext() - if (!context) return <></> - const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context - const transformer: Transformer = { - id: "constrain-y-axis", - order: 100, - callback: (transform) => ({ ...transform, y: 0 }), - } - onDragStart((event) => { - const id = getDraggableId(event) - if (!id) return - addTransformer("draggables", id, transformer) - }) - onDragEnd((event) => { - const id = getDraggableId(event) - if (!id) return - removeTransformer("draggables", id, transformer.id) - }) - return <></> -} diff --git a/packages/desktop/src/utils/speech.ts b/packages/desktop/src/utils/speech.ts deleted file mode 100644 index 921e0a159..000000000 --- a/packages/desktop/src/utils/speech.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { createSignal, onCleanup } from "solid-js" - -// Minimal types to avoid relying on non-standard DOM typings -type RecognitionResult = { - 0: { transcript: string } - isFinal: boolean -} - -type RecognitionEvent = { - results: RecognitionResult[] - resultIndex: number -} - -interface Recognition { - continuous: boolean - interimResults: boolean - lang: string - start: () => void - stop: () => void - onresult: ((e: RecognitionEvent) => void) | null - onerror: ((e: { error: string }) => void) | null - onend: (() => void) | null - onstart: (() => void) | null -} - -const COMMIT_DELAY = 250 - -const appendSegment = (base: string, addition: string) => { - const trimmed = addition.trim() - if (!trimmed) return base - if (!base) return trimmed - const needsSpace = /\S$/.test(base) && !/^[,.;!?]/.test(trimmed) - return `${base}${needsSpace ? " " : ""}${trimmed}` -} - -const extractSuffix = (committed: string, hypothesis: string) => { - const cleanHypothesis = hypothesis.trim() - if (!cleanHypothesis) return "" - const baseTokens = committed.trim() ? committed.trim().split(/\s+/) : [] - const hypothesisTokens = cleanHypothesis.split(/\s+/) - let index = 0 - while ( - index < baseTokens.length && - index < hypothesisTokens.length && - baseTokens[index] === hypothesisTokens[index] - ) { - index += 1 - } - if (index < baseTokens.length) return "" - return hypothesisTokens.slice(index).join(" ") -} - -export function createSpeechRecognition(opts?: { - lang?: string - onFinal?: (text: string) => void - onInterim?: (text: string) => void -}) { - const hasSupport = - typeof window !== "undefined" && - Boolean((window as any).webkitSpeechRecognition || (window as any).SpeechRecognition) - - const [isRecording, setIsRecording] = createSignal(false) - const [committed, setCommitted] = createSignal("") - const [interim, setInterim] = createSignal("") - - let recognition: Recognition | undefined - let shouldContinue = false - let committedText = "" - let sessionCommitted = "" - let pendingHypothesis = "" - let lastInterimSuffix = "" - let shrinkCandidate: string | undefined - let commitTimer: number | undefined - - const cancelPendingCommit = () => { - if (commitTimer === undefined) return - clearTimeout(commitTimer) - commitTimer = undefined - } - - const commitSegment = (segment: string) => { - const nextCommitted = appendSegment(committedText, segment) - if (nextCommitted === committedText) return - committedText = nextCommitted - setCommitted(committedText) - if (opts?.onFinal) opts.onFinal(segment.trim()) - } - - const promotePending = () => { - if (!pendingHypothesis) return - const suffix = extractSuffix(sessionCommitted, pendingHypothesis) - if (!suffix) { - pendingHypothesis = "" - return - } - sessionCommitted = appendSegment(sessionCommitted, suffix) - commitSegment(suffix) - pendingHypothesis = "" - lastInterimSuffix = "" - shrinkCandidate = undefined - setInterim("") - if (opts?.onInterim) opts.onInterim("") - } - - const applyInterim = (suffix: string, hypothesis: string) => { - cancelPendingCommit() - pendingHypothesis = hypothesis - lastInterimSuffix = suffix - shrinkCandidate = undefined - setInterim(suffix) - if (opts?.onInterim) { - opts.onInterim(suffix ? appendSegment(committedText, suffix) : "") - } - if (!suffix) return - const snapshot = hypothesis - commitTimer = window.setTimeout(() => { - if (pendingHypothesis !== snapshot) return - const currentSuffix = extractSuffix(sessionCommitted, pendingHypothesis) - if (!currentSuffix) return - sessionCommitted = appendSegment(sessionCommitted, currentSuffix) - commitSegment(currentSuffix) - pendingHypothesis = "" - lastInterimSuffix = "" - shrinkCandidate = undefined - setInterim("") - if (opts?.onInterim) opts.onInterim("") - }, COMMIT_DELAY) - } - - if (hasSupport) { - const Ctor: new () => Recognition = (window as any).webkitSpeechRecognition || (window as any).SpeechRecognition - - recognition = new Ctor() - recognition.continuous = false - recognition.interimResults = true - recognition.lang = opts?.lang || (typeof navigator !== "undefined" ? navigator.language : "en-US") - - recognition.onresult = (event: RecognitionEvent) => { - if (!event.results.length) return - - let aggregatedFinal = "" - let latestHypothesis = "" - - for (let i = 0; i < event.results.length; i += 1) { - const result = event.results[i] - const transcript = (result[0]?.transcript || "").trim() - if (!transcript) continue - if (result.isFinal) { - aggregatedFinal = appendSegment(aggregatedFinal, transcript) - } else { - latestHypothesis = transcript - } - } - - if (aggregatedFinal) { - cancelPendingCommit() - const finalSuffix = extractSuffix(sessionCommitted, aggregatedFinal) - if (finalSuffix) { - sessionCommitted = appendSegment(sessionCommitted, finalSuffix) - commitSegment(finalSuffix) - } - pendingHypothesis = "" - lastInterimSuffix = "" - shrinkCandidate = undefined - setInterim("") - if (opts?.onInterim) opts.onInterim("") - return - } - - cancelPendingCommit() - - if (!latestHypothesis) { - shrinkCandidate = undefined - applyInterim("", "") - return - } - - const suffix = extractSuffix(sessionCommitted, latestHypothesis) - - if (!suffix) { - if (!lastInterimSuffix) { - shrinkCandidate = undefined - applyInterim("", latestHypothesis) - return - } - if (shrinkCandidate === "") { - applyInterim("", latestHypothesis) - return - } - shrinkCandidate = "" - pendingHypothesis = latestHypothesis - return - } - - if (lastInterimSuffix && suffix.length < lastInterimSuffix.length) { - if (shrinkCandidate === suffix) { - applyInterim(suffix, latestHypothesis) - return - } - shrinkCandidate = suffix - pendingHypothesis = latestHypothesis - return - } - - shrinkCandidate = undefined - applyInterim(suffix, latestHypothesis) - } - - recognition.onerror = (e: { error: string }) => { - cancelPendingCommit() - lastInterimSuffix = "" - shrinkCandidate = undefined - if (e.error === "no-speech" && shouldContinue) { - setInterim("") - if (opts?.onInterim) opts.onInterim("") - setTimeout(() => { - try { - recognition?.start() - } catch {} - }, 150) - return - } - shouldContinue = false - setIsRecording(false) - } - - recognition.onstart = () => { - sessionCommitted = "" - pendingHypothesis = "" - cancelPendingCommit() - lastInterimSuffix = "" - shrinkCandidate = undefined - setInterim("") - if (opts?.onInterim) opts.onInterim("") - setIsRecording(true) - } - - recognition.onend = () => { - cancelPendingCommit() - lastInterimSuffix = "" - shrinkCandidate = undefined - setIsRecording(false) - if (shouldContinue) { - setTimeout(() => { - try { - recognition?.start() - } catch {} - }, 150) - } - } - } - - const start = () => { - if (!recognition) return - shouldContinue = true - sessionCommitted = "" - pendingHypothesis = "" - cancelPendingCommit() - lastInterimSuffix = "" - shrinkCandidate = undefined - setInterim("") - try { - recognition.start() - } catch {} - } - - const stop = () => { - if (!recognition) return - shouldContinue = false - promotePending() - cancelPendingCommit() - lastInterimSuffix = "" - shrinkCandidate = undefined - setInterim("") - if (opts?.onInterim) opts.onInterim("") - try { - recognition.stop() - } catch {} - } - - onCleanup(() => { - shouldContinue = false - promotePending() - cancelPendingCommit() - lastInterimSuffix = "" - shrinkCandidate = undefined - setInterim("") - if (opts?.onInterim) opts.onInterim("") - try { - recognition?.stop() - } catch {} - }) - - return { - isSupported: () => hasSupport, - isRecording, - committed, - interim, - start, - stop, - } -} |
