import { useEffect, useState } from 'react'
import { apiFetch } from '@core/lib/api'
import { Button } from '@core/components/ui/button'
import { Textarea } from '@core/components/ui/textarea'
import { ScrollArea } from '@core/components/ui/scroll-area'
import { Bot, FileText, MessageSquare, Pencil, Send, User } from 'lucide-react'
function formatDate(value?: string | null): string | null {
if (!value) return null
const d = new Date(value)
if (Number.isNaN(d.getTime())) return value
return d.toLocaleString()
}
function authorName(id: string | null, people: any[]): string {
if (!id) return 'Unknown'
if (id === '00000000000') return 'Devin'
const person = people.find((p: any) => p.id === id)
return person?.full_name || id
}
interface MessageProps {
author: string
content: string
createdAt?: string
isDescription?: boolean
isDevin?: boolean
isAgent?: boolean
}
function Message({ author, content, createdAt, isDescription, isDevin, isAgent }: MessageProps) {
const Icon = isDescription ? FileText : (isDevin || isAgent) ? Bot : User
return (
{author}
{createdAt && {formatDate(createdAt)}}
)
}
interface DescriptionMessageProps {
author: string
display: string
raw: string
createdAt?: string
isDevin?: boolean
onUpdate: (value: string) => Promise
}
function DescriptionMessage({ author, display, raw, createdAt, isDevin, onUpdate }: DescriptionMessageProps) {
const [editing, setEditing] = useState(false)
const [draft, setDraft] = useState(raw)
const [saving, setSaving] = useState(false)
useEffect(() => {
setDraft(raw)
}, [raw])
const handleSave = async () => {
setSaving(true)
try {
await onUpdate(draft)
setEditing(false)
} finally {
setSaving(false)
}
}
const Icon = isDevin ? Bot : FileText
return (
{author}
{createdAt && {formatDate(createdAt)}}
{editing ? (
) : (
setEditing(true)}
/>
{display || 'No description.'}
)}
)
}
interface WorkItemThreadProps {
item: any
comments: any[]
loadingComments: boolean
aiMode?: boolean
onToggleAiMode?: () => void
onPost: (content: string) => Promise
onPostAi: (content: string) => Promise
onUpdateDescription: (description: string) => Promise
itemLabel?: string
emptyMessage?: string
placeholder?: string
}
export function WorkItemThread({
item,
comments,
loadingComments,
aiMode = false,
onToggleAiMode,
onPost,
onPostAi,
onUpdateDescription,
itemLabel = 'Work item',
emptyMessage = 'Create the work item to start the conversation.',
placeholder = 'Add a message…',
}: WorkItemThreadProps) {
const [people, setPeople] = useState([])
const [newComment, setNewComment] = useState('')
const [posting, setPosting] = useState(false)
const hasItem = !!item
useEffect(() => {
apiFetch('/api/admin-data?action=list&entity=people&limit=200')
.then(r => r.json())
.then(json => setPeople(Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : []))
.catch(() => setPeople([]))
}, [])
const handlePost = async () => {
if (!newComment.trim()) return
setPosting(true)
try {
if (aiMode) {
await onPostAi(newComment.trim())
} else {
await onPost(newComment.trim())
}
setNewComment('')
} catch (err) {
console.error('Failed to post message', err)
} finally {
setPosting(false)
}
}
return (
Conversation
{!loadingComments && (
{comments.length} messages
)}
{loadingComments ? (
Loading messages…
) : !hasItem ? (
) : (
{comments.map((c: any, idx: number) => {
const isAgent = c.data?.message_type === 'agent'
const isAiTarget = c.data?.ai_target === true
return (
)
})}
)}
{aiMode && (
Messages are sent to the AI agent.
)}
)
}