Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
FASTEMBED_CACHE_PATH: ${{ github.workspace }}/.cache/fastembed
permissions:
contents: read
actions: write

steps:
- name: Checkout
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/deploy-pazaakworld.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ jobs:
exit 1
fi

- name: Verify Trask public API health (worker + upstream)
if: hashFiles('apps/holocron-web/package.json') != ''
env:
TRASK_API_BASE: ${{ vars.TRASK_API_BASE }}
run: bash scripts/check_trask_public_api.sh

- name: Build Holocron web (Trask Q&A SPA)
if: hashFiles('apps/holocron-web/package.json') != ''
env:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/trask-worker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,9 @@ jobs:
deploy_args+=(--var "TRASK_RESEARCHWIZARD_BASE_URL:${TRASK_RESEARCHWIZARD_BASE_URL}")
fi
pnpm dlx wrangler@4.87.0 deploy "${deploy_args[@]}"

- name: Verify deployed worker health
if: steps.cf_check.outputs.configured == 'true'
env:
TRASK_API_BASE: ${{ vars.TRASK_API_BASE != '' && vars.TRASK_API_BASE || 'https://trask-worker.bocloud.workers.dev' }}
run: bash scripts/check_trask_public_api.sh
Binary file modified apps/holocron-web/public/holocron/holocron-artifact.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 54 additions & 4 deletions apps/holocron-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HolocronModelPicker } from '@/components/HolocronModelPicker'
import { SourceWeightsDialog } from '@/components/SourceWeightsDialog'
import { KeyboardShortcutsDialog } from '@/components/KeyboardShortcutsDialog'
import { TopNav, type HolocronSessionUi } from '@/components/TopNav'
import { TraskBackendStatus } from '@/components/TraskBackendStatus'
import { HolocronGlyph } from '@/components/HolocronGlyph'
import {
HolocronSanctum,
Expand Down Expand Up @@ -44,12 +45,14 @@ import {
traskListModels,
traskLogout,
traskPollIterationSignal,
traskErrorMessageFromUnknown,
traskUsesSameOriginApi,
type TraskHistoryLiveEventDto,
type TraskHistoryRecordDto,
type TraskSessionDto,
} from '@/lib/trask-api'
import { priorUserQuestionsFromOtherThreads } from '@/lib/starter-suggestions'
import { holocronAssetUrl } from '@/lib/asset-url'

const CONVERSATIONS_KEY = 'qa-conversations-v2'
const LEGACY_CONVERSATIONS_KEY = 'qa-conversations'
Expand All @@ -58,6 +61,8 @@ const RESEARCH_RETRY_BASE_MS = 5_000
const RESEARCH_RETRY_MAX_MS = 90_000
/** ~2.5 min of missing thread rows before re-dispatching (research can run up to ~90s). */
const RESEARCH_POLL_FAILURE_GIVE_UP = 48
/** Stop silent retries and surface a failed assistant message when the API stays unreachable. */
const RESEARCH_CONNECTION_FAILURE_MAX_ATTEMPTS = 8
const SIDEBAR_WIDTH_MIN = 260
const SIDEBAR_WIDTH_MAX = 520

Expand Down Expand Up @@ -692,6 +697,11 @@ function App() {
const mobileSidebarToggleButtonRef = useRef<HTMLButtonElement>(null)
const lastFocusedElementRef = useRef<HTMLElement | null>(null)

useEffect(() => {
const artifactUrl = holocronAssetUrl('holocron/holocron-artifact.png')
document.documentElement.style.setProperty('--holocron-artifact-url', `url("${artifactUrl}")`)
}, [])

const detachFromBottom = useCallback(() => {
shouldStickToBottomRef.current = false
if (!scrollRef.current) return
Expand Down Expand Up @@ -1538,7 +1548,31 @@ function App() {
replaceResearchAssistantMessage(job, createFailedMessageFromTraskRecord(record, job.queryType))
setResearchJobs((current) => normalizeResearchJobs(current).filter((candidate) => candidate.clientId !== job.clientId))
},
[activeConversationId, replaceResearchAssistantMessage, setResearchJobs],
[replaceResearchAssistantMessage, setResearchJobs],
)

const failResearchJobFromConnectionError = useCallback(
(job: HolocronResearchJob, errorMessage: string) => {
const nowIso = new Date().toISOString()
const record: TraskHistoryRecordDto = {
queryId: job.serverQueryId ?? job.clientId,
threadId: job.threadId,
userId: 'holocron-web',
query: job.question,
status: 'failed',
answer: null,
sources: [],
error: errorMessage,
createdAt: new Date(job.createdAt).toISOString(),
completedAt: nowIso,
liveTrace: [
{ at: nowIso, phase: 'error', detail: errorMessage },
{ at: nowIso, phase: 'dispatch', detail: 'Could not reach the Holocron research API.' },
],
}
failResearchJob(job, record)
},
[failResearchJob],
)

const completeResearchJob = useCallback(
Expand Down Expand Up @@ -1722,10 +1756,24 @@ function App() {
pollFailures: 0,
nextAttemptAt: Date.now() + 1_500,
})
} catch {
if (!cancelled) {
retryLater(job)
} catch (err) {
if (cancelled || !isJobCurrent()) return
const errorMessage = traskErrorMessageFromUnknown(err)
if (job.attemptCount >= RESEARCH_CONNECTION_FAILURE_MAX_ATTEMPTS) {
failResearchJobFromConnectionError(job, errorMessage)
return
}
replaceResearchAssistantMessage(job, createResearchLoadingMessage(
job.assistantMessageId,
job.question,
job.createdAt,
job.queryType,
[
localResearchStep('queued', 'Persisted locally; continuing in the background.'),
localResearchStep('retry', errorMessage),
],
))
retryLater(job)
} finally {
researchWorkersRef.current.delete(job.clientId)
researchConversationWorkersRef.current.delete(job.conversationId)
Expand Down Expand Up @@ -1762,6 +1810,7 @@ function App() {
activeConversationId,
completeResearchJob,
failResearchJob,
failResearchJobFromConnectionError,
replaceResearchAssistantMessage,
researchJobs,
setResearchJobs,
Expand Down Expand Up @@ -2010,6 +2059,7 @@ function App() {
return (
<div className="h-dvh flex flex-col bg-background relative overflow-x-hidden overflow-y-hidden">
<TopNav holocronSession={holocronSession} onHolocronLogout={handleHolocronLogout} />
<TraskBackendStatus />

<div className="flex-1 flex min-h-0 pt-14 relative">
<div className="holocron-atmosphere pointer-events-none overflow-visible" aria-hidden>
Expand Down
3 changes: 2 additions & 1 deletion apps/holocron-web/src/components/HolocronGlyph.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Sparkle } from '@phosphor-icons/react'
import { useState } from 'react'
import { holocronAssetUrl } from '@/lib/asset-url'

type HolocronGlyphProps = {
variant: 'header' | 'hero'
Expand Down Expand Up @@ -28,7 +29,7 @@ export function HolocronGlyph({ variant, className = '' }: HolocronGlyphProps) {

return (
<img
src="/holocron/holocron-artifact.png"
src={holocronAssetUrl('holocron/holocron-artifact.png')}
alt=""
className={`${dim} ${className}`}
onError={() => setUseFallback(true)}
Expand Down
3 changes: 2 additions & 1 deletion apps/holocron-web/src/components/HolocronSanctum.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react'
import { holocronAssetUrl } from '@/lib/asset-url'
import { fluxTokensFromQuery, holocronMulberry32 } from '@/lib/holocron-live'

const HOLOCRON_ARTIFACT_SRC = '/holocron/holocron-artifact.png'
const HOLOCRON_ARTIFACT_SRC = holocronAssetUrl('holocron/holocron-artifact.png')

export type HolocronActivityMood = 'idle' | 'retrieve' | 'success' | 'warn' | 'hot'

Expand Down
Loading
Loading