Skip to content

Commit

Permalink
Small improvement to add ability to copy note
Browse files Browse the repository at this point in the history
  • Loading branch information
actions-user committed Aug 29, 2024
1 parent f9dd392 commit 2f2e1f2
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 5 deletions.
47 changes: 47 additions & 0 deletions backend/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Any, Dict, List, Tuple

from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import PlainTextResponse
from httpx import AsyncClient
from motor.motor_asyncio import AsyncIOMotorDatabase
from sqlalchemy.ext.asyncio import AsyncSession
Expand Down Expand Up @@ -275,6 +276,52 @@ async def get_medical_note(
) from e


@router.get("/medical_notes/note/{note_id}/raw", response_class=PlainTextResponse)
async def get_raw_medical_note(
note_id: str,
db: AsyncIOMotorDatabase[Any] = Depends(get_database), # noqa: B008
current_user: User = Depends(get_current_active_user), # noqa: B008
) -> str:
"""
Retrieve the raw text of a specific medical note by its ID.
Parameters
----------
note_id : str
The ID of the medical note.
db : AsyncIOMotorDatabase
The database connection.
current_user : User
The current authenticated user.
Returns
-------
str
The raw text of the retrieved medical note.
Raises
------
HTTPException
If the note is not found or an error occurs during retrieval.
"""
try:
collection = db.medical_notes
note = await collection.find_one({"note_id": note_id})

if not note:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Medical note not found"
)

return str(note["text"])
except Exception as e:
logger.error(f"Error retrieving raw medical note with ID {note_id}: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="An error occurred while retrieving the raw medical note",
) from e


@router.post("/auth/signin")
async def signin(
request: Request,
Expand Down
73 changes: 68 additions & 5 deletions frontend/src/app/note/[noteId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ import {
StatLabel,
StatNumber,
StatGroup,
IconButton,
Tooltip,
Container,
} from '@chakra-ui/react'
import { useParams } from 'next/navigation'
import { MedicalNote } from '../../types/note'
import useSWR from 'swr'
import Sidebar from '../../components/sidebar'
import { withAuth } from '../../components/with-auth'
import EntityVisualization from '../../components/entity-viz'
import { CopyIcon } from '@chakra-ui/icons'

const fetcher = async (url: string) => {
const res = await fetch(url, {
Expand Down Expand Up @@ -64,11 +68,13 @@ function NotePage() {
)
const [nerResponse, setNerResponse] = useState<NERResponse | null>(null)
const [isExtracting, setIsExtracting] = useState(false)
const [isCopying, setIsCopying] = useState(false)
const toast = useToast()

const bgColor = useColorModeValue('gray.50', 'gray.900')
const textColor = useColorModeValue('gray.800', 'gray.100')
const cardBgColor = useColorModeValue('white', 'gray.700')
const noteBgColor = useColorModeValue('gray.100', 'gray.800')

const extractEntities = useCallback(async () => {
if (!noteId) return
Expand Down Expand Up @@ -124,6 +130,47 @@ function NotePage() {
setNerResponse(null)
}, [])

const copyToClipboard = useCallback(async () => {
if (!noteId) return

setIsCopying(true)

try {
const response = await fetch(`/api/medical_notes/note/${noteId}/raw`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
})

if (!response.ok) {
throw new Error('Failed to fetch raw note')
}

const rawNote = await response.text()

await navigator.clipboard.writeText(rawNote)

toast({
title: "Copied",
description: "Raw note text copied to clipboard",
status: "success",
duration: 2000,
isClosable: true,
})
} catch (error) {
console.error('Failed to copy text: ', error)
toast({
title: "Error",
description: "Failed to copy text to clipboard",
status: "error",
duration: 2000,
isClosable: true,
})
} finally {
setIsCopying(false)
}
}, [noteId, toast])

const entityStats = useMemo(() => {
if (!nerResponse) return []
const groups = nerResponse.entities.reduce((acc, entity) => {
Expand Down Expand Up @@ -154,12 +201,12 @@ function NotePage() {
}

return (
<Card bg={cardBgColor}>
<Card bg={cardBgColor} shadow="md">
<CardHeader>
<Heading size="md">Medical Note Details</Heading>
</CardHeader>
<CardBody>
<VStack align="stretch" spacing={4}>
<VStack align="stretch" spacing={6}>
<Flex wrap="wrap" gap={2}>
<Badge colorScheme="blue">Note ID: {note.note_id}</Badge>
<Badge colorScheme="green">Subject ID: {note.subject_id}</Badge>
Expand Down Expand Up @@ -195,13 +242,27 @@ function NotePage() {
))}
</StatGroup>
)}
<Box>
<Box position="relative" bg={noteBgColor} p={4} borderRadius="md" shadow="sm">
<Tooltip label="Copy raw note to clipboard">
<IconButton
aria-label="Copy raw note to clipboard"
icon={<CopyIcon />}
onClick={copyToClipboard}
isLoading={isCopying}
size="md"
position="absolute"
top={2}
right={2}
zIndex={1}
colorScheme="teal"
/>
</Tooltip>
{isExtracting ? (
<Skeleton height="200px" />
) : nerResponse ? (
<EntityVisualization text={nerResponse.text} entities={nerResponse.entities} />
) : (
<Text whiteSpace="pre-wrap">{note.text}</Text>
<Text whiteSpace="pre-wrap" fontSize="sm">{note.text}</Text>
)}
</Box>
</VStack>
Expand All @@ -214,7 +275,9 @@ function NotePage() {
<Flex direction={{ base: 'column', md: 'row' }} minHeight="100vh" bg={bgColor} color={textColor}>
<Sidebar />
<Box flex={1} p={4} ml={{ base: 0, md: 60 }} transition="margin-left 0.3s" overflowX="hidden">
{renderContent()}
<Container maxW="container.xl">
{renderContent()}
</Container>
</Box>
</Flex>
)
Expand Down

0 comments on commit 2f2e1f2

Please sign in to comment.