diff --git a/echo/directus/sync/snapshot/collections/aspect_segment.json b/echo/directus/sync/snapshot/collections/aspect_segment.json index 26f9ec26..33da7d3e 100644 --- a/echo/directus/sync/snapshot/collections/aspect_segment.json +++ b/echo/directus/sync/snapshot/collections/aspect_segment.json @@ -9,14 +9,14 @@ "collection": "aspect_segment", "color": null, "display_template": null, - "group": "project_analysis_run", + "group": "aspect", "hidden": true, "icon": null, "item_duplication_fields": null, "note": null, "preview_url": null, "singleton": false, - "sort": 4, + "sort": 1, "sort_field": null, "translations": null, "unarchive_value": null, diff --git a/echo/directus/sync/snapshot/collections/project_analysis_run.json b/echo/directus/sync/snapshot/collections/project_analysis_run.json index 6a232909..76159262 100644 --- a/echo/directus/sync/snapshot/collections/project_analysis_run.json +++ b/echo/directus/sync/snapshot/collections/project_analysis_run.json @@ -10,7 +10,7 @@ "color": null, "display_template": null, "group": null, - "hidden": true, + "hidden": false, "icon": null, "item_duplication_fields": null, "note": null, diff --git a/echo/directus/sync/snapshot/collections/view.json b/echo/directus/sync/snapshot/collections/view.json index 6eae1148..4f13e2d7 100644 --- a/echo/directus/sync/snapshot/collections/view.json +++ b/echo/directus/sync/snapshot/collections/view.json @@ -8,9 +8,9 @@ "collapse": "open", "collection": "view", "color": null, - "display_template": null, + "display_template": "{{project_analysis_run_id.project_id.name}}- {{name}}", "group": "project_analysis_run", - "hidden": true, + "hidden": false, "icon": null, "item_duplication_fields": null, "note": null, diff --git a/echo/directus/sync/snapshot/fields/aspect/aspect_segment.json b/echo/directus/sync/snapshot/fields/aspect/aspect_segment.json index 9a397f73..fb693576 100644 --- a/echo/directus/sync/snapshot/fields/aspect/aspect_segment.json +++ b/echo/directus/sync/snapshot/fields/aspect/aspect_segment.json @@ -19,7 +19,7 @@ }, "readonly": false, "required": false, - "sort": 13, + "sort": 10, "special": [ "o2m" ], diff --git a/echo/directus/sync/snapshot/fields/aspect/created_at.json b/echo/directus/sync/snapshot/fields/aspect/created_at.json index 51b0fd8c..231856f0 100644 --- a/echo/directus/sync/snapshot/fields/aspect/created_at.json +++ b/echo/directus/sync/snapshot/fields/aspect/created_at.json @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 2, + "sort": 3, "special": [ "date-created" ], diff --git a/echo/directus/sync/snapshot/fields/aspect/description.json b/echo/directus/sync/snapshot/fields/aspect/description.json index 76bf009e..192b2956 100644 --- a/echo/directus/sync/snapshot/fields/aspect/description.json +++ b/echo/directus/sync/snapshot/fields/aspect/description.json @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 5, + "sort": 6, "special": null, "translations": null, "validation": null, diff --git a/echo/directus/sync/snapshot/fields/aspect/id.json b/echo/directus/sync/snapshot/fields/aspect/id.json index a3fb617d..f5599d06 100644 --- a/echo/directus/sync/snapshot/fields/aspect/id.json +++ b/echo/directus/sync/snapshot/fields/aspect/id.json @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 1, + "sort": 2, "special": [ "uuid" ], diff --git a/echo/directus/sync/snapshot/fields/aspect/long_summary.json b/echo/directus/sync/snapshot/fields/aspect/long_summary.json index b73eb06e..cd033608 100644 --- a/echo/directus/sync/snapshot/fields/aspect/long_summary.json +++ b/echo/directus/sync/snapshot/fields/aspect/long_summary.json @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 7, + "sort": 8, "special": null, "translations": null, "validation": null, diff --git a/echo/directus/sync/snapshot/fields/aspect/name.json b/echo/directus/sync/snapshot/fields/aspect/name.json index 2bb060ab..f8cf14d2 100644 --- a/echo/directus/sync/snapshot/fields/aspect/name.json +++ b/echo/directus/sync/snapshot/fields/aspect/name.json @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 4, + "sort": 5, "special": null, "translations": null, "validation": null, diff --git a/echo/directus/sync/snapshot/fields/aspect/short_summary.json b/echo/directus/sync/snapshot/fields/aspect/short_summary.json index b1a21933..f119b3b3 100644 --- a/echo/directus/sync/snapshot/fields/aspect/short_summary.json +++ b/echo/directus/sync/snapshot/fields/aspect/short_summary.json @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 6, + "sort": 7, "special": null, "translations": null, "validation": null, diff --git a/echo/directus/sync/snapshot/fields/aspect/updated_at.json b/echo/directus/sync/snapshot/fields/aspect/updated_at.json index 1b21fa2a..a59bb6c8 100644 --- a/echo/directus/sync/snapshot/fields/aspect/updated_at.json +++ b/echo/directus/sync/snapshot/fields/aspect/updated_at.json @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 3, + "sort": 4, "special": [ "date-updated" ], diff --git a/echo/directus/sync/snapshot/fields/aspect/view_id.json b/echo/directus/sync/snapshot/fields/aspect/view_id.json index 934aa9fa..4730cf24 100644 --- a/echo/directus/sync/snapshot/fields/aspect/view_id.json +++ b/echo/directus/sync/snapshot/fields/aspect/view_id.json @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 12, + "sort": 1, "special": null, "translations": null, "validation": null, diff --git a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_completed_at.json b/echo/directus/sync/snapshot/fields/processing_status/project_analysis_run_id.json similarity index 60% rename from echo/directus/sync/snapshot/fields/project_analysis_run/processing_completed_at.json rename to echo/directus/sync/snapshot/fields/processing_status/project_analysis_run_id.json index 4a2c0280..f2b5418a 100644 --- a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_completed_at.json +++ b/echo/directus/sync/snapshot/fields/processing_status/project_analysis_run_id.json @@ -1,31 +1,33 @@ { - "collection": "project_analysis_run", - "field": "processing_completed_at", - "type": "timestamp", + "collection": "processing_status", + "field": "project_analysis_run_id", + "type": "uuid", "meta": { - "collection": "project_analysis_run", + "collection": "processing_status", "conditions": null, "display": null, "display_options": null, - "field": "processing_completed_at", + "field": "project_analysis_run_id", "group": null, "hidden": false, - "interface": null, + "interface": "select-dropdown-m2o", "note": null, "options": null, "readonly": false, "required": false, "sort": 10, - "special": null, + "special": [ + "m2o" + ], "translations": null, "validation": null, "validation_message": null, "width": "full" }, "schema": { - "name": "processing_completed_at", - "table": "project_analysis_run", - "data_type": "timestamp with time zone", + "name": "project_analysis_run_id", + "table": "processing_status", + "data_type": "uuid", "default_value": null, "max_length": null, "numeric_precision": null, @@ -37,7 +39,7 @@ "is_generated": false, "generation_expression": null, "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null + "foreign_key_table": "project_analysis_run", + "foreign_key_column": "id" } } diff --git a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_started_at.json b/echo/directus/sync/snapshot/fields/processing_status/project_id.json similarity index 55% rename from echo/directus/sync/snapshot/fields/project_analysis_run/processing_started_at.json rename to echo/directus/sync/snapshot/fields/processing_status/project_id.json index 5c6c7a28..7d0b8c99 100644 --- a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_started_at.json +++ b/echo/directus/sync/snapshot/fields/processing_status/project_id.json @@ -1,31 +1,38 @@ { - "collection": "project_analysis_run", - "field": "processing_started_at", - "type": "timestamp", + "collection": "processing_status", + "field": "project_id", + "type": "uuid", "meta": { - "collection": "project_analysis_run", + "collection": "processing_status", "conditions": null, "display": null, "display_options": null, - "field": "processing_started_at", + "field": "project_id", "group": null, "hidden": false, - "interface": null, + "interface": "select-dropdown-m2o", "note": null, - "options": null, + "options": { + "enableCreate": false, + "enableLink": true, + "enableSelect": false, + "template": "{{name}}" + }, "readonly": false, "required": false, "sort": 9, - "special": null, + "special": [ + "m2o" + ], "translations": null, "validation": null, "validation_message": null, "width": "full" }, "schema": { - "name": "processing_started_at", - "table": "project_analysis_run", - "data_type": "timestamp with time zone", + "name": "project_id", + "table": "processing_status", + "data_type": "uuid", "default_value": null, "max_length": null, "numeric_precision": null, @@ -37,7 +44,7 @@ "is_generated": false, "generation_expression": null, "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null + "foreign_key_table": "project", + "foreign_key_column": "id" } } diff --git a/echo/directus/sync/snapshot/fields/project/processing_status.json b/echo/directus/sync/snapshot/fields/project/processing_status.json new file mode 100644 index 00000000..b79ffb82 --- /dev/null +++ b/echo/directus/sync/snapshot/fields/project/processing_status.json @@ -0,0 +1,27 @@ +{ + "collection": "project", + "field": "processing_status", + "type": "alias", + "meta": { + "collection": "project", + "conditions": null, + "display": null, + "display_options": null, + "field": "processing_status", + "group": null, + "hidden": false, + "interface": "list-o2m", + "note": null, + "options": null, + "readonly": false, + "required": false, + "sort": 29, + "special": [ + "o2m" + ], + "translations": null, + "validation": null, + "validation_message": null, + "width": "full" + } +} diff --git a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_status.json b/echo/directus/sync/snapshot/fields/project_analysis_run/processing_status.json index 48e1dd74..992b24b8 100644 --- a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_status.json +++ b/echo/directus/sync/snapshot/fields/project_analysis_run/processing_status.json @@ -1,7 +1,7 @@ { "collection": "project_analysis_run", "field": "processing_status", - "type": "string", + "type": "alias", "meta": { "collection": "project_analysis_run", "conditions": null, @@ -10,34 +10,18 @@ "field": "processing_status", "group": null, "hidden": false, - "interface": null, + "interface": "list-o2m", "note": null, "options": null, "readonly": false, "required": false, - "sort": 6, - "special": null, + "sort": 14, + "special": [ + "o2m" + ], "translations": null, "validation": null, "validation_message": null, "width": "full" - }, - "schema": { - "name": "processing_status", - "table": "project_analysis_run", - "data_type": "character varying", - "default_value": "PENDING", - "max_length": 255, - "numeric_precision": null, - "numeric_scale": null, - "is_nullable": true, - "is_unique": false, - "is_indexed": false, - "is_primary_key": false, - "is_generated": false, - "generation_expression": null, - "has_auto_increment": false, - "foreign_key_table": null, - "foreign_key_column": null } } diff --git a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_message.json b/echo/directus/sync/snapshot/fields/view/user_input.json similarity index 77% rename from echo/directus/sync/snapshot/fields/project_analysis_run/processing_message.json rename to echo/directus/sync/snapshot/fields/view/user_input.json index df37a8c4..905119fd 100644 --- a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_message.json +++ b/echo/directus/sync/snapshot/fields/view/user_input.json @@ -1,13 +1,13 @@ { - "collection": "project_analysis_run", - "field": "processing_message", + "collection": "view", + "field": "user_input", "type": "string", "meta": { - "collection": "project_analysis_run", + "collection": "view", "conditions": null, "display": null, "display_options": null, - "field": "processing_message", + "field": "user_input", "group": null, "hidden": false, "interface": "input", @@ -15,7 +15,7 @@ "options": null, "readonly": false, "required": false, - "sort": 7, + "sort": 8, "special": null, "translations": null, "validation": null, @@ -23,8 +23,8 @@ "width": "full" }, "schema": { - "name": "processing_message", - "table": "project_analysis_run", + "name": "user_input", + "table": "view", "data_type": "character varying", "default_value": null, "max_length": 255, diff --git a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_error.json b/echo/directus/sync/snapshot/fields/view/user_input_description.json similarity index 68% rename from echo/directus/sync/snapshot/fields/project_analysis_run/processing_error.json rename to echo/directus/sync/snapshot/fields/view/user_input_description.json index 010f9b60..dac12132 100644 --- a/echo/directus/sync/snapshot/fields/project_analysis_run/processing_error.json +++ b/echo/directus/sync/snapshot/fields/view/user_input_description.json @@ -1,21 +1,21 @@ { - "collection": "project_analysis_run", - "field": "processing_error", - "type": "text", + "collection": "view", + "field": "user_input_description", + "type": "string", "meta": { - "collection": "project_analysis_run", + "collection": "view", "conditions": null, "display": null, "display_options": null, - "field": "processing_error", + "field": "user_input_description", "group": null, "hidden": false, - "interface": null, + "interface": "input", "note": null, "options": null, "readonly": false, "required": false, - "sort": 8, + "sort": 9, "special": null, "translations": null, "validation": null, @@ -23,11 +23,11 @@ "width": "full" }, "schema": { - "name": "processing_error", - "table": "project_analysis_run", - "data_type": "text", + "name": "user_input_description", + "table": "view", + "data_type": "character varying", "default_value": null, - "max_length": null, + "max_length": 255, "numeric_precision": null, "numeric_scale": null, "is_nullable": true, diff --git a/echo/directus/sync/snapshot/relations/processing_status/project_analysis_run_id.json b/echo/directus/sync/snapshot/relations/processing_status/project_analysis_run_id.json new file mode 100644 index 00000000..82995e12 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/processing_status/project_analysis_run_id.json @@ -0,0 +1,25 @@ +{ + "collection": "processing_status", + "field": "project_analysis_run_id", + "related_collection": "project_analysis_run", + "meta": { + "junction_field": null, + "many_collection": "processing_status", + "many_field": "project_analysis_run_id", + "one_allowed_collections": null, + "one_collection": "project_analysis_run", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": "processing_status", + "sort_field": null + }, + "schema": { + "table": "processing_status", + "column": "project_analysis_run_id", + "foreign_key_table": "project_analysis_run", + "foreign_key_column": "id", + "constraint_name": "processing_status_project_analysis_run_id_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} diff --git a/echo/directus/sync/snapshot/relations/processing_status/project_id.json b/echo/directus/sync/snapshot/relations/processing_status/project_id.json new file mode 100644 index 00000000..346812e8 --- /dev/null +++ b/echo/directus/sync/snapshot/relations/processing_status/project_id.json @@ -0,0 +1,25 @@ +{ + "collection": "processing_status", + "field": "project_id", + "related_collection": "project", + "meta": { + "junction_field": null, + "many_collection": "processing_status", + "many_field": "project_id", + "one_allowed_collections": null, + "one_collection": "project", + "one_collection_field": null, + "one_deselect_action": "nullify", + "one_field": "processing_status", + "sort_field": null + }, + "schema": { + "table": "processing_status", + "column": "project_id", + "foreign_key_table": "project", + "foreign_key_column": "id", + "constraint_name": "processing_status_project_id_foreign", + "on_update": "NO ACTION", + "on_delete": "SET NULL" + } +} diff --git a/echo/frontend/src/Router.tsx b/echo/frontend/src/Router.tsx index 5cb64721..235c5587 100644 --- a/echo/frontend/src/Router.tsx +++ b/echo/frontend/src/Router.tsx @@ -23,7 +23,6 @@ import { } from "./routes/project/ProjectRoutes"; import { ProjectConversationOverviewRoute } from "./routes/project/conversation/ProjectConversationOverview"; import { ProjectConversationTranscript } from "./routes/project/conversation/ProjectConversationTranscript"; -import { ProjectConversationAnalysis } from "./routes/project/conversation/ProjectConversationAnalysis"; import { ParticipantPostConversation } from "./routes/participant/ParticipantPostConversation"; import { ParticipantConversationAudioRoute, @@ -51,10 +50,6 @@ const ProjectLibraryRoute = createLazyNamedRoute( () => import("./routes/project/library/ProjectLibrary"), "ProjectLibraryRoute", ); -const ProjectLibraryInsight = createLazyNamedRoute( - () => import("./routes/project/library/ProjectLibraryInsight"), - "ProjectLibraryInsight", -); const ProjectLibraryView = createLazyNamedRoute( () => import("./routes/project/library/ProjectLibraryView"), @@ -230,10 +225,6 @@ export const mainRouter = createBrowserRouter([ path: "transcript", element: , }, - { - path: "analysis", - element: , - }, { path: "debug", element: , @@ -253,10 +244,6 @@ export const mainRouter = createBrowserRouter([ path: "views/:viewId", element: , }, - { - path: "insights/:insightId", - element: , - }, { index: true, element: , diff --git a/echo/frontend/src/components/conversation/ConversationAccordion.tsx b/echo/frontend/src/components/conversation/ConversationAccordion.tsx index 7e1ab5dc..a9261f29 100644 --- a/echo/frontend/src/components/conversation/ConversationAccordion.tsx +++ b/echo/frontend/src/components/conversation/ConversationAccordion.tsx @@ -424,13 +424,12 @@ export const ConversationStatusIndicators = ({ {!hasContent && conversation.is_finished === true && - conversation.is_all_chunks_processed === true && - conversation.error == null && ( + conversation.is_all_chunks_transcribed === true && ( {t`Empty`} )} - +{/* {conversation.error != null && ( - )} + )} */} ); }; diff --git a/echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx b/echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx index 3161e0f1..90d9c36b 100644 --- a/echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx +++ b/echo/frontend/src/components/dropzone/UploadConversationDropzone.tsx @@ -471,11 +471,11 @@ export const UploadConversationDropzone = ( projectId: props.projectId, namePrefix: "", chunks: selectedFiles, - pin: projectQuery.data?.pin || "", + pin: "", tagIdList: [], timestamps: selectedFiles.map(() => new Date()), }); - }, [selectedFiles, props.projectId, projectQuery.data?.pin, uploader]); + }, [selectedFiles, props.projectId, uploader]); if (projectQuery.isLoading) { return ; diff --git a/echo/frontend/src/components/participant/ParticipantInitiateForm.tsx b/echo/frontend/src/components/participant/ParticipantInitiateForm.tsx index 76346ce5..8be921de 100644 --- a/echo/frontend/src/components/participant/ParticipantInitiateForm.tsx +++ b/echo/frontend/src/components/participant/ParticipantInitiateForm.tsx @@ -43,7 +43,7 @@ export const ParticipantInitiateForm = ({ project }: { project: Project }) => { initiateConversationMutation.mutate({ projectId: project.id, name: data.name ?? t`Participant`, - pin: project.pin ?? "", + pin:"", tagIdList: data.tagIdList, source: "PORTAL_AUDIO", }); diff --git a/echo/frontend/src/components/project/ProjectAnalysisRunStatus.tsx b/echo/frontend/src/components/project/ProjectAnalysisRunStatus.tsx index 19eb8479..ba74bcae 100644 --- a/echo/frontend/src/components/project/ProjectAnalysisRunStatus.tsx +++ b/echo/frontend/src/components/project/ProjectAnalysisRunStatus.tsx @@ -56,30 +56,42 @@ export const ProjectAnalysisRunStatus = ({ return null; } - if (data.processing_status === "DONE") { - return ( - - {!!conversationChunksQuery.data && conversationChunksQuery.data > 0 ? ( + // if (data.processing_status === "DONE") { + // return ( + // + // {!!conversationChunksQuery.data && conversationChunksQuery.data > 0 ? ( + // + // + // New conversations have been added since the library was generated. + // Regenerate the library to process them. + // + // + // ) : ( + // <> + // )} + //
+ // This project library was generated on{" "} + // {new Date(data.created_at ?? new Date()).toLocaleString()}. + //
+ //
+ // ); + // } + + + return ( + +
+ { + conversationChunksQuery.data && conversationChunksQuery.data > 0 && ( New conversations have been added since the library was generated. Regenerate the library to process them. - ) : ( - <> - )} -
- This project library was generated on{" "} - {new Date(data.created_at ?? new Date()).toLocaleString()}. -
- - ); - } - - return ( -
- {data.processing_status}: {data.processing_message}{" "} + ) + } + {/* {data.processing_status}: {data.processing_message}{" "} */}
); }; diff --git a/echo/frontend/src/components/quote/Quote.tsx b/echo/frontend/src/components/quote/Quote.tsx index a628d000..d4c384ff 100644 --- a/echo/frontend/src/components/quote/Quote.tsx +++ b/echo/frontend/src/components/quote/Quote.tsx @@ -4,41 +4,155 @@ import { I18nLink } from "../common/i18nLink"; import { cn } from "@/lib/utils"; import { useCopyQuote } from "@/hooks/useCopyQuote"; import { CopyIconButton } from "../common/CopyIconButton"; +import { useState } from "react"; +import { ActionIcon, Tooltip } from "@mantine/core"; +import { IconChevronDown, IconChevronUp, IconQuote, IconBulb } from "@tabler/icons-react"; + +// replacement for AspectSegment export const Quote = ({ data, className, }: { - data: Quote; + data: AspectSegment; className?: string; }) => { const { projectId } = useParams(); + const [showTranscript, setShowTranscript] = useState(false); const { copyQuote, copied } = useCopyQuote(); + let conversationId: string | undefined; + try { + conversationId = (data.segment as ConversationSegment)?.conversation_id as string; + } catch (e) { + console.error(e); + } + + // Parse the relevant_index to extract the portion of transcript + const getTranscriptExcerpt = () => { + if (!data.verbatim_transcript || !data.relevant_index) return null; + + const [startStr, endStr] = data.relevant_index.split(':'); + const start = parseInt(startStr); + const end = parseInt(endStr); + + if (isNaN(start) || isNaN(end)) return null; + + return data.verbatim_transcript.slice(start, end); + }; + + const transcriptExcerpt = getTranscriptExcerpt(); + const hasTranscript = !!data.verbatim_transcript; + return ( - - "{data.text}" - - {data.conversation_id && ( - + {/* Main insight/reason */} + +
+ + + + Insight + + + + {data.description} + +
+ + copyQuote(data.description || "")} + copied={copied} + copyTooltip="Copy" + size={16} + /> +
+ + {/* Supporting transcript */} + {transcriptExcerpt && ( +
+ + + + + Supporting Quote + {data.relevant_index && ( + + ({data.relevant_index}) + + )} + + + + {hasTranscript && ( + + setShowTranscript(!showTranscript)} + > + {showTranscript ? : } + + + )} + + +
!showTranscript && hasTranscript && setShowTranscript(true)} + > + {showTranscript ? ( +
+ + {data.verbatim_transcript} + + {data.relevant_index && ( +
+ + Highlighted portion: characters {data.relevant_index} + +
+ )} +
+ ) : ( +
+ + "{transcriptExcerpt}" + + {hasTranscript && ( + + Click to see full context + + )} +
+ )} +
+
+ )} + + {/* Conversation link */} + {conversationId && ( + - - {((data as any).conversation_id as Conversation) - .participant_name ?? ""} + + {((data.segment as ConversationSegment).conversation_id as Conversation) + .participant_name ?? "View Conversation"} - - copyQuote(data.id)} - copied={copied} - size={18} - /> )}
diff --git a/echo/frontend/src/components/quote/quoteUtils.tsx b/echo/frontend/src/components/quote/quoteUtils.tsx deleted file mode 100644 index 80ef56d0..00000000 --- a/echo/frontend/src/components/quote/quoteUtils.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export const dedupeQuotes = (quotes: QuoteAspect[]): QuoteAspect[] => { - const seen = new Set(); - return quotes.filter((quote) => { - if (seen.has((quote.quote_id as Quote).id)) { - return false; - } - seen.add((quote.quote_id as Quote).id); - return true; - }); -}; diff --git a/echo/frontend/src/components/view/View.tsx b/echo/frontend/src/components/view/View.tsx index 50272cbf..5ba488d1 100644 --- a/echo/frontend/src/components/view/View.tsx +++ b/echo/frontend/src/components/view/View.tsx @@ -68,11 +68,11 @@ export const ViewExpandedCard = ({ data }: { data: View }) => { - {data.processing_status !== "DONE" && ( + {/* {data.processing_status !== "DONE" && ( {data.processing_status}: {data.processing_message} - )} + )} */} {data.name} diff --git a/echo/frontend/src/hooks/useCopyAspect.tsx b/echo/frontend/src/hooks/useCopyAspect.tsx index 8e93666d..31721ab4 100644 --- a/echo/frontend/src/hooks/useCopyAspect.tsx +++ b/echo/frontend/src/hooks/useCopyAspect.tsx @@ -29,13 +29,15 @@ export const useCopyAspect = () => { "image_url", "view_id", { - representative_quotes: [ + aspect_segment: [ { - quote_id: [ + segment: [ { conversation_id: ["id", "participant_name"], }, - "text", + "description", + "verbatim_transcript", + "relevant_index", ], }, ], @@ -62,22 +64,27 @@ export const useCopyAspect = () => { ); } - const quotes = Array.isArray(aspect.representative_quotes) - ? (aspect.representative_quotes as Quote[]) + const quotes = Array.isArray(aspect.aspect_segment) + ? (aspect.aspect_segment as AspectSegment[]) : []; if (quotes.length > 0) { stringBuilder.push(`## Top Quotes`); for (const quote of quotes) { - if (!quote.quote_id) continue; + if (!quote.segment) continue; + + const conversationId = (quote.segment as ConversationSegment).conversation_id as string; + const description = quote.description ?? "No description available"; + const conversation = (quote.segment as ConversationSegment)?.conversation_id as Conversation; + const participantName = conversation?.participant_name ?? "Unknown"; const conversationUrl = window.location.origin + - `/${language}/projects/${projectId}/conversation/${quote.quote_id.conversation_id.id}/transcript`; + `/${language}/projects/${projectId}/conversation/${conversationId}/transcript`; - stringBuilder.push(`"${quote.quote_id.text}"\n`); + stringBuilder.push(`"${description}"\n`); stringBuilder.push( - `from [${quote.quote_id.conversation_id.participant_name}](${conversationUrl})\n\n`, + `from [${participantName}](${conversationUrl})\n\n`, ); } } diff --git a/echo/frontend/src/hooks/useCopyInsight.tsx b/echo/frontend/src/hooks/useCopyInsight.tsx deleted file mode 100644 index 089536e7..00000000 --- a/echo/frontend/src/hooks/useCopyInsight.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { directus } from "@/lib/directus"; -import { readItem } from "@directus/sdk"; -import useCopyToRichText from "@/hooks/useCopyToRichText"; -import { useParams } from "react-router-dom"; - -interface Quote { - text: string; - conversation_id: { - id: string; - participant_name: string; - }; -} - -export const useCopyInsight = () => { - const { language, projectId } = useParams(); - const { copied, copy } = useCopyToRichText(); - - const copyInsight = async (insightId: string) => { - const stringBuilder: string[] = []; - const insight = await directus.request( - readItem("insight", insightId, { - fields: [ - "id", - "title", - "summary", - { - quotes: [ - "text", - { - conversation_id: ["id", "participant_name"], - }, - ], - }, - ], - }), - ); - - // Add insight title with link - stringBuilder.push( - `# Insight: [${insight.title || "Untitled"}](${window.location.origin}/${language}/projects/${projectId}/insights/${insightId})`, - ); - - // Add summary - if (insight.summary) { - stringBuilder.push(insight.summary); - } else { - stringBuilder.push( - "The summary for this insight is not available. Please try again later.", - ); - } - - // Add quotes - const quotes = Array.isArray(insight.quotes) - ? (insight.quotes as Quote[]) - : []; - if (quotes.length > 0) { - stringBuilder.push(`## Supporting Quotes`); - - for (const quote of quotes) { - const conversationUrl = - window.location.origin + - `/${language}/projects/${projectId}/conversation/${quote.conversation_id.id}/transcript`; - - stringBuilder.push(`"${quote.text}"\n`); - stringBuilder.push( - `from [${quote.conversation_id.participant_name}](${conversationUrl})\n\n`, - ); - } - } - - copy(stringBuilder.join("\n")); - }; - - return { - copyInsight, - copied, - }; -}; diff --git a/echo/frontend/src/hooks/useCopyQuote.ts b/echo/frontend/src/hooks/useCopyQuote.ts index 8bb88619..22a604b4 100644 --- a/echo/frontend/src/hooks/useCopyQuote.ts +++ b/echo/frontend/src/hooks/useCopyQuote.ts @@ -23,26 +23,22 @@ export const useCopyQuote = () => { const { language, projectId } = useParams(); const { copied, copy } = useCopyToRichText(); + // actually aspect Segment ID const copyQuote = async (quoteId: string) => { const stringBuilder: string[] = []; // @ts-expect-error - Directus SDK has incorrect types for nested fields - const quote: QuoteWithConversation = await directus.request( - readItem("quote", quoteId, { + const quote: AspectSegment = await directus.request( + readItem("aspect_segment", quoteId, { fields: [ "id", - "text", - "timestamp", + "description", + "verbatim_transcript", + "relevant_index", { - conversation_id: [ - "id", - "participant_name", + segment: [ { - tags: [ - { - project_tag_id: ["text"], - }, - ], + conversation_id: ["id", "participant_name", "created_at",], }, ], }, @@ -50,29 +46,46 @@ export const useCopyQuote = () => { }), ); + const conversation = (quote.segment as ConversationSegment)?.conversation_id as Conversation; + // Format timestamp if available - const timestamp = quote.timestamp - ? new Date(quote.timestamp).toLocaleString() - : ""; - - // Format tags if available - const tags = quote.conversation_id.tags - ?.map( - (tag: { project_tag_id: { text: string | null } }) => - tag.project_tag_id?.text, - ) - .join(", "); + const timestamp = conversation?.created_at ?? "" + + // // Format tags if available + // const tags = ((quote.segment as ConversationSegment)?.conversation_id as Conversation)?.tags + // ?.map( + // (tag: ConversationProjectTag) => + // tag.project_tag_id?.text ?? "", + // ) + // .join(", "); // Build the formatted quote with context stringBuilder.push( - `# Quote from [${quote.conversation_id.participant_name}](${window.location.origin}/${language}/projects/${projectId}/conversation/${quote.conversation_id.id}/transcript)`, + `# Quote from [${conversation?.participant_name}](${window.location.origin}/${language}/projects/${projectId}/conversation/${conversation?.id}/transcript)`, ); - stringBuilder.push(`"${quote.text}"`); + stringBuilder.push(`"${quote.description}"`); + stringBuilder.push(`${quote.verbatim_transcript}`); + + try { + const startIndex = parseInt(quote.relevant_index?.split(":")[0] ?? "0"); + const endIndex = parseInt(quote.relevant_index?.split(":")[1] ?? "0"); + + const relevantTranscript = quote.verbatim_transcript?.slice(startIndex, endIndex); + + if (relevantTranscript) { + stringBuilder.push(`${relevantTranscript}`); + } + } catch (e) { + console.error(e); + } + + stringBuilder.push("---"); + stringBuilder.push(""); // Empty line for spacing // Add metadata - if (quote.conversation_id.title) { - stringBuilder.push(`**Conversation:** ${quote.conversation_id.title}`); + if (conversation?.participant_name ) { + stringBuilder.push(`**Conversation:** ${conversation.participant_name}`); } if (timestamp) { @@ -80,13 +93,13 @@ export const useCopyQuote = () => { stringBuilder.push(""); } - if (tags) { - stringBuilder.push(`**Tags:** ${tags}`); - stringBuilder.push(""); - } + // if (tags) { + // stringBuilder.push(`**Tags:** ${tags}`); + // stringBuilder.push(""); + // } // Add source link - const sourceUrl = `${window.location.origin}/${language}/projects/${projectId}/conversation/${quote.conversation_id.id}/transcript`; + const sourceUrl = `${window.location.origin}/${language}/projects/${projectId}/conversation/${conversation?.id}/transcript`; stringBuilder.push(""); // Empty line before source stringBuilder.push(`[View in conversation](${sourceUrl})`); diff --git a/echo/frontend/src/lib/api.ts b/echo/frontend/src/lib/api.ts index 4c335cec..4b4ee0da 100644 --- a/echo/frontend/src/lib/api.ts +++ b/echo/frontend/src/lib/api.ts @@ -219,78 +219,6 @@ export const getProjectViews = async (projectId: string) => { ); }; -export const getProjectInsights = async (projectId: string) => { - const project_analysis_run = - await getLatestProjectAnalysisRunByProjectId(projectId); - - if (!project_analysis_run) { - return []; - } - - return directus.request( - readItems("insight", { - fields: [ - "*", - { - quotes: [ - "id", - "text", - "timestamp", - { - conversation_id: ["id", "participant_name"], - }, - ], - }, - "count(quotes)", - ], - filter: { - project_analysis_run_id: project_analysis_run?.id, - }, - sort: "-created_at", - }), - ); -}; - -export const getQuotesByConversationId = async (conversationId: string) => { - const conversation = await directus.request( - readItem("conversation", conversationId, { - fields: ["project_id"], - }), - ); - - if (!conversation) { - return []; - } - - const project_analysis_run = await getLatestProjectAnalysisRunByProjectId( - conversation.project_id as string, - ); - - if (!project_analysis_run) { - return []; - } - - const data = await directus.request( - readItems("quote", { - fields: [ - "*", - { - conversation_id: ["id", "participant_name"], - }, - ], - sort: "order", - filter: { - conversation_id: { - _eq: conversationId, - }, - project_analysis_run_id: project_analysis_run?.id, - }, - }), - ); - - return data; -}; - export const getProjectTranscriptsLink = (projectId: string) => `${apiCommonConfig.baseURL}/projects/${projectId}/transcripts`; diff --git a/echo/frontend/src/lib/query.ts b/echo/frontend/src/lib/query.ts index c061b1c3..a6b61aba 100644 --- a/echo/frontend/src/lib/query.ts +++ b/echo/frontend/src/lib/query.ts @@ -27,9 +27,7 @@ import { getLatestProjectAnalysisRunByProjectId, getProjectChatContext, getProjectConversationCounts, - getProjectInsights, getProjectViews, - getQuotesByConversationId, getResourceById, getResourcesByProjectId, initiateAndUploadConversationChunk, @@ -352,35 +350,6 @@ export const useProjectById = ({ }); }; -export const useProjectInsights = (projectId: string) => { - return useQuery({ - queryKey: ["projects", projectId, "insights"], - queryFn: () => getProjectInsights(projectId), - }); -}; - -export const useInsight = (insightId: string) => { - return useQuery({ - queryKey: ["insights", insightId], - queryFn: () => - directus.request( - readItem("insight", insightId, { - fields: [ - "*", - { - quotes: [ - "*", - { - conversation_id: ["id", "participant_name", "created_at"], - }, - ], - }, - ], - }), - ), - }); -}; - export const useProjectViews = (projectId: string) => { return useQuery({ queryKey: ["projects", projectId, "views"], @@ -395,11 +364,16 @@ export const useViewById = (projectId: string, viewId: string) => { queryFn: () => directus.request( readItem("view", viewId, { - fields: ["*", { aspects: ["*", "count(quotes)"] }], + fields: [ + "*", + { + aspects: ["*", "count(aspect_segment)"], + }, + ], deep: { - // get the aspects that have at least one representative quote + // get the aspects that have at least one aspect segment aspects: { - _sort: "-count(representative_quotes)", + _sort: "-count(aspect_segment)", } as any, }, }), @@ -416,35 +390,15 @@ export const useAspectById = (projectId: string, aspectId: string) => { fields: [ "*", { - quotes: [ + "aspect_segment": [ "*", - { - quote_id: [ - "id", - "text", - "created_at", - { - conversation_id: ["id", "participant_name", "created_at"], - }, - ], - }, - ], - }, - { - representative_quotes: [ - "*", - { - quote_id: [ - "id", - "text", - "created_at", - { - conversation_id: ["id", "participant_name", "created_at"], - }, - ], - }, - ], - }, + { + "segment": [ + "*", + ] + } + ] + } ], }), ), @@ -609,13 +563,6 @@ export const useConversationById = ({ }); }; -export const useConversationQuotes = (conversationId: string) => { - return useQuery({ - queryKey: ["conversations", conversationId, "quotes"], - queryFn: () => getQuotesByConversationId(conversationId), - }); -}; - export const useUpdateConversationByIdMutation = () => { const queryClient = useQueryClient(); return useMutation({ @@ -842,7 +789,6 @@ export const useConversationsByProjectId = ( _limit: loadChunks ? 1000 : 1, }, }, - // @ts-expect-error filterBySource is not typed filter: { project_id: { _eq: projectId, @@ -1244,33 +1190,6 @@ export const useConversationTranscriptString = (conversationId: string) => { }); }; -// export const useConversationTokenCount = (conversationId: string) => { -// return useQuery({ -// queryKey: ["conversations", conversationId, "token_count"], -// queryFn: () => getConversationTokenCount(conversationId), -// }); -// }; - -export const useInsightsByConversationId = (conversationId: string) => { - return useQuery({ - queryKey: ["conversations", conversationId, "insights"], - queryFn: () => - directus.request( - readItems("insight", { - filter: { - quotes: { - _some: { - conversation_id: { - _eq: conversationId, - }, - }, - }, - }, - }), - ), - }); -}; - export const useCreateChatMutation = () => { const navigate = useI18nNavigate(); const queryClient = useQueryClient(); diff --git a/echo/frontend/src/lib/typesDirectus.d.ts b/echo/frontend/src/lib/typesDirectus.d.ts index c9ac9d5f..12c9f32e 100644 --- a/echo/frontend/src/lib/typesDirectus.d.ts +++ b/echo/frontend/src/lib/typesDirectus.d.ts @@ -1,88 +1,108 @@ -type Account = { - date_created?: string | null; - date_updated?: string | null; +type Announcement = { + activity: any[] | AnnouncementActivity[]; + created_at?: string | null; + expires_at?: string | null; id: string; - status: string; - users: any[] | AccountDirectusUsers[]; + level?: string | null; + sort?: number | null; + translations: any[] | AnnouncementTranslations[]; + updated_at?: string | null; + user_created?: string | DirectusUsers | null; + user_updated?: string | DirectusUsers | null; }; -type AccountDirectusUsers = { - account_id?: string | Account | null; - directus_users_id?: string | DirectusUsers | null; +type AnnouncementActivity = { + announcement_activity?: string | Announcement | null; + created_at?: string | null; + id: string; + read?: boolean | null; + sort?: number | null; + updated_at?: string | null; + user_created?: string | DirectusUsers | null; + user_id?: string | null; + user_updated?: string | DirectusUsers | null; +}; + +type AnnouncementTranslations = { + announcement_id?: string | Announcement | null; id: number; + languages_code?: string | Languages | null; + message?: string | null; + title?: string | null; }; type Aspect = { - centroid_embedding?: string | null; + aspect_segment: any[] | AspectSegment[]; created_at?: string | null; description?: string | null; id: string; image_url?: string | null; long_summary?: string | null; name?: string | null; - quotes: any[] | QuoteAspect[]; - representative_quotes: any[] | QuoteAspect1[]; short_summary?: string | null; updated_at?: string | null; view_id?: string | View | null; }; -type ProcessingStatus = { - id: int; - parent?: string | ProcessingStatus | null; - conversation_id?: string | Conversation | null; - conversation_chunk_id?: string | ConversationChunk | null; - timestamp: string; - event?: string | null; - message?: string | null; - duration_ms?: number | null; +type AspectSegment = { + aspect?: string | Aspect | null; + description?: string | null; + id: string; + relevant_index?: string | null; + segment?: number | ConversationSegment | null; + verbatim_transcript?: string | null; }; type Conversation = { - is_finished: boolean; - is_audio_processing_finished: boolean; - is_all_chunks_processed: boolean; chunks: any[] | ConversationChunk[]; - context?: string | null; + conversation_segments: any[] | ConversationSegment[]; created_at?: string | null; - description?: string | null; - id: string; - source?: "DASHBOARD_UPLOAD" | "CLONE" | null duration?: number | null; + id: string; + is_all_chunks_transcribed?: boolean | null; + is_audio_processing_finished?: boolean | null; + is_finished?: boolean | null; + merged_audio_path?: string | null; + merged_transcript?: string | null; participant_email?: string | null; participant_name?: string | null; participant_user_agent?: string | null; + processing_status: any[] | ProcessingStatus[]; project_chat_messages: any[] | ProjectChatMessageConversation[]; project_chats: any[] | ProjectChatConversation[]; project_id: string | Project; replies: any[] | ConversationReply[]; + source?: string | null; summary?: string | null; tags: any[] | ConversationProjectTag[]; - title?: string | null; updated_at?: string | null; - merged_transcript?: string | null; - merged_audio_path?: string | null; - error?: string | null; }; type ConversationChunk = { conversation_id: string | Conversation; + conversation_segments: any[] | ConversationSegmentConversationChunk[]; created_at?: string | null; - id: string; - path?: string | null; - quotes: any[] | QuoteConversationChunk[]; - timestamp: string; - transcript?: string | null; - updated_at?: string | null; - error?: string | null; - source?: "DASHBOARD_UPLOAD" | "SPLIT" | "PORTAL_AUDIO" | "PORTAL_TEXT" | null; - hallucination_score?: number | null; - hallucination_reason?: string | null; + cross_talk_instances?: number | null; desired_language?: string | null; detected_language?: string | null; detected_language_confidence?: number | null; + diarization?: unknown | null; + error?: string | null; + hallucination_reason?: string | null; + hallucination_score?: number | null; + id: string; + noise_ratio?: number | null; + path?: string | null; + processing_status: any[] | ProcessingStatus[]; raw_transcript?: string | null; + runpod_job_status_link?: string | null; + runpod_request_count?: number | null; + silence_ratio?: number | null; + source?: string | null; + timestamp: string; + transcript?: string | null; translation_error?: string | null; + updated_at?: string | null; }; type ConversationProjectTag = { @@ -93,12 +113,32 @@ type ConversationProjectTag = { type ConversationReply = { content_text?: string | null; - conversation_id?: string | Conversation | null; + conversation_id?: string | null; date_created?: string | null; - id: number; + id: string; + reply?: string | Conversation | null; + sort?: number | null; type?: string | null; }; +type ConversationSegment = { + chunks: any[] | ConversationSegmentConversationChunk[]; + config_id?: string | null; + contextual_transcript?: string | null; + conversation_id?: string | Conversation | null; + counter?: number | null; + id: number; + lightrag_flag?: boolean | null; + path?: string | null; + transcript?: string | null; +}; + +type ConversationSegmentConversationChunk = { + conversation_chunk_id?: string | ConversationChunk | null; + conversation_segment_id?: number | ConversationSegment | null; + id: number; +}; + type DirectusAccess = { id: string; policy: string | DirectusPolicies; @@ -453,11 +493,11 @@ type DirectusTranslations = { }; type DirectusUsers = { - accounts: any[] | AccountDirectusUsers[]; appearance?: string | null; auth_data?: unknown | null; avatar?: string | DirectusFiles | null; description?: string | null; + disable_create_project?: boolean | null; email?: string | null; email_notifications?: boolean | null; external_identifier?: string | null; @@ -482,7 +522,6 @@ type DirectusUsers = { theme_light_overrides?: unknown | null; title?: string | null; token?: string | null; - disable_create_project?: boolean | null; }; type DirectusVersions = { @@ -513,34 +552,34 @@ type DirectusWebhooks = { was_active_before_deprecation: boolean; }; -type Document = { - context?: string | null; - created_at?: string | null; - description?: string | null; - id: string; - is_processed: boolean; - original_filename?: string | null; - path?: string | null; - processing_error?: string | null; - project_id: string | Project; - title?: string | null; - type?: string | null; - updated_at?: string | null; -}; - type Insight = { created_at?: string | null; id: string; project_analysis_run_id?: string | ProjectAnalysisRun | null; - quotes: any[] | Quote[]; summary?: string | null; title?: string | null; updated_at?: string | null; }; +type Languages = { + code: string; + direction?: string | null; + name?: string | null; +}; + +type ProcessingStatus = { + conversation_chunk_id?: string | ConversationChunk | null; + conversation_id?: string | Conversation | null; + duration_ms?: number | null; + event?: string | null; + id: number; + message?: string | null; + parent?: number | ProcessingStatus | null; + project_id?: string | Project | null; + timestamp?: string | null; +}; + type Project = { - conversations_count?: number | null; - is_enhanced_audio_processing_enabled?: boolean | null; context?: string | null; conversation_ask_for_participant_name_label?: string | null; conversations: any[] | Conversation[]; @@ -548,39 +587,35 @@ type Project = { default_conversation_ask_for_participant_name?: boolean | null; default_conversation_description?: string | null; default_conversation_finish_text?: string | null; - is_project_notification_subscription_allowed?: boolean | null; default_conversation_title?: string | null; default_conversation_transcript_prompt?: string | null; default_conversation_tutorial_slug?: string | null; directus_user_id?: string | DirectusUsers | null; + get_reply_mode?: string | null; get_reply_prompt?: string | null; id: string; image_generation_model?: string | null; is_conversation_allowed: boolean; + is_enhanced_audio_processing_enabled?: boolean | null; is_get_reply_enabled?: boolean | null; - get_reply_mode?: string | null; - is_library_insights_enabled?: boolean | null; + is_project_notification_subscription_allowed?: boolean | null; language?: string | null; name?: string | null; - pin?: string | null; + processing_status: any[] | ProcessingStatus[]; project_analysis_runs: any[] | ProjectAnalysisRun[]; project_chats: any[] | ProjectChat[]; project_reports: any[] | ProjectReport[]; tags: any[] | ProjectTag[]; updated_at?: string | null; + + conversations_count?: number | null; }; type ProjectAnalysisRun = { created_at?: string | null; id: string; insights: any[] | Insight[]; - processing_completed_at?: string | null; - processing_error?: string | null; - processing_message?: string | null; - processing_started_at?: string | null; - processing_status?: string | null; project_id?: string | Project | null; - quotes: any[] | Quote[]; updated_at?: string | null; views: any[] | View[]; }; @@ -606,27 +641,18 @@ type ProjectChatConversation = { type ProjectChatMessage = { added_conversations: any[] | ProjectChatMessageConversation1[]; + chat_message_metadata: any[] | ProjectChatMessageMetadata[]; date_created?: string | null; date_updated?: string | null; id: string; message_from?: string | null; - chat_message_metadata?: any[] | ProjectChatMessageMetadata[]; project_chat_id?: string | ProjectChat | null; + template_key?: string | null; text?: string | null; tokens_count?: number | null; used_conversations: any[] | ProjectChatMessageConversation[]; }; -type ProjectChatMessageMetadata = { - id?: string; - type: "reference" | "citation"; - conversation: string | Conversation; - conversation_title?: string; - ratio: number; - reference_text?: string; - message_metadata?: string; -}; - type ProjectChatMessageConversation = { conversation_id?: string | Conversation | null; id: number; @@ -639,6 +665,16 @@ type ProjectChatMessageConversation1 = { project_chat_message_id?: string | ProjectChatMessage | null; }; +type ProjectChatMessageMetadata = { + conversation?: string | Conversation | null; + date_created?: string | null; + id: string; + message_metadata?: string | ProjectChatMessage | null; + ratio?: number | null; + reference_text?: string | null; + type?: string | null; +}; + type ProjectReport = { content?: string | null; date_created?: string | null; @@ -651,14 +687,6 @@ type ProjectReport = { status: string; }; -type ProjectReportNotificationParticipants = { - id: number; - email?: string | null; - project_id?: string | number | null; - email_opt_in?: boolean; - email_opt_out_token?: string | null; -}; - type ProjectReportMetric = { date_created?: string | null; date_updated?: string | null; @@ -668,6 +696,18 @@ type ProjectReportMetric = { type?: string | null; }; +type ProjectReportNotificationParticipants = { + conversation_id?: string | Conversation | null; + date_submitted?: string | null; + date_updated?: string | null; + email?: string | null; + email_opt_in?: boolean | null; + email_opt_out_token?: string | null; + id: string; + project_id?: string | null; + sort?: number | null; +}; + type ProjectTag = { conversations: any[] | ConversationProjectTag[]; created_at?: string | null; @@ -678,89 +718,30 @@ type ProjectTag = { updated_at?: string | null; }; -type Quote = { - aspects: any[] | QuoteAspect[]; - conversation_chunks: any[] | QuoteConversationChunk[]; - conversation_id: string | Conversation; - created_at?: string | null; - embedding: string; - id: string; - insight_id?: string | Insight | null; - order?: number | null; - project_analysis_run_id?: string | ProjectAnalysisRun | null; - representative_aspects: any[] | QuoteAspect1[]; - text: string; - timestamp?: string | null; - updated_at?: string | null; -}; - -type QuoteAspect = { - aspect_id?: string | Aspect | null; - id: number; - quote_id?: string | Quote | null; -}; - -type QuoteAspect1 = { - aspect_id?: string | Aspect | null; - id: number; - quote_id?: string | Quote | null; -}; - -type QuoteConversationChunk = { - conversation_chunk_id?: string | ConversationChunk | null; - id: number; - quote_id?: string | Quote | null; -}; - type View = { aspects: any[] | Aspect[]; created_at?: string | null; id: string; name?: string | null; - processing_completed_at?: string | null; - processing_error?: string | null; - processing_message?: string | null; - processing_started_at?: string | null; - processing_status?: string | null; project_analysis_run_id?: string | ProjectAnalysisRun | null; summary?: string | null; updated_at?: string | null; -}; - -type Announcement = { - id: string; - user_created?: string | DirectusUsers | null; - created_at?: Date | null; - expires_at?: Date | null; - level?: string | null; - translations: any[] | AnnouncementTranslations[]; - activity: any[] | AnnouncementActivity[]; -}; - -type AnnouncementTranslations = { - id: number; - languages_code?: string | Languages | null; - title?: string | null; - message?: string | null; -}; - -type AnnouncementActivity = { - id: string; - user_created?: string | DirectusUsers | null; - created_at?: string | null; - user_id?: string | null; - announcement_activity?: string | Announcement | null; - read: boolean | null; + user_input?: string | null; + user_input_description?: string | null; }; type CustomDirectusTypes = { - account: Account[]; - account_directus_users: AccountDirectusUsers[]; + announcement: Announcement[]; + announcement_activity: AnnouncementActivity[]; + announcement_translations: AnnouncementTranslations[]; aspect: Aspect[]; + aspect_segment: AspectSegment[]; conversation: Conversation[]; conversation_chunk: ConversationChunk[]; conversation_project_tag: ConversationProjectTag[]; conversation_reply: ConversationReply[]; + conversation_segment: ConversationSegment[]; + conversation_segment_conversation_chunk: ConversationSegmentConversationChunk[]; directus_access: DirectusAccess[]; directus_activity: DirectusActivity[]; directus_collections: DirectusCollections[]; @@ -789,8 +770,9 @@ type CustomDirectusTypes = { directus_users: DirectusUsers[]; directus_versions: DirectusVersions[]; directus_webhooks: DirectusWebhooks[]; - document: Document[]; insight: Insight[]; + languages: Languages[]; + processing_status: ProcessingStatus[]; project: Project[]; project_analysis_run: ProjectAnalysisRun[]; project_chat: ProjectChat[]; @@ -798,17 +780,10 @@ type CustomDirectusTypes = { project_chat_message: ProjectChatMessage[]; project_chat_message_conversation: ProjectChatMessageConversation[]; project_chat_message_conversation_1: ProjectChatMessageConversation1[]; + project_chat_message_metadata: ProjectChatMessageMetadata[]; project_report: ProjectReport[]; project_report_metric: ProjectReportMetric[]; project_report_notification_participants: ProjectReportNotificationParticipants[]; project_tag: ProjectTag[]; - quote: Quote[]; - quote_aspect: QuoteAspect[]; - quote_aspect_1: QuoteAspect1[]; - quote_conversation_chunk: QuoteConversationChunk[]; view: View[]; - processing_status: ProcessingStatus[]; - announcement: Announcement[]; - announcement_translations: AnnouncementTranslations[]; - announcement_activity: AnnouncementActivity[]; }; diff --git a/echo/frontend/src/routes/participant/ParticipantConversation.tsx b/echo/frontend/src/routes/participant/ParticipantConversation.tsx index 567b81d8..71b93849 100644 --- a/echo/frontend/src/routes/participant/ParticipantConversation.tsx +++ b/echo/frontend/src/routes/participant/ParticipantConversation.tsx @@ -391,6 +391,7 @@ export const ParticipantConversationAudioRoute = () => { { { - const { conversationId, projectId } = useParams(); - - const quotesQuery = useConversationQuotes(conversationId ?? ""); - - const insightsQuery = useInsightsByConversationId(conversationId ?? ""); - const [showInsights, setShowInsights] = useState(false); - - return ( - - - {insightsQuery.data && insightsQuery.data.length > 0 && ( - - {insightsQuery.data.length > 99 ? "99+" : insightsQuery.data.length} - - )} - - <Trans>Insights</Trans> - - {insightsQuery.data && insightsQuery.data.length > 0 && ( - - )} - - {insightsQuery.error && ( - - Error loading insights - - )} - {insightsQuery.isLoading && ( - <> - - - - - )} - setShowInsights(expanded)} - > - {insightsQuery.data && insightsQuery.data.length === 0 && ( - - - No insights available. Generate insights for this conversation by - visiting - - the project library. - - - - )} - - - {insightsQuery.data && - insightsQuery.data.map((insight) => ( - - ))} - - - - - - - {quotesQuery.data && quotesQuery.data.length > 0 && ( - - {quotesQuery.data.length > 99 ? "99+" : quotesQuery.data.length} - - )} - - <Trans>Quotes</Trans> - - - {quotesQuery.error && ( - - Error loading quotes - - )} - {quotesQuery.isLoading && ( - <> - - - - - )} - - {quotesQuery.data && quotesQuery.data.length === 0 && ( - - - - No quotes available. Generate quotes for this conversation by - visiting - - - - - the project library. - - - - )} - - {quotesQuery.data && - quotesQuery.data.map((quote) => ( - - ))} - - {/* */} - {/* */} - - ); -}; diff --git a/echo/frontend/src/routes/project/conversation/ProjectConversationTranscript.tsx b/echo/frontend/src/routes/project/conversation/ProjectConversationTranscript.tsx index d0508f21..cefe348b 100644 --- a/echo/frontend/src/routes/project/conversation/ProjectConversationTranscript.tsx +++ b/echo/frontend/src/routes/project/conversation/ProjectConversationTranscript.tsx @@ -123,7 +123,6 @@ export const ProjectConversationTranscript = () => { a.download = filename; } else { a.download = - conversationQuery.data.title ?? "Conversation" + "-" + conversationQuery.data.participant_email + diff --git a/echo/frontend/src/routes/project/library/ProjectLibrary.tsx b/echo/frontend/src/routes/project/library/ProjectLibrary.tsx index 5d883b09..e28116b8 100644 --- a/echo/frontend/src/routes/project/library/ProjectLibrary.tsx +++ b/echo/frontend/src/routes/project/library/ProjectLibrary.tsx @@ -2,7 +2,6 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { Breadcrumbs } from "@/components/common/Breadcrumbs"; import { CloseableAlert } from "@/components/common/ClosableAlert"; -import { Insight } from "@/components/insight/Insight"; import { ProjectAnalysisRunStatus } from "@/components/project/ProjectAnalysisRunStatus"; import { ViewExpandedCard } from "@/components/view/View"; import { Icons } from "@/icons"; @@ -11,7 +10,6 @@ import { useGenerateProjectLibraryMutation, useLatestProjectAnalysisRunByProjectId, useProjectById, - useProjectInsights, useProjectViews, } from "@/lib/query"; import { useLanguage } from "@/hooks/useLanguage"; @@ -54,7 +52,6 @@ export const ProjectLibraryRoute = () => { const projectQuery = useProjectById({ projectId: projectId ?? "", }); - const insightsQuery = useProjectInsights(projectId ?? ""); const conversationsQuery = useConversationsByProjectId( projectId ?? "", false, @@ -84,44 +81,6 @@ export const ProjectLibraryRoute = () => { ); } - const sortInsights = (data: Insight[], sortBy: SortBy) => { - try { - if (sortBy === "default") { - return data.sort( - (a, b) => - new Date(a.created_at!).getTime() - - new Date(b.created_at!).getTime(), - ); - } else if (sortBy === "relevance") { - // Ubiquity - Measured by the number of unique conversations in each insight. - // Relevance - Measured by the number of quotes present in each insight. - return data.sort((a, b) => { - const uniqueConversationsA = new Set( - a.quotes.map((quote) => (quote as Quote).conversation_id), - ).size; - const uniqueConversationsB = new Set( - b.quotes.map((quote) => (quote as Quote).conversation_id), - ).size; - - // primary sort on the number of unique conversations - if (uniqueConversationsA !== uniqueConversationsB) { - return uniqueConversationsB - uniqueConversationsA; // descending order - } - - // secondary sort on the number of quotes - return b.quotes.length - a.quotes.length; // descending order - }); - } else { - throw new Error("Invalid sortBy value"); - } - } catch (err) { - console.error("Invalid sort", err); - return data; - } - }; - - const insightsExist = - insightsQuery && insightsQuery.data && insightsQuery.data.length > 0; const viewsExist = viewsQuery && viewsQuery.data && viewsQuery.data.length > 0; @@ -154,7 +113,7 @@ export const ProjectLibraryRoute = () => { ]} /> - {latestRun && latestRun.processing_completed_at ? ( + {latestRun ? ( @@ -242,7 +205,9 @@ export const ProjectLibraryRoute = () => { - {!opened && latestRun && latestRun.processing_status === "DONE" && ( + {!opened && latestRun + // && latestRun.processing_status === "DONE" + && ( }> @@ -258,67 +223,6 @@ export const ProjectLibraryRoute = () => { {viewsQuery.data && viewsQuery.data.map((v) => )} - - {projectQuery.data?.is_library_insights_enabled && ( - <> - - - <Trans>All Insights</Trans> - - - {!insightsExist && ( - }> - - - Your library is empty. Create a library to see your first - insights. - - - - )} - - {insightsQuery.data && insightsQuery.data.length > 0 && ( - <> - - - - - - -
- {insightsQuery.isLoading && ( - <> - - - - )} - {insightsQuery.data && - insightsQuery.data.length > 0 && - sortInsights(insightsQuery.data, sortBy).map((insight) => ( - - ))} -
- - )} - - )} ); }; diff --git a/echo/frontend/src/routes/project/library/ProjectLibraryAspect.tsx b/echo/frontend/src/routes/project/library/ProjectLibraryAspect.tsx index 4f1b3add..82ae30f4 100644 --- a/echo/frontend/src/routes/project/library/ProjectLibraryAspect.tsx +++ b/echo/frontend/src/routes/project/library/ProjectLibraryAspect.tsx @@ -13,23 +13,10 @@ import { Quote } from "../../../components/quote/Quote"; import { Markdown } from "@/components/common/Markdown"; import { useAspectById, useProjectById } from "@/lib/query"; import { Breadcrumbs } from "@/components/common/Breadcrumbs"; -import { useMemo } from "react"; import { useCopyAspect } from "@/hooks/useCopyAspect"; import { CopyIconButton } from "@/components/common/CopyIconButton"; import { sanitizeImageUrl } from "@/lib/utils"; -const dedupeQuotes = (quotes: QuoteAspect[]): QuoteAspect[] => { - const seen = new Set(); - - return quotes.filter((quote) => { - if (seen.has((quote.quote_id as Quote).id)) { - return false; - } - seen.add((quote.quote_id as Quote).id); - return true; - }); -}; - export const ProjectLibraryAspect = () => { const { projectId, viewId, aspectId } = useParams(); @@ -47,14 +34,6 @@ export const ProjectLibraryAspect = () => { }, }); - const quotes = useMemo( - () => - dedupeQuotes([ - ...(aspect?.representative_quotes ?? []), - ...(aspect?.quotes ?? []), - ]), - [aspect], - ); return ( @@ -100,23 +79,15 @@ export const ProjectLibraryAspect = () => { /> {!isLoading ? ( <> - {quotes.length > 0 && ( + {aspect?.aspect_segment?.length && aspect?.aspect_segment?.length > 0 && ( - <Trans>Quotes</Trans> + <Trans>Segments</Trans> )} - {quotes.map((quote: QuoteAspect) => ( + {aspect?.aspect_segment?.map((segment: AspectSegment) => ( q.id === quote.id, - ) - ? "border-gray-400" - : "" - } + key={segment.id} + data={segment} /> ))} diff --git a/echo/frontend/src/routes/project/library/ProjectLibraryInsight.tsx b/echo/frontend/src/routes/project/library/ProjectLibraryInsight.tsx deleted file mode 100644 index 28dacc3f..00000000 --- a/echo/frontend/src/routes/project/library/ProjectLibraryInsight.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Trans } from "@lingui/react/macro"; -import { useInsight } from "@/lib/query"; -import { - ActionIcon, - Container, - Divider, - Group, - LoadingOverlay, - Stack, - Text, - Title, -} from "@mantine/core"; -import { IconArrowBack } from "@tabler/icons-react"; -import { useParams } from "react-router-dom"; -import { Quote } from "../../../components/quote/Quote"; -import { Breadcrumbs } from "@/components/common/Breadcrumbs"; -import { I18nLink } from "@/components/common/i18nLink"; -import useCopyToRichText from "@/hooks/useCopyToRichText"; -import { useCopyInsight } from "@/hooks/useCopyInsight"; -import { CopyIconButton } from "@/components/common/CopyIconButton"; - -export const ProjectLibraryInsight = () => { - const { projectId, insightId } = useParams(); - - const insightQuery = useInsight(insightId ?? ""); - const { copied, copyInsight } = useCopyInsight(); - - if (!insightQuery.isLoading && !insightQuery.data) { - return ( - - - - - - - - - <Trans>Insight Library</Trans> - - - - - Insight not found - - - ); - } - - const insight = insightQuery.data; - const quotes = insight?.quotes; - - if (!insight || !quotes) { - return ; - } - - return ( - - - {/* - navigate(-1)}> - - - Insight Library */} - - {/* */} - - Insights - - ), - link: `/projects/${projectId}/library#insights`, - }, - { - label: ( - - {insight.title} - copyInsight(insight.id)} - copied={copied} - /> - - ), - }, - ]} - /> - - - {insight.summary} - - - - <Trans>Quotes</Trans> - - - {quotes.map((quote) => ( - - ))} - - - - ); -}; diff --git a/echo/server/dembrane/processing_status_utils.py b/echo/server/dembrane/processing_status_utils.py index 51e71ad8..522aeb61 100644 --- a/echo/server/dembrane/processing_status_utils.py +++ b/echo/server/dembrane/processing_status_utils.py @@ -19,9 +19,12 @@ class ProcessingStatus(Enum): def add_processing_status( conversation_id: Optional[str] = None, conversation_chunk_id: Optional[str] = None, + project_id: Optional[str] = None, + project_analysis_run_id: Optional[str] = None, event: Optional[str] = None, message: Optional[str] = None, duration_ms: Optional[int] = None, + parent_id: Optional[int] = None, ) -> int: logger.info(f"{event} {message} - {duration_ms}") with directus_client_context() as client: @@ -30,9 +33,12 @@ def add_processing_status( { "conversation_id": conversation_id, "conversation_chunk_id": conversation_chunk_id, + "project_id": project_id, + "project_analysis_run_id": project_analysis_run_id, "event": event, "message": message, "duration_ms": duration_ms, + "parent_id": parent_id, }, )["data"]["id"] @@ -76,6 +82,8 @@ class ProcessingStatusContext: def __init__( self, + project_id: Optional[str] = None, + project_analysis_run_id: Optional[str] = None, conversation_id: Optional[str] = None, conversation_chunk_id: Optional[str] = None, message: Optional[str] = None, @@ -89,15 +97,20 @@ def __init__( When no exception occurs, the context manager will log a COMPLETED event with the message and duration. Args: + project_id: The ID of the project. (str) + project_analysis_run_id: The ID of the project analysis run. (str) conversation_id: The ID of the conversation. (str) conversation_chunk_id: The ID of the conversation chunk. (str) message: The message to log. (str) event_prefix: The prefix of the event. (str) Conventionally, you will see this being set to method name. """ + self.project_id = project_id + self.project_analysis_run_id = project_analysis_run_id self.conversation_id = conversation_id self.conversation_chunk_id = conversation_chunk_id self.event_prefix = event_prefix self.message = message + self.exit_message: Optional[str] = None # Custom exit message self.start_time: float = 0.0 self.logger = getLogger(f"status.{self.event_prefix}") @@ -105,10 +118,16 @@ def __init__( self.processing_status_failed_id: Optional[int] = None self.processing_status_completed_id: Optional[int] = None + def set_exit_message(self, message: str) -> None: + """Set a custom message to be used in the exit event.""" + self.exit_message = message + def __enter__(self) -> "ProcessingStatusContext": # Log start event without duration self.start_time = time.time() self.processing_status_start_id = add_processing_status( + project_id=self.project_id, + project_analysis_run_id=self.project_analysis_run_id, conversation_id=self.conversation_id, conversation_chunk_id=self.conversation_chunk_id, event=f"{self.event_prefix}.started", @@ -125,28 +144,45 @@ def __exit__( ) -> Literal[False]: duration_ms = int((time.time() - self.start_time) * 1000) # if exception occurs, log FAILED event with error message and duration + if exc_type: - err_msg = str(exc_value) + if self.exit_message: + message = self.exit_message + f" -\n {str(exc_value)}" + else: + message = str(exc_value) + self.processing_status_failed_id = add_processing_status( + project_id=self.project_id, + project_analysis_run_id=self.project_analysis_run_id, conversation_id=self.conversation_id, conversation_chunk_id=self.conversation_chunk_id, event=f"{self.event_prefix}.failed", - message=err_msg, + message=message, duration_ms=duration_ms, + parent_id=self.processing_status_start_id, ) self.logger.error( - f"{self.processing_status_failed_id} {err_msg} (duration: {duration_ms / 1000}s) (started: {self.processing_status_start_id})" + f"{self.processing_status_failed_id} {message} (duration: {duration_ms / 1000}s) (started: {self.processing_status_start_id})" ) # if no exception occurs, log COMPLETED event with message and duration else: + if self.exit_message: + message = self.exit_message + else: + message = self.message if self.message else "" + self.processing_status_completed_id = add_processing_status( + project_id=self.project_id, + project_analysis_run_id=self.project_analysis_run_id, conversation_id=self.conversation_id, conversation_chunk_id=self.conversation_chunk_id, event=f"{self.event_prefix}.completed", - message=self.message, + message=message, duration_ms=duration_ms, + parent_id=self.processing_status_start_id, ) self.logger.info( - f"{self.processing_status_completed_id} {self.message} (duration: {duration_ms / 1000}s) (started: {self.processing_status_start_id})" + f"{self.processing_status_completed_id} {message} (duration: {duration_ms / 1000}s) (started: {self.processing_status_start_id})" ) + return False diff --git a/echo/server/dembrane/tasks.py b/echo/server/dembrane/tasks.py index dc550a3f..3bf4f13c 100644 --- a/echo/server/dembrane/tasks.py +++ b/echo/server/dembrane/tasks.py @@ -1,4 +1,5 @@ from json import JSONDecodeError +from typing import Optional from logging import getLogger import dramatiq @@ -13,7 +14,7 @@ from dramatiq.rate_limits.backends import RedisBackend as RateLimitRedisBackend from dramatiq.results.backends.redis import RedisBackend as ResultsRedisBackend -from dembrane.utils import get_utc_timestamp +from dembrane.utils import generate_uuid, get_utc_timestamp from dembrane.config import ( REDIS_URL, RUNPOD_WHISPER_API_KEY, @@ -22,7 +23,12 @@ RUNPOD_TOPIC_MODELER_API_KEY, ) from dembrane.sentry import init_sentry -from dembrane.directus import directus, directus_client_context +from dembrane.directus import ( + DirectusBadRequest, + DirectusServerError, + directus, + directus_client_context, +) from dembrane.transcribe import transcribe_conversation_chunk from dembrane.conversation_utils import ( collect_unfinished_conversations, @@ -424,73 +430,255 @@ def task_collect_and_finish_unfinished_conversations() -> None: def task_create_view( project_analysis_run_id: str, user_query: str, - user_query_context: str, + user_query_context: Optional[str], language: str, ) -> None: logger = getLogger("dembrane.tasks.task_create_view") + logger.info(f"Creating view for project_analysis_run_id: {project_analysis_run_id}") + + if not project_analysis_run_id or not user_query: + logger.error( + f"Invalid project_analysis_run_id: {project_analysis_run_id} or user_query: {user_query}" + ) + return + + logger.info(f"User query: {user_query}") + + project_id: Optional[str] = None + try: with directus_client_context() as client: - response = client.get_items( - "project_analysis_run", - { - "query": { - "filter": { - "id": project_analysis_run_id, - }, - "fields": ["project_id"], - } - }, - ) - project_id = response[0]["project_id"] - # get all segment ids from project_id - segments = client.get_items( - "project", - { - "query": { - "filter": { - "id": project_id, - }, - "fields": ["conversations.conversation_segments.id"], - } - }, - ) - segment_ids = list( - set( - [ - seg["id"] - for conv in segments[0]["conversations"] - for seg in conv["conversation_segments"] - ] + project_analysis_run = client.get_item("project_analysis_run", project_analysis_run_id) + + if not project_analysis_run: + logger.error(f"Project analysis run not found: {project_analysis_run_id}") + return + + project_id = project_analysis_run["project_id"] + except DirectusBadRequest as e: + logger.error( + f"Bad Directus request. Something item might be missing? analysis_run_id: {project_analysis_run_id} {e}" + ) + return + except DirectusServerError as e: + logger.error( + f"Can retry. Directus server down? analysis_run_id: {project_analysis_run_id} {e}" + ) + raise e from e + except Exception as e: + logger.error( + f"Can retry. Failed to get project_analysis_run: analysis_run_id: {project_analysis_run_id} {e}" + ) + raise e from e + + with ProcessingStatusContext( + project_analysis_run_id=project_analysis_run_id, + project_id=project_id, + event_prefix="task_create_view", + ) as status_ctx: + try: + with directus_client_context() as client: + # get all segment ids from project_id + segments = client.get_items( + "project", + { + "query": { + "filter": { + "id": project_id, + }, + "fields": ["conversations.conversation_segments.id"], + } + }, ) - ) + + if not segments or len(segments) == 0: + status_ctx.set_exit_message(f"No segments found for project: {project_id}") + logger.error(f"No segments found for project: {project_id}") + return + + segment_ids = list( + set( + [ + seg["id"] + for conv in segments[0]["conversations"] + for seg in conv["conversation_segments"] + ] + ) + ) + headers = { "Content-Type": "application/json", "Authorization": f"Bearer {RUNPOD_TOPIC_MODELER_API_KEY}", } + data = { "input": { + "project_analysis_run_id": project_analysis_run_id, "response_language": language, "segment_ids": segment_ids, - "user_prompt": "\n\n\n".join([user_query, user_query_context]), - "project_analysis_run_id": project_analysis_run_id, + "user_input": user_query, + "user_input_description": user_query_context or "", + "user_prompt": "\n\n\n".join([user_query, user_query_context or ""]), # depr } } + url = f"{str(RUNPOD_TOPIC_MODELER_URL).rstrip('/')}/run" + logger.debug(f"sending url to runpod: {url} with data: {data}") response = requests.post(url, headers=headers, json=data, timeout=600) - except Exception as e: - logger.error(f"Error in task_create_view: {e}") - raise e from e - return + # Handle the response + if not response.status_code == 200: + status_ctx.set_exit_message( + f"RunPod API returned status {response.status_code}: {response.text}" + ) + logger.error(f"RunPod API returned status {response.status_code}: {response.text}") + # TODO: handle class of error in runpod + raise Exception(f"RunPod API failed with status {response.status_code}") + + status_ctx.set_exit_message( + f"Successfully created view with {len(segment_ids)} segments" + ) + logger.info( + f"Successfully created view for project_analysis_run_id: {project_analysis_run_id}" + ) + logger.debug(f"RunPod response: {response.json()}") + return + + except DirectusBadRequest as e: + status_ctx.set_exit_message(f"Bad Directus request: {str(e)}") + logger.error(f"Bad Directus request. Something item might be missing? {e}") + return + + except DirectusServerError as e: + status_ctx.set_exit_message(f"Can retry. Directus server down? {e}") + logger.error(f"Can retry. Directus server down? {e}") + raise e from e + + except requests.exceptions.RequestException as e: + status_ctx.set_exit_message(f"Can retry. Network error calling RunPod API: {e}") + logger.error(f"Can retry. Network error calling RunPod API: {e}") + raise e from e -@dramatiq.actor(queue_name="cpu", priority=50) + except Exception as e: + status_ctx.set_exit_message( + f"Can retry. Views failed to create for unknown reason: {e}" + ) + logger.error(f"Can retry. Views failed to create for unknown reason: {e}") + raise e from e + + +@dramatiq.actor(queue_name="network", priority=50) def task_create_project_library(project_id: str, language: str) -> None: logger = getLogger("dembrane.tasks.task_create_project_library") - logger.warning( - f"NOT IMPLEMENTED: task_create_project_library (project_id: {project_id}, language: {language})" - ) - return + + with ProcessingStatusContext( + project_id=project_id, + event_prefix="task_create_project_library", + ) as status_ctx: + logger.info(f"Creating project library for project: {project_id}") + + try: + with directus_client_context() as client: + project = client.get_item("project", project_id) + + if not project: + status_ctx.set_exit_message(f"Project not found: {project_id}") + logger.error(f"Project not found: {project_id}") + return + + new_run_id = client.create_item( + "project_analysis_run", + { + "id": generate_uuid(), + "project_id": project_id, + }, + )["data"]["id"] + + status_ctx.set_exit_message(f"Successfully created library: {new_run_id}") + logger.info(f"Successfully created library: {new_run_id}") + except DirectusBadRequest as e: + status_ctx.set_exit_message(f"Bad Directus request: {str(e)}") + logger.error(f"Bad Directus request: {str(e)}") + return + except DirectusServerError as e: + status_ctx.set_exit_message(f"Can retry. Directus server down? {e}") + logger.error(f"Can retry. Directus server down? {e}") + raise e from e + except Exception as e: + status_ctx.set_exit_message(f"Can retry. Failed to create project analysis run: {e}") + logger.error(f"Can retry. Failed to create project analysis run: {e}") + raise e from e + + DEFAULT_PROMPTS = { + "en": [ + { + "user_query": "Power Plays", + "user_query_context": "Identify who drives conversations, influences decisions, and shapes outcomes. Focus on detecting authority patterns, persuasion dynamics, and organizational hierarchy signals. Analyze speaking time distribution, interruption patterns, and whose ideas get adopted or dismissed. Map the power architecture of decision-making processes. Expected aspects: 3-7 for small datasets, 5-12 for medium datasets, 8-15 for large datasets. Processing hint: Look for distinct power centers, influence networks, and decision-making bottlenecks. Quality threshold: Each aspect should represent a unique power dynamic with clear behavioral evidence.", + }, + { + "user_query": "Smart vs. Loud", + "user_query_context": "Distinguish between evidence-backed arguments and opinion-based statements. Identify participants who support claims with data, examples, and logical reasoning versus those relying on volume, authority, or emotional appeals. Measure argument quality, source credibility, and reasoning depth across different speakers. Expected aspects: 2-5 for small datasets, 4-8 for medium datasets, 6-12 for large datasets. Processing hint: Focus on argument structure, evidence types, and persuasion mechanisms. Quality threshold: Each aspect should demonstrate clear qualitative differences in reasoning approaches.", + }, + { + "user_query": "Mind Changes", + "user_query_context": "Track position shifts, consensus formation, and persuasion effectiveness throughout conversations. Identify moments where participants change their stance, what triggers these shifts, and which arguments successfully influence others. Map the evolution from initial disagreement to final alignment or persistent division. Expected aspects: 2-6 for small datasets, 4-10 for medium datasets, 6-14 for large datasets. Processing hint: Identify temporal patterns, trigger events, and persuasion pathways. Quality threshold: Each aspect should show distinct change mechanisms with clear before/after states.", + }, + { + "user_query": "Trust Signals", + "user_query_context": "Analyze what evidence types, sources, and expertise different participants find credible and compelling. Identify which data sources carry weight, whose opinions influence others, and how different groups validate information. Reveal the implicit credibility hierarchy that drives decision-making. Expected aspects: 3-6 for small datasets, 5-10 for medium datasets, 7-13 for large datasets. Processing hint: Map credibility networks, authority recognition patterns, and validation processes. Quality threshold: Each aspect should represent distinct credibility criteria with supporting behavioral evidence.", + }, + { + "user_query": "Getting Stuff Done", + "user_query_context": "Measure conversation momentum toward actionable outcomes. Track progression from problem identification to concrete solutions, next steps, and ownership assignment. Identify which discussions generate accountability versus those that circle without resolution. Focus on decision-making effectiveness and implementation planning. Expected aspects: 2-5 for small datasets, 3-8 for medium datasets, 5-11 for large datasets. Processing hint: Focus on resolution patterns, accountability mechanisms, and implementation pathways. Quality threshold: Each aspect should demonstrate measurable progress toward concrete outcomes.", + }, + ], + "nl": [ + { + "user_query": "Wie heeft de touwtjes in handen", + "user_query_context": "Kijk wie de gesprekken stuurt, beslissingen beïnvloedt en de uitkomsten bepaalt. Let op wie het meeste spreekt, wie anderen onderbreekt en wiens ideeën worden opgepakt. Breng in kaart hoe de machtsbalans werkt en wie echt de lakens uitdeelt. Verwachte aspecten: 3-7 voor kleine datasets, 5-12 voor middelgrote datasets, 8-15 voor grote datasets. Verwerkingstip: Zoek naar duidelijke machtscentra, invloedsnetwerken en besluitvormingsknelpunten. Kwaliteitsdrempel: Elk aspect moet een unieke machtsdynamiek tonen met duidelijk gedragsbewijs.", + }, + { + "user_query": "Slim praten vs. hard praten", + "user_query_context": "Onderscheid tussen argumenten met bewijs en loze praatjes. Wie ondersteunt hun punt met data en voorbeelden? Wie vertrouwt vooral op volume, status of emoties? Ontdek het verschil tussen echte inhoud en veel lawaai maken. Verwachte aspecten: 2-5 voor kleine datasets, 4-8 voor middelgrote datasets, 6-12 voor grote datasets. Verwerkingstip: Focus op argumentstructuur, bewijstypen en overtuigingsmechanismen. Kwaliteitsdrempel: Elk aspect moet duidelijke kwaliteitsverschillen in redeneerbenaderingen tonen.", + }, + { + "user_query": "Van mening veranderen", + "user_query_context": "Volg wie van standpunt wisselt tijdens gesprekken. Wat zorgt ervoor dat mensen hun mening bijstellen? Welke argumenten werken echt? Zie hoe discussies evoleren van onenigheid naar overeenstemming (of juist niet). Verwachte aspecten: 2-6 voor kleine datasets, 4-10 voor middelgrote datasets, 6-14 voor grote datasets. Verwerkingstip: Identificeer tijdspatronen, trigger events en overtuigingstrajecten. Kwaliteitsdrempel: Elk aspect moet duidelijke veranderingsmechanismen tonen met voor/na situaties.", + }, + { + "user_query": "Wat mensen geloven", + "user_query_context": "Analyseer waar verschillende mensen op vertrouwen. Welke bronnen vinden ze geloofwaardig? Wiens mening telt? Hoe valideren verschillende groepen informatie? Ontdek de onzichtbare geloofwaardigheidshiërarchie die beslissingen stuurt. Verwachte aspecten: 3-6 voor kleine datasets, 5-10 voor middelgrote datasets, 7-13 voor grote datasets. Verwerkingstip: Breng geloofwaardigheidsnetwerken, autoriteitsherkenning en validatieprocessen in kaart. Kwaliteitsdrempel: Elk aspect moet verschillende geloofwaardigheidscriteria tonen met gedragsbewijs.", + }, + { + "user_query": "Shit voor elkaar krijgen", + "user_query_context": "Meet hoe gesprekken leiden tot echte actie. Gaan discussies van probleem naar oplossing? Wie pakt wat op? Welke gesprekken leiden tot resultaat en welke draaien maar rond zonder uitkomst? Focus op wat er daadwerkelijk gebeurt na het praten. Verwachte aspecten: 2-5 voor kleine datasets, 3-8 voor middelgrote datasets, 5-11 voor grote datasets. Verwerkingstip: Focus op oplossingspatronen, verantwoordelijkheidsmechanismen en implementatietrajecten. Kwaliteitsdrempel: Elk aspect moet meetbare vooruitgang naar concrete resultaten tonen.", + }, + ], + } + + messages = [] + + for prompt in DEFAULT_PROMPTS[language]: + messages.append( + task_create_view.message( + project_analysis_run_id=new_run_id, + user_query=prompt["user_query"], + user_query_context=prompt["user_query_context"], + language=language, + ) + ) + + group(messages).run() + + status_ctx.set_exit_message( + f"Successfully created {len(messages)} views for project: {project_id}" + ) + logger.info( + f"Successfully created {len(messages)} views for project: {project_id} (language: {language})" + ) + + return @dramatiq.actor(queue_name="network", priority=50)