feat: Add Save/Save Draft button to chat interface
- Add dynamic button that shows "Save" when no draft exists - Changes to "Save Draft" when a pending draft is in progress - Uses floppy disk icon (IconDeviceFloppy) for visual consistency - Clicking "Save" creates empty draft and navigates to edit page - Clicking "Save Draft" navigates to edit page with existing draft - Reactive state tracking using useSelector for draft state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,12 +15,13 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
import { IconVolume, IconMicrophone, IconNotes } from '@tabler/icons-react';
|
import { IconVolume, IconMicrophone, IconDeviceFloppy } from '@tabler/icons-react';
|
||||||
import { UserMenu } from '@/components/UserMenu';
|
import { UserMenu } from '@/components/UserMenu';
|
||||||
import { useVoiceMode } from '@/hooks/useVoiceMode';
|
import { useVoiceMode } from '@/hooks/useVoiceMode';
|
||||||
import { useAppMachine } from '@/hooks/useAppMachine';
|
import { useAppMachine } from '@/hooks/useAppMachine';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
|
import { useSelector } from '@xstate/react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the voice button text based on the current state
|
* Get the voice button text based on the current state
|
||||||
@@ -62,6 +63,10 @@ export default function ChatPage() {
|
|||||||
// State for creating node
|
// State for creating node
|
||||||
const [isCreatingNode, setIsCreatingNode] = useState(false);
|
const [isCreatingNode, setIsCreatingNode] = useState(false);
|
||||||
|
|
||||||
|
// Check if we have a pending draft (using useSelector for reactivity)
|
||||||
|
const pendingNodeDraft = useSelector(appActor, (state) => state.context.pendingNodeDraft);
|
||||||
|
const hasPendingDraft = !!pendingNodeDraft;
|
||||||
|
|
||||||
// Use the clean voice mode hook
|
// Use the clean voice mode hook
|
||||||
const { state, send, transcript, error } = useVoiceMode({
|
const { state, send, transcript, error } = useVoiceMode({
|
||||||
messages,
|
messages,
|
||||||
@@ -122,6 +127,34 @@ export default function ChatPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handler for Manual/Save button
|
||||||
|
const handleManualOrSave = () => {
|
||||||
|
if (hasPendingDraft && pendingNodeDraft) {
|
||||||
|
// If we have a draft, navigate to edit with it
|
||||||
|
appActor.send({
|
||||||
|
type: 'NAVIGATE_TO_EDIT',
|
||||||
|
draft: pendingNodeDraft,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create an empty draft for manual entry
|
||||||
|
const emptyDraft = {
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
conversationContext: messages.map((m) => {
|
||||||
|
if ('parts' in m && Array.isArray((m as any).parts)) {
|
||||||
|
return `${m.role}: ${(m as any).parts.find((p: any) => p.type === 'text')?.text || ''}`;
|
||||||
|
}
|
||||||
|
return `${m.role}: ${(m as any).content || ''}`;
|
||||||
|
}).join('\n'),
|
||||||
|
};
|
||||||
|
|
||||||
|
appActor.send({
|
||||||
|
type: 'CREATE_NODE_FROM_CONVERSATION',
|
||||||
|
draft: emptyDraft,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Add initial greeting message on first load
|
// Add initial greeting message on first load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
@@ -377,6 +410,15 @@ export default function ChatPage() {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
disabled={isVoiceActive}
|
disabled={isVoiceActive}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={handleManualOrSave}
|
||||||
|
radius="xl"
|
||||||
|
variant={hasPendingDraft ? 'filled' : 'light'}
|
||||||
|
color={hasPendingDraft ? 'blue' : 'gray'}
|
||||||
|
leftSection={<IconDeviceFloppy size={20} />}
|
||||||
|
>
|
||||||
|
{hasPendingDraft ? 'Save Draft' : 'Save'}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
|
|||||||
Reference in New Issue
Block a user