Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s
A professional workspace for AI coding agents with: - Real-time streaming chat with SSE - Session management (fork, clone, tree navigation) - MCP tools browser & metadata panel - Skills browser (global, project, package sources) - Voice input & STT provider configuration - Integrated terminal & file explorer - Model selector with capability metadata - Keyboard-first UI with command palette - 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi) - Tauri 2 desktop app - React 19 + TypeScript + Vite 8 + Tailwind CSS 4 - Pi Agent extension runtime Originally forked from OpenCodeUI, now an independent project. License: GPL-3.0-only
66 lines
1.8 KiB
TypeScript
66 lines
1.8 KiB
TypeScript
function fallbackCopyText(text: string): boolean {
|
|
if (typeof document === 'undefined' || !document.body) {
|
|
return false
|
|
}
|
|
|
|
const textArea = document.createElement('textarea')
|
|
textArea.value = text
|
|
textArea.setAttribute('readonly', '')
|
|
textArea.setAttribute('aria-hidden', 'true')
|
|
textArea.style.position = 'fixed'
|
|
textArea.style.top = '0'
|
|
textArea.style.left = '-9999px'
|
|
textArea.style.opacity = '0'
|
|
textArea.style.pointerEvents = 'none'
|
|
|
|
const selection = document.getSelection()
|
|
const savedRanges = selection
|
|
? Array.from({ length: selection.rangeCount }, (_, index) => selection.getRangeAt(index).cloneRange())
|
|
: []
|
|
const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null
|
|
|
|
document.body.appendChild(textArea)
|
|
textArea.focus()
|
|
textArea.select()
|
|
textArea.setSelectionRange(0, text.length)
|
|
|
|
const copied = typeof document.execCommand === 'function' && document.execCommand('copy')
|
|
|
|
document.body.removeChild(textArea)
|
|
|
|
if (selection) {
|
|
selection.removeAllRanges()
|
|
savedRanges.forEach(range => selection.addRange(range))
|
|
}
|
|
activeElement?.focus()
|
|
|
|
return copied
|
|
}
|
|
|
|
export async function copyTextToClipboard(text: string): Promise<void> {
|
|
let clipboardError: unknown
|
|
|
|
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
|
try {
|
|
await navigator.clipboard.writeText(text)
|
|
return
|
|
} catch (error) {
|
|
clipboardError = error
|
|
}
|
|
}
|
|
|
|
if (fallbackCopyText(text)) {
|
|
return
|
|
}
|
|
|
|
throw clipboardError instanceof Error ? clipboardError : new Error('Failed to copy text to clipboard')
|
|
}
|
|
|
|
export async function readTextFromClipboard(): Promise<string> {
|
|
if (typeof navigator !== 'undefined' && navigator.clipboard?.readText) {
|
|
return navigator.clipboard.readText()
|
|
}
|
|
|
|
throw new Error('Clipboard read API is not available')
|
|
}
|