feat: you can summary screen shot directly
This commit is contained in:
+132
-5
@@ -3,9 +3,7 @@
|
||||
* App shell — switches between two distinct UI modes that share the same
|
||||
* Wails window:
|
||||
*
|
||||
* • "settings" : full settings UI (self-drawn title bar + sidebar + form).
|
||||
* Self-drawn because the window is now Frameless to make
|
||||
* the overlay paint edge-to-edge.
|
||||
* • "settings" : full settings UI (native title bar + sidebar + form).
|
||||
* • "overlay" : Snipaste-style region picker that fills the whole
|
||||
* primary display.
|
||||
*
|
||||
@@ -24,6 +22,7 @@ import {
|
||||
CopyRegionImage,
|
||||
SaveRegionImage,
|
||||
SaveRegionToRemote,
|
||||
SummarizeRegion,
|
||||
CancelRegion,
|
||||
} from '../wailsjs/go/main/App'
|
||||
|
||||
@@ -39,7 +38,7 @@ const mode = ref<Mode>('settings')
|
||||
// `SettingsTab` is hoisted to the App shell so the sidebar (which lives
|
||||
// here) and the inner SettingsView (which renders the matching card) can
|
||||
// share a single source of truth without an event bus.
|
||||
type SettingsTab = 'general' | 's3' | 'ssh'
|
||||
type SettingsTab = 'general' | 's3' | 'ssh' | 'llm'
|
||||
const activeTab = ref<SettingsTab>('general')
|
||||
|
||||
// Sidebar entries are declarative so adding a destination type later is
|
||||
@@ -48,6 +47,7 @@ const sidebarItems: Array<{ id: SettingsTab; label: string }> = [
|
||||
{ id: 'general', label: 'General' },
|
||||
{ id: 's3', label: 'S3' },
|
||||
{ id: 'ssh', label: 'SSH' },
|
||||
{ id: 'llm', label: 'LLM' },
|
||||
]
|
||||
|
||||
interface OverlayPayload {
|
||||
@@ -62,6 +62,14 @@ const capturing = ref(false)
|
||||
const toast = ref<{ kind: 'success' | 'error'; text: string } | null>(null)
|
||||
let toastTimer: number | undefined
|
||||
|
||||
const operationStatus = ref<{
|
||||
operation: string
|
||||
phase: string
|
||||
message: string
|
||||
state: 'running' | 'success' | 'error'
|
||||
} | null>(null)
|
||||
let operationTimer: number | undefined
|
||||
|
||||
function showToast(kind: 'success' | 'error', text: string) {
|
||||
toast.value = { kind, text }
|
||||
window.clearTimeout(toastTimer)
|
||||
@@ -70,6 +78,21 @@ function showToast(kind: 'success' | 'error', text: string) {
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
function updateOperationStatus(payload: {
|
||||
operation: string
|
||||
phase: string
|
||||
message: string
|
||||
state: 'running' | 'success' | 'error'
|
||||
}) {
|
||||
operationStatus.value = payload
|
||||
window.clearTimeout(operationTimer)
|
||||
if (payload.state !== 'running') {
|
||||
operationTimer = window.setTimeout(() => {
|
||||
operationStatus.value = null
|
||||
}, payload.state === 'success' ? 1600 : 3200)
|
||||
}
|
||||
}
|
||||
|
||||
async function retryHotkey() {
|
||||
try {
|
||||
await RetryRegisterHotkey()
|
||||
@@ -168,6 +191,28 @@ async function onOverlaySaveRemote(rect: {
|
||||
}
|
||||
}
|
||||
|
||||
async function onOverlaySummarize(rect: {
|
||||
rect: {
|
||||
x: number
|
||||
y: number
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
annotations: Array<{
|
||||
tool: string
|
||||
color: string
|
||||
points: Array<{ x: number; y: number }>
|
||||
}>
|
||||
}) {
|
||||
mode.value = 'settings'
|
||||
overlayPayload.value = null
|
||||
try {
|
||||
await SummarizeRegion(rect as any)
|
||||
} catch {
|
||||
/* Surfaced via upload:failure */
|
||||
}
|
||||
}
|
||||
|
||||
async function onOverlayCancel() {
|
||||
mode.value = 'settings'
|
||||
overlayPayload.value = null
|
||||
@@ -194,7 +239,12 @@ onMounted(() => {
|
||||
capturing.value = false
|
||||
})
|
||||
EventsOn('upload:success', (url: string) => {
|
||||
showToast('success', `Copied: ${url}`)
|
||||
showToast(
|
||||
'success',
|
||||
url === 'summary copied to clipboard'
|
||||
? 'Summary copied to clipboard'
|
||||
: `Copied: ${url}`
|
||||
)
|
||||
})
|
||||
EventsOn('upload:failure', (reason: string) => {
|
||||
showToast('error', reason)
|
||||
@@ -205,6 +255,7 @@ onMounted(() => {
|
||||
EventsOn('hotkey:error', (reason: string) => {
|
||||
hotkeyStatus.value = { state: 'error', reason }
|
||||
})
|
||||
EventsOn('operation:status', updateOperationStatus)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -216,6 +267,7 @@ onUnmounted(() => {
|
||||
EventsOff('upload:failure')
|
||||
EventsOff('hotkey:ready')
|
||||
EventsOff('hotkey:error')
|
||||
EventsOff('operation:status')
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -229,6 +281,7 @@ onUnmounted(() => {
|
||||
@copy="onOverlayCopy"
|
||||
@save="onOverlaySave"
|
||||
@save-remote="onOverlaySaveRemote"
|
||||
@summarize="onOverlaySummarize"
|
||||
@cancel="onOverlayCancel"
|
||||
/>
|
||||
|
||||
@@ -264,6 +317,20 @@ onUnmounted(() => {
|
||||
</main>
|
||||
|
||||
<Toast v-if="toast" :kind="toast.kind" :text="toast.text" />
|
||||
<div
|
||||
v-if="operationStatus"
|
||||
class="operation-hud"
|
||||
:class="operationStatus.state"
|
||||
>
|
||||
<span v-if="operationStatus.state === 'running'" class="spinner" />
|
||||
<span v-else class="status-mark">
|
||||
{{ operationStatus.state === 'success' ? 'OK' : '!' }}
|
||||
</span>
|
||||
<div>
|
||||
<strong>{{ operationStatus.phase }}</strong>
|
||||
<p>{{ operationStatus.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -371,6 +438,66 @@ onUnmounted(() => {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.operation-hud {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 260px;
|
||||
max-width: 360px;
|
||||
padding: 12px 14px;
|
||||
color: #f9fafb;
|
||||
background: rgba(17, 24, 39, 0.94);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 12px 34px rgba(15, 23, 42, 0.28);
|
||||
}
|
||||
.operation-hud strong {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.operation-hud p {
|
||||
margin: 0;
|
||||
color: #d1d5db;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #60a5fa;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.75s linear infinite;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.status-mark {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.operation-hud.success .status-mark {
|
||||
color: #052e16;
|
||||
background: #86efac;
|
||||
}
|
||||
.operation-hud.error .status-mark {
|
||||
color: #450a0a;
|
||||
background: #fca5a5;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.app-root {
|
||||
background: #1c1d22;
|
||||
|
||||
Reference in New Issue
Block a user