mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
183 lines
7.7 KiB
TypeScript
183 lines
7.7 KiB
TypeScript
import { useActions, useValues } from 'kea'
|
|
|
|
import { IconPencil } from '@hanzo/icons'
|
|
import { Banner, Button } from '@hanzo/elements'
|
|
|
|
import { NotFound } from 'lib/components/NotFound'
|
|
import { useFeatureFlag } from 'lib/hooks/useFeatureFlag'
|
|
import { Spinner } from 'lib/elements/Spinner/Spinner'
|
|
import { SceneExport } from 'scenes/sceneTypes'
|
|
import { urls } from 'scenes/urls'
|
|
|
|
import { SceneTitleSection } from '~/layout/scenes/components/SceneTitleSection'
|
|
|
|
import { StreamlitAppLoading } from './StreamlitAppLoading'
|
|
import { StreamlitAppLogicProps, streamlitAppLogic } from './streamlitAppLogic'
|
|
|
|
export const scene: SceneExport = {
|
|
component: StreamlitAppViewer,
|
|
logic: streamlitAppLogic,
|
|
paramsToProps: ({ params: { id } }): StreamlitAppLogicProps => ({
|
|
shortId: id as string,
|
|
}),
|
|
}
|
|
|
|
const GENERIC_ERROR_MESSAGE = 'Something went wrong starting your app. Check the logs or contact support.'
|
|
|
|
// Keeps raw stack traces and DB errors out of the viewer UI.
|
|
const ERROR_PREFIX_MESSAGES: Array<[string, string]> = [
|
|
[
|
|
'Max restart count',
|
|
"We've hit the restart limit for this app. Try editing the source and uploading a new version.",
|
|
],
|
|
['Auth proxy failed to become ready', "The app's auth proxy didn't start in time. Try restarting."],
|
|
['Startup timed out', 'Startup timed out. Try restarting.'],
|
|
['Sandbox terminated', 'The sandbox stopped unexpectedly. Restart to try again.'],
|
|
['No active version', 'No version uploaded yet. Upload a zip file from the edit page.'],
|
|
]
|
|
|
|
function curateErrorMessage(raw?: string | null): string {
|
|
if (!raw) {
|
|
return GENERIC_ERROR_MESSAGE
|
|
}
|
|
for (const [prefix, friendly] of ERROR_PREFIX_MESSAGES) {
|
|
if (raw.startsWith(prefix)) {
|
|
return friendly
|
|
}
|
|
}
|
|
return GENERIC_ERROR_MESSAGE
|
|
}
|
|
|
|
export function StreamlitAppViewer(props: Record<string, any>): JSX.Element {
|
|
const streamlitAppsFeatureFlagEnabled = useFeatureFlag('STREAMLIT_APPS')
|
|
const shortId = props.id as string
|
|
const { streamlitApp, streamlitAppLoading, appStatus, iframeSrc, sandboxStatus, connectError } = useValues(
|
|
streamlitAppLogic({ shortId })
|
|
)
|
|
const { startApp, restartApp, loadConnectInfo } = useActions(streamlitAppLogic({ shortId }))
|
|
|
|
if (!streamlitAppsFeatureFlagEnabled) {
|
|
return <NotFound object="page" />
|
|
}
|
|
|
|
if (streamlitAppLoading && !streamlitApp) {
|
|
return (
|
|
<div className="flex items-center justify-center py-20">
|
|
<Spinner className="text-4xl" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!streamlitApp) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
<h2 className="text-lg font-semibold mb-2">App not found</h2>
|
|
<p className="text-muted mb-4">This Streamlit app doesn't exist or you don't have access to it.</p>
|
|
<Button type="primary" to={urls.streamlitApps()}>
|
|
Back to apps
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col h-full">
|
|
<SceneTitleSection
|
|
name={streamlitApp.name}
|
|
resourceType={{ type: 'streamlit_app' }}
|
|
actions={
|
|
<Button
|
|
type="secondary"
|
|
size="small"
|
|
icon={<IconPencil />}
|
|
to={urls.streamlitAppEdit(streamlitApp.short_id)}
|
|
>
|
|
Edit
|
|
</Button>
|
|
}
|
|
/>
|
|
{/* Nudge on version mismatch between activated and running versions. */}
|
|
{appStatus === 'running' &&
|
|
streamlitApp.active_version &&
|
|
sandboxStatus?.version_number != null &&
|
|
streamlitApp.active_version.version_number !== sandboxStatus.version_number && (
|
|
<Banner
|
|
type="info"
|
|
className="mb-2"
|
|
action={{
|
|
children: 'Restart now',
|
|
onClick: () => restartApp(),
|
|
}}
|
|
>
|
|
New version available — restart to apply the changes.
|
|
</Banner>
|
|
)}
|
|
<div className="flex-1 min-h-0">
|
|
{appStatus === 'starting' && <StreamlitAppLoading />}
|
|
|
|
{appStatus === 'stopping' && <StreamlitAppLoading message="Stopping the app..." />}
|
|
|
|
{appStatus === 'running' && iframeSrc && (
|
|
<iframe
|
|
src={iframeSrc}
|
|
// allow-same-origin lets Streamlit read its own cookies/sessionStorage.
|
|
// Insights is a different origin, so the proxy's scope check is the real boundary.
|
|
sandbox="allow-scripts allow-forms allow-popups allow-modals allow-same-origin"
|
|
referrerPolicy="no-referrer"
|
|
className="w-full h-full border-0 rounded-lg"
|
|
style={{ minHeight: '600px' }}
|
|
title={streamlitApp.name}
|
|
/>
|
|
)}
|
|
|
|
{appStatus === 'running' && !iframeSrc && connectError && (
|
|
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
<h2 className="text-lg font-semibold mb-2">Connection failed</h2>
|
|
<p className="text-muted mb-6">{connectError}</p>
|
|
<Button type="primary" onClick={() => loadConnectInfo()}>
|
|
Retry
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{appStatus === 'running' && !iframeSrc && !connectError && <StreamlitAppLoading />}
|
|
|
|
{appStatus === 'error' && (
|
|
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
<h2 className="text-lg font-semibold mb-2">App failed to start</h2>
|
|
<p className="text-muted mb-4 max-w-lg">{curateErrorMessage(sandboxStatus?.last_error)}</p>
|
|
<div className="flex gap-2">
|
|
<Button type="primary" onClick={restartApp}>
|
|
Try again
|
|
</Button>
|
|
<Button type="secondary" to={urls.streamlitAppEdit(streamlitApp.short_id)}>
|
|
View settings
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{appStatus === 'stopped' && !streamlitApp.active_version && (
|
|
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
<h2 className="text-lg font-semibold mb-2">No version uploaded</h2>
|
|
<p className="text-muted mb-4">Upload a zip file to get started.</p>
|
|
<Button type="primary" to={urls.streamlitAppEdit(streamlitApp.short_id)}>
|
|
Upload
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{appStatus === 'stopped' && streamlitApp.active_version && (
|
|
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
<h2 className="text-lg font-semibold mb-2">App is stopped</h2>
|
|
<p className="text-muted mb-4">Start the app to view it.</p>
|
|
<Button type="primary" onClick={startApp}>
|
|
Start app
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|