Skip to content
Closed
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@
# `OBSERVABILITY_ENABLED=1` uses the same unified switch as the backend. When
# enabled by the LexVoice runtime, browser-side probes publish LiveKit data
# packets for the local observability report.
# `ACTIVITY_INTELLIGENCE_PUBLIC_URL` enables the Team Pulse button and points
# its embedded panel at the aggregate-only public activity page.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ input source as `lexvoice-${INPUT_SOURCE}-agent`; an explicit `AGENT_NAME`
always wins. Standalone deployments that do not run a matching agent worker
should set `AGENT_NAME` to the worker name they expect to dispatch.

Set `ACTIVITY_INTELLIGENCE_PUBLIC_URL` in the LexVoice repository `.env` to
show the “团队动态” entry in the voice UI. The URL must point to Activity
Intelligence's aggregate `/public` page; the embedded surface intentionally
excludes names, personal arrival times, and unobserved-member details.

Vision-related frontend variables use the `*_VISION_*` names. The older
`*_VIDEO_*` names are still accepted as migration fallbacks, but new
configuration should use the current names:
Expand Down
2 changes: 2 additions & 0 deletions app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface AppConfig {
debugAudio?: boolean;
debugVideo?: boolean;
observabilityEnabled?: boolean;
activityIntelligencePublicUrl?: string;

// 全局调试配置
enableGlobalDebug?: boolean; // 全局调试开关,控制所有调试信息的显示
Expand Down Expand Up @@ -243,6 +244,7 @@ export const APP_CONFIG_DEFAULTS: AppConfig = {
debugAudio: false,
debugVideo: false,
observabilityEnabled: false,
activityIntelligencePublicUrl: undefined,

// 全局调试配置
enableGlobalDebug: process.env.NEXT_PUBLIC_ENABLE_GLOBAL_DEBUG === 'true' || false, // 全局调试开关
Expand Down
63 changes: 63 additions & 0 deletions components/app/activity-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client';

import { useState } from 'react';
import { ChartLineUp, X } from '@phosphor-icons/react';

interface ActivityPanelProps {
publicUrl?: string;
}

export function ActivityPanel({ publicUrl }: ActivityPanelProps) {
const [open, setOpen] = useState(false);

if (!publicUrl) return null;

return (
<>
<button
type="button"
onClick={() => setOpen(true)}
aria-label="查看团队今日动态"
className="bg-background/90 text-foreground border-input hover:bg-accent fixed top-16 right-4 z-40 flex items-center gap-2 rounded-full border px-3 py-2 text-xs font-medium shadow-sm backdrop-blur transition-colors"
>
<ChartLineUp aria-hidden="true" className="size-4" weight="bold" />
<span className="hidden sm:inline">团队动态</span>
</button>

{open && (
<section
role="dialog"
aria-modal="true"
aria-labelledby="activity-panel-title"
className="bg-background fixed inset-0 z-50 flex flex-col"
>
<header className="border-input flex h-14 shrink-0 items-center justify-between border-b px-4 sm:px-6">
<div className="flex items-center gap-2">
<ChartLineUp aria-hidden="true" className="text-primary size-5" weight="bold" />
<div>
<h2 id="activity-panel-title" className="text-sm font-semibold">
团队今日动态
</h2>
<p className="text-muted-foreground text-[10px]">公共聚合视图 · 不展示个人明细</p>
</div>
</div>
<button
type="button"
onClick={() => setOpen(false)}
aria-label="关闭团队动态"
className="hover:bg-accent grid size-9 place-items-center rounded-full transition-colors"
>
<X aria-hidden="true" className="size-4" weight="bold" />
</button>
</header>
<iframe
title="Lex 团队今日动态"
src={publicUrl}
className="min-h-0 flex-1 border-0"
allow="clipboard-read; clipboard-write"
/>
</section>
)}
</>
);
}
2 changes: 2 additions & 0 deletions components/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { StartAudio } from '@livekit/components-react';
import type { AppConfig } from '@/app-config';
import { ActivityPanel } from '@/components/app/activity-panel';
import { SessionProvider } from '@/components/app/session-provider';
import { ViewController } from '@/components/app/view-controller';
import { AudioFilterDebug } from '@/components/livekit/audio-filter-debug';
Expand All @@ -28,6 +29,7 @@ export function App({ appConfig }: AppProps) {
excludeTrackNames={appConfig.excludeAudioTracks}
show={appConfig.showAudioFilterDebug}
/>
<ActivityPanel publicUrl={appConfig.activityIntelligencePublicUrl} />
<Toaster />
</SessionProvider>
);
Expand Down
3 changes: 3 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ export function getClientConfigFromEnv(): AppConfig {
APP_CONFIG_DEFAULTS.observabilityEnabled ?? false,
'OBSERVABILITY_ENABLED'
),
activityIntelligencePublicUrl:
readEnv('ACTIVITY_INTELLIGENCE_PUBLIC_URL', 'NEXT_PUBLIC_ACTIVITY_INTELLIGENCE_PUBLIC_URL') ||
undefined,
};
}

Expand Down
25 changes: 25 additions & 0 deletions tests/project-config.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,28 @@ test('client config reads frontend observability from OBSERVABILITY_ENABLED only
}
}
});

test('client config exposes the activity public view URL to LexVoice', () => {
const previous = process.env.ACTIVITY_INTELLIGENCE_PUBLIC_URL;
const previousPublic = process.env.NEXT_PUBLIC_ACTIVITY_INTELLIGENCE_PUBLIC_URL;
try {
delete process.env.ACTIVITY_INTELLIGENCE_PUBLIC_URL;
process.env.NEXT_PUBLIC_ACTIVITY_INTELLIGENCE_PUBLIC_URL = 'http://public.example/pulse';
assert.equal(
getClientConfigFromEnv().activityIntelligencePublicUrl,
'http://public.example/pulse'
);

process.env.ACTIVITY_INTELLIGENCE_PUBLIC_URL = 'http://server.example/pulse';
assert.equal(
getClientConfigFromEnv().activityIntelligencePublicUrl,
'http://server.example/pulse'
);
} finally {
if (previous === undefined) delete process.env.ACTIVITY_INTELLIGENCE_PUBLIC_URL;
else process.env.ACTIVITY_INTELLIGENCE_PUBLIC_URL = previous;
if (previousPublic === undefined)
delete process.env.NEXT_PUBLIC_ACTIVITY_INTELLIGENCE_PUBLIC_URL;
else process.env.NEXT_PUBLIC_ACTIVITY_INTELLIGENCE_PUBLIC_URL = previousPublic;
}
});
Loading