generated from nhcarrigan/template
feat: add native clipboard support for screenshot paste (#67)
## Summary - Adds Tauri clipboard-manager plugin to read images from native clipboard - Falls back to native clipboard when WebView clipboard API returns empty (fixes screenshot paste) - Allows sending messages with just attachments (no text required) - Logs attached files to output with 📎 emoji ## Test plan - [ ] Build and run the app natively on Windows - [ ] Copy a screenshot (Win+Shift+S) and paste in the chat input - [ ] Verify the screenshot appears as an attachment preview - [ ] Send the attachment and verify Claude receives the file path - [ ] Test sending a message with only an attachment (no text) - [ ] Verify the 📎 log line shows the attached filename **Note:** Paste will not work in WSLg dev environment due to clipboard isolation - needs native Windows build to test. ✨ This PR was created with help from Hikari~ 🌸 Co-authored-by: Hikari <hikari@nhcarrigan.com> Reviewed-on: #67 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit was merged in pull request #67.
This commit is contained in:
@@ -25,6 +25,7 @@ export const claudeStore = {
|
||||
isProcessing: conversationsStore.isProcessing,
|
||||
grantedTools: conversationsStore.grantedTools,
|
||||
pendingRetryMessage: conversationsStore.pendingRetryMessage,
|
||||
attachments: conversationsStore.attachments,
|
||||
|
||||
// New conversation-aware subscriptions
|
||||
conversations: conversationsStore.conversations,
|
||||
@@ -67,6 +68,12 @@ export const claudeStore = {
|
||||
saveScrollPosition: conversationsStore.saveScrollPosition,
|
||||
getScrollPosition: conversationsStore.getScrollPosition,
|
||||
|
||||
// Attachment management
|
||||
addAttachment: conversationsStore.addAttachment,
|
||||
removeAttachment: conversationsStore.removeAttachment,
|
||||
clearAttachments: conversationsStore.clearAttachments,
|
||||
getAttachments: conversationsStore.getAttachments,
|
||||
|
||||
getGrantedTools: (): string[] => {
|
||||
let tools: string[] = [];
|
||||
conversationsStore.grantedTools.subscribe((t) => (tools = Array.from(t)))();
|
||||
@@ -86,6 +93,7 @@ export const claudeStore = {
|
||||
conversationsStore.setWorkingDirectory("");
|
||||
conversationsStore.setProcessing(false);
|
||||
conversationsStore.revokeAllTools();
|
||||
conversationsStore.clearAttachments();
|
||||
// Also clear history restoration
|
||||
clearHistoryRestore();
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
ConnectionStatus,
|
||||
PermissionRequest,
|
||||
UserQuestionEvent,
|
||||
Attachment,
|
||||
} from "$lib/types/messages";
|
||||
import type { CharacterState } from "$lib/types/states";
|
||||
import { cleanupConversationTracking } from "$lib/tauri";
|
||||
@@ -24,6 +25,7 @@ export interface Conversation {
|
||||
scrollPosition: number;
|
||||
createdAt: Date;
|
||||
lastActivityAt: Date;
|
||||
attachments: Attachment[];
|
||||
}
|
||||
|
||||
function createConversationsStore() {
|
||||
@@ -59,6 +61,7 @@ function createConversationsStore() {
|
||||
scrollPosition: -1, // -1 means "scroll to bottom" (auto-scroll)
|
||||
createdAt: new Date(),
|
||||
lastActivityAt: new Date(),
|
||||
attachments: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,6 +112,7 @@ function createConversationsStore() {
|
||||
);
|
||||
const pendingQuestion = derived(activeConversation, ($conv) => $conv?.pendingQuestion || null);
|
||||
const scrollPosition = derived(activeConversation, ($conv) => $conv?.scrollPosition ?? -1);
|
||||
const attachments = derived(activeConversation, ($conv) => $conv?.attachments || []);
|
||||
|
||||
return {
|
||||
// Expose derived stores for compatibility
|
||||
@@ -122,6 +126,7 @@ function createConversationsStore() {
|
||||
grantedTools: { subscribe: grantedTools.subscribe },
|
||||
pendingRetryMessage: { subscribe: pendingRetryMessage.subscribe },
|
||||
scrollPosition: { subscribe: scrollPosition.subscribe },
|
||||
attachments: { subscribe: attachments.subscribe },
|
||||
|
||||
// New conversation-specific stores
|
||||
conversations: { subscribe: conversations.subscribe },
|
||||
@@ -272,7 +277,7 @@ function createConversationsStore() {
|
||||
return newConv.id;
|
||||
},
|
||||
|
||||
deleteConversation: (id: string) => {
|
||||
deleteConversation: async (id: string) => {
|
||||
ensureInitialized();
|
||||
const convs = get(conversations);
|
||||
const activeId = get(activeConversationId);
|
||||
@@ -282,8 +287,8 @@ function createConversationsStore() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clean up tracking for this conversation
|
||||
cleanupConversationTracking(id);
|
||||
// Clean up tracking for this conversation (including temp files)
|
||||
await cleanupConversationTracking(id);
|
||||
|
||||
conversations.update((c) => {
|
||||
c.delete(id);
|
||||
@@ -571,6 +576,58 @@ function createConversationsStore() {
|
||||
return conv?.grantedTools.has(toolName) || false;
|
||||
},
|
||||
|
||||
// Attachment management
|
||||
addAttachment: (attachment: Attachment) => {
|
||||
const activeId = get(activeConversationId);
|
||||
if (!activeId) return;
|
||||
|
||||
conversations.update((convs) => {
|
||||
const conv = convs.get(activeId);
|
||||
if (conv) {
|
||||
conv.attachments.push(attachment);
|
||||
conv.lastActivityAt = new Date();
|
||||
}
|
||||
return convs;
|
||||
});
|
||||
},
|
||||
|
||||
removeAttachment: (id: string) => {
|
||||
const activeId = get(activeConversationId);
|
||||
if (!activeId) return;
|
||||
|
||||
conversations.update((convs) => {
|
||||
const conv = convs.get(activeId);
|
||||
if (conv) {
|
||||
conv.attachments = conv.attachments.filter((a) => a.id !== id);
|
||||
conv.lastActivityAt = new Date();
|
||||
}
|
||||
return convs;
|
||||
});
|
||||
},
|
||||
|
||||
clearAttachments: () => {
|
||||
const activeId = get(activeConversationId);
|
||||
if (!activeId) return;
|
||||
|
||||
conversations.update((convs) => {
|
||||
const conv = convs.get(activeId);
|
||||
if (conv) {
|
||||
conv.attachments = [];
|
||||
conv.lastActivityAt = new Date();
|
||||
}
|
||||
return convs;
|
||||
});
|
||||
},
|
||||
|
||||
getAttachments: (): Attachment[] => {
|
||||
const activeId = get(activeConversationId);
|
||||
if (!activeId) return [];
|
||||
|
||||
const convs = get(conversations);
|
||||
const conv = convs.get(activeId);
|
||||
return conv?.attachments || [];
|
||||
},
|
||||
|
||||
// Add initialization helper
|
||||
initialize: () => {
|
||||
ensureInitialized();
|
||||
|
||||
Reference in New Issue
Block a user