chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1 @@
export { MessageActions } from './message-actions'
@@ -0,0 +1,294 @@
'use client'
import { memo, useEffect, useRef, useState } from 'react'
import {
Check,
ChipModal,
ChipModalBody,
ChipModalField,
ChipModalFooter,
ChipModalHeader,
cn,
Duplicate,
ThumbsDown,
ThumbsUp,
Tooltip,
toast,
} from '@sim/emcn'
import { GitBranch } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
import { useSubmitCopilotFeedback } from '@/hooks/queries/copilot-feedback'
import { useForkMothershipChat } from '@/hooks/queries/mothership-chats'
import { useFolderStore } from '@/stores/folders/store'
const SPECIAL_TAGS = 'thinking|options|usage_upgrade|credential|mothership-error|file'
function toPlainText(raw: string): string {
return (
raw
// Strip special tags and their contents
.replace(new RegExp(`<\\/?(${SPECIAL_TAGS})(?:>[\\s\\S]*?<\\/(${SPECIAL_TAGS})>|>)`, 'g'), '')
// Strip markdown
.replace(/^#{1,6}\s+/gm, '')
.replace(/\*\*(.+?)\*\*/g, '$1')
.replace(/\*(.+?)\*/g, '$1')
.replace(/`{3}[\s\S]*?`{3}/g, '')
.replace(/`(.+?)`/g, '$1')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
.replace(/^[>\-*]\s+/gm, '')
.replace(/!\[[^\]]*\]\([^)]+\)/g, '')
// Normalize whitespace
.replace(/\n{3,}/g, '\n\n')
.trim()
)
}
const ICON_CLASS = 'size-[14px]'
const BUTTON_CLASS =
'flex size-[26px] items-center justify-center rounded-[6px] text-[var(--text-icon)] transition-colors hover-hover:bg-[var(--surface-hover)] focus-visible:outline-none'
interface MessageActionsProps {
content: string
userQuery?: string
requestId?: string
messageId?: string
}
export const MessageActions = memo(function MessageActions({
content,
userQuery,
requestId,
messageId,
}: MessageActionsProps) {
const router = useRouter()
const params = useParams<{ workspaceId: string }>()
const { chatId } = useChatSurface()
const [copied, setCopied] = useState(false)
const [copiedRequestId, setCopiedRequestId] = useState(false)
const [pendingFeedback, setPendingFeedback] = useState<'up' | 'down' | null>(null)
const [feedbackText, setFeedbackText] = useState('')
const resetTimeoutRef = useRef<number | null>(null)
const requestIdTimeoutRef = useRef<number | null>(null)
const submitFeedback = useSubmitCopilotFeedback()
const forkChat = useForkMothershipChat(params.workspaceId)
useEffect(() => {
return () => {
if (resetTimeoutRef.current !== null) {
window.clearTimeout(resetTimeoutRef.current)
}
if (requestIdTimeoutRef.current !== null) {
window.clearTimeout(requestIdTimeoutRef.current)
}
}
}, [])
const copyToClipboard = async () => {
if (!content) return
const text = toPlainText(content)
if (!text) return
try {
await navigator.clipboard.writeText(text)
setCopied(true)
if (resetTimeoutRef.current !== null) {
window.clearTimeout(resetTimeoutRef.current)
}
resetTimeoutRef.current = window.setTimeout(() => setCopied(false), 1500)
} catch {
/* clipboard unavailable */
}
}
const copyRequestId = async () => {
if (!requestId) return
try {
await navigator.clipboard.writeText(requestId)
setCopiedRequestId(true)
if (requestIdTimeoutRef.current !== null) {
window.clearTimeout(requestIdTimeoutRef.current)
}
requestIdTimeoutRef.current = window.setTimeout(() => setCopiedRequestId(false), 1500)
} catch {
/* clipboard unavailable */
}
}
const handleFeedbackClick = (type: 'up' | 'down') => {
if (chatId && userQuery) {
setPendingFeedback(type)
setFeedbackText('')
setCopiedRequestId(false)
}
}
const handleSubmitFeedback = () => {
if (!pendingFeedback || !chatId || !userQuery) return
const text = feedbackText.trim()
if (!text) {
setPendingFeedback(null)
setFeedbackText('')
return
}
submitFeedback.mutate({
chatId,
userQuery,
agentResponse: content,
isPositiveFeedback: pendingFeedback === 'up',
feedback: text,
})
setPendingFeedback(null)
setFeedbackText('')
}
const handleModalClose = (open: boolean) => {
if (!open) {
setPendingFeedback(null)
setFeedbackText('')
setCopiedRequestId(false)
}
}
const handleFork = async () => {
if (!chatId || !messageId || forkChat.isPending) return
try {
const result = await forkChat.mutateAsync({ chatId, upToMessageId: messageId })
useFolderStore.getState().clearChatSelection()
router.push(`/workspace/${params.workspaceId}/chat/${result.id}`)
} catch {
toast.error('Failed to fork chat')
}
}
const hasContent = Boolean(content)
const canSubmitFeedback = Boolean(chatId && userQuery)
const canFork = false
if (!hasContent && !canSubmitFeedback && !canFork) return null
return (
<>
<div className='flex items-center gap-0.5'>
{hasContent && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
type='button'
aria-label='Copy message'
onClick={copyToClipboard}
className={BUTTON_CLASS}
>
{copied ? <Check className={ICON_CLASS} /> : <Duplicate className={ICON_CLASS} />}
</button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
{copied ? 'Copied message' : 'Copy message'}
</Tooltip.Content>
</Tooltip.Root>
)}
{canSubmitFeedback && (
<>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
type='button'
aria-label='Like'
onClick={() => handleFeedbackClick('up')}
className={BUTTON_CLASS}
>
<ThumbsUp className={ICON_CLASS} />
</button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Good response</Tooltip.Content>
</Tooltip.Root>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
type='button'
aria-label='Dislike'
onClick={() => handleFeedbackClick('down')}
className={BUTTON_CLASS}
>
<ThumbsDown className={ICON_CLASS} />
</button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Bad response</Tooltip.Content>
</Tooltip.Root>
</>
)}
{canFork && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
type='button'
aria-label='Fork from here'
onClick={handleFork}
disabled={forkChat.isPending}
className={cn(BUTTON_CLASS, forkChat.isPending && 'cursor-not-allowed opacity-50')}
>
<GitBranch className={ICON_CLASS} />
</button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Fork from here</Tooltip.Content>
</Tooltip.Root>
)}
</div>
<ChipModal
open={pendingFeedback !== null}
onOpenChange={handleModalClose}
srTitle='Give feedback'
>
<ChipModalHeader onClose={() => handleModalClose(false)}>Give feedback</ChipModalHeader>
<ChipModalBody>
<div className='flex items-start justify-between gap-2 px-2'>
<p className='font-medium text-[var(--text-secondary)] text-sm'>
{pendingFeedback === 'up' ? 'What did you like?' : 'What could be improved?'}
</p>
{pendingFeedback === 'down' && requestId && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
type='button'
aria-label='Copy request ID'
onClick={copyRequestId}
className='flex size-[22px] shrink-0 items-center justify-center rounded-full text-[var(--text-icon)] transition-colors hover-hover:bg-[var(--surface-hover)] focus-visible:outline-none'
>
{copiedRequestId ? (
<Check className='size-[14px]' />
) : (
<Duplicate className='size-[14px]' />
)}
</button>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
{copiedRequestId ? 'Copied request ID' : 'Copy request ID'}
</Tooltip.Content>
</Tooltip.Root>
)}
</div>
<ChipModalField
type='textarea'
title='Feedback'
value={feedbackText}
onChange={setFeedbackText}
rows={6}
minHeight={140}
resizable
placeholder={
pendingFeedback === 'up'
? 'Tell us what was helpful...'
: 'Tell us what went wrong...'
}
/>
</ChipModalBody>
<ChipModalFooter
onCancel={() => handleModalClose(false)}
primaryAction={{
label: 'Submit',
onClick: handleSubmitFeedback,
}}
/>
</ChipModal>
</>
)
})