Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ app.whenReady().then(() => {
return await threadManager.updateThreadNodePositions(threadId, positions)
})

ipcMain.handle('toggle-reference-frame', async (_event, { threadId, filePath }) => {
return await threadManager.toggleThreadReferenceFrame(threadId, filePath)
})

ipcMain.handle('show-confirmation', async (_event, { title, message, detail, type = 'question', buttons = ['Cancel', 'Yes'], defaultId = 1, cancelId = 0 }) => {
const focusedWindow = BrowserWindow.getFocusedWindow()
if (!focusedWindow) return cancelId
Expand Down
24 changes: 24 additions & 0 deletions src/main/threads/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,30 @@ class ThreadManager {

return !!result
}

async toggleThreadReferenceFrame(threadId: string, filePath: string): Promise<boolean> {
const thread = this.getThread(threadId)
if (!thread) return false

const currentRefs = thread.preprocessing?.['reference-frames'] || []
const index = currentRefs.indexOf(filePath)

let newRefs: string[]
if (index === -1) {
newRefs = [...currentRefs, filePath]
} else {
newRefs = currentRefs.filter(r => r !== filePath)
}

const result = await this.updateThread(threadId, {
preprocessing: {
...(thread.preprocessing || {}),
'reference-frames': newRefs
}
})

return !!result
}
}

export const threadManager = new ThreadManager()
2 changes: 2 additions & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ const api = {
ipcRenderer.invoke('remove-single-message', { threadId, messageId }),
updateMessage: (threadId: string, messageId: string, updates: any) =>
ipcRenderer.invoke('update-message', { threadId, messageId, updates }),
toggleReferenceFrame: (threadId: string, filePath: string) =>
ipcRenderer.invoke('toggle-reference-frame', { threadId, filePath }),
showConfirmation: (options: { title: string, message: string, detail?: string, type?: string, buttons?: string[], defaultId?: number, cancelId?: number }) =>
ipcRenderer.invoke('show-confirmation', options),
saveVideo: (sourcePath: string) => ipcRenderer.invoke('save-video', sourcePath),
Expand Down
46 changes: 46 additions & 0 deletions src/renderer/src/components/SlimTooltip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<template>
<div class="relative group/slim-tooltip inline-flex items-center justify-center">
<!-- Trigger -->
<slot />

<!-- Tooltip Content -->
<div
class="absolute invisible group-hover/slim-tooltip:visible opacity-0 group-hover/slim-tooltip:opacity-100 transition-all duration-200 z-[100] pointer-events-none"
:class="[
positionClasses,
'px-2 py-1 rounded bg-zinc-900/90 backdrop-blur-md border border-white/10 text-white text-[9px] font-black uppercase tracking-[0.05em] whitespace-nowrap shadow-2xl flex items-center gap-1.5'
]"
>
{{ text }}
</div>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

const props = withDefaults(defineProps<{
text: string
placement?: 'left' | 'right' | 'top' | 'bottom'
}>(), {
placement: 'left'
})

const positionClasses = computed(() => {
switch (props.placement) {
case 'top':
return 'bottom-full mb-2 left-1/2 -translate-x-1/2 scale-90 group-hover/slim-tooltip:scale-100'
case 'bottom':
return 'top-full mt-2 left-1/2 -translate-x-1/2 scale-90 group-hover/slim-tooltip:scale-100'
case 'right':
return 'left-full ml-2 top-1/2 -translate-y-1/2 scale-90 group-hover/slim-tooltip:scale-100'
case 'left':
default:
return 'right-full mr-2 top-1/2 -translate-y-1/2 scale-90 group-hover/slim-tooltip:scale-100'
}
})
</script>

<style scoped>
/* Scoped styles removed as they were interfering with pointer events */
</style>
32 changes: 19 additions & 13 deletions src/renderer/src/components/chat/BaseMessageInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
class="relative rounded-xl overflow-hidden group shadow-sm border border-zinc-200 dark:border-zinc-800 transition-all hover:scale-105"
:class="compact ? 'w-10 h-10' : 'w-16 h-16'">
<img :src="normalizeUrl(img)" class="w-full h-full object-cover" />
<button
@click.stop="removeAttachment(idx)"
class="absolute top-1 right-1 p-1 bg-black/60 text-white rounded-full opacity-0 group-hover:opacity-100 transition-all hover:bg-red-500 hover:scale-110 z-10 shadow-lg"
title="Remove attachment"
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-2.5 h-2.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"/><path d="m6 6 12 12"/>
</svg>
</button>
<SlimTooltip text="Remove attachment" placement="top">
<button
@click.stop="removeAttachment(idx)"
class="absolute top-1 right-1 p-1 bg-black/60 text-white rounded-full opacity-0 group-hover:opacity-100 transition-all hover:bg-red-500 hover:scale-110 z-10 shadow-lg"
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-2.5 h-2.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18"/><path d="m6 6 12 12"/>
</svg>
</button>
</SlimTooltip>
</div>
</div>

Expand All @@ -25,8 +26,10 @@
: 'space-x-4 p-1.5 rounded-2xl border border-zinc-200 dark:border-zinc-800 bg-white/50 dark:bg-zinc-950/20 backdrop-blur-xl shadow-lg focus-within:ring-2 focus-within:ring-primary/20'
]">
<!-- Attachment Toggle -->
<IconButton @click="showAttachmentModal = true" icon="IconPlus" :size="compact ? 'sm' : 'md'" rounded="full"
variant="ghost" class="mb-0.5 text-zinc-400 hover:text-primary transition-all active:scale-95" />
<SlimTooltip text="Add Attachments" :placement="compact ? 'top' : 'bottom'">
<IconButton @click="showAttachmentModal = true" icon="IconPlus" :size="compact ? 'sm' : 'md'" rounded="full"
variant="ghost" class="mb-0.5 text-zinc-400 hover:text-primary transition-all active:scale-95" />
</SlimTooltip>

<div class="flex-1 min-w-0">
<textarea v-model="internalText" ref="textareaRef" :placeholder="placeholder" :rows="1"
Expand All @@ -35,8 +38,10 @@
@keydown.enter="handleEnter"></textarea>
</div>

<IconButton @click="handleSend" :icon="submitIcon" color="primary" :size="compact ? 'xs' : 'sm'" rounded="lg"
:disabled="!internalText.trim() && attachedImages.length === 0" />
<SlimTooltip :text="submitIcon === 'IconSend' ? 'Send Message (Enter)' : 'Execute Task'" :placement="compact ? 'top' : 'bottom'">
<IconButton @click="handleSend" :icon="submitIcon" color="primary" :size="compact ? 'xs' : 'sm'" rounded="lg"
:disabled="!internalText.trim() && attachedImages.length === 0" />
</SlimTooltip>
</div>

<AttachmentModal v-model="showAttachmentModal" @select="handleImagesSelected" />
Expand All @@ -46,6 +51,7 @@
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { IconButton } from 'pilotui/elements'
import SlimTooltip from '../SlimTooltip.vue'
import AttachmentModal from './AttachmentModal.vue'

const props = withDefaults(defineProps<{
Expand Down
Loading
Loading