Skip to content

feat(studio): polish artifact viewers and podcast persistence#5

Merged
LRriver merged 2 commits into
mainfrom
studio-artifact-polish
Jul 8, 2026
Merged

feat(studio): polish artifact viewers and podcast persistence#5
LRriver merged 2 commits into
mainfrom
studio-artifact-polish

Conversation

@LRriver

@LRriver LRriver commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add interactive FAQ artifact rendering and improve Data Table horizontal scrolling/accessibility in the Studio panel.
  • Persist generated podcast scripts as podcast_script artifacts so they survive refresh and remain available through /api/artifacts.
  • Preserve podcast transcript/audio download metadata across immediate generation and restored artifact views.

Context

This branch is based on origin/main after the Slide Deck/AIPPT integration merge. The merge base is 15641de ci: add project quality checks, which follows PR #3 notebooklm-slide-deck-heavy-integration.

Validation

  • frontend: npm test -- --run
  • frontend: npm run typecheck
  • frontend: npm run build
  • backend: python -m pytest -q
  • backend: python -m ruff check backend tests
  • backend: python -m compileall -q backend
  • frontend e2e: playwright test e2e/interactive-artifacts.spec.ts e2e/non-ppt-workflow.spec.ts

Copilot AI review requested due to automatic review settings July 8, 2026 13:22

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces persistence for generated podcast scripts as structured artifacts in the backend, updating schemas, routes, and domain payloads accordingly. On the frontend, it adds support for rendering collapsible FAQ artifacts, wraps data tables in accessible horizontally scrollable regions, and integrates restored podcast artifact downloads. Feedback from the reviewer focuses on improving internationalization (i18n) by dynamically localizing hardcoded strings using the useLanguage hook, applying defensive programming patterns (optional chaining) to external payload properties, and removing redundant ARIA attributes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useId, useMemo, useState } from 'react';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Internationalization (i18n) Import

Import useLanguage from ../App to enable internationalization (i18n) for user-facing strings in the viewer components.

Suggested change
import React, { useEffect, useId, useMemo, useState } from 'react';
import React, { useEffect, useId, useMemo, useState } from 'react';
import { useLanguage } from '../App';

Comment on lines +25 to +44
{items.map((item: AnyRecord, index: number) => {
const expanded = openIndex === index;
const question = textValue(item.question);
const answerId = `${answerIdPrefix}-faq-answer-${index}`;
return (
<div key={`${question}-${index}`} className={`faq-item ${expanded ? 'open' : ''}`}>
<button
className="faq-question"
aria-label={question}
aria-expanded={expanded}
aria-controls={answerId}
onClick={() => setOpenIndex(expanded ? null : index)}
>
<span>{question}</span>
<small aria-hidden="true">{index + 1} / {items.length}</small>
</button>
{expanded && <p id={answerId} className="faq-answer">{textValue(item.answer)}</p>}
</div>
);
})}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensive Programming & Accessibility Improvement

  1. Defensive Programming: The item object inside items.map is typed as AnyRecord and could potentially be null or undefined if the payload structure from the API/LLM is malformed or unexpected. Using optional chaining (item?.question and item?.answer) prevents potential runtime crashes.
  2. Redundant aria-label: The <button> element visible text is already exactly the question (since the <small> element has aria-hidden="true"). Specifying aria-label={question} is redundant and can interfere with screen readers or translation tools. Removing it simplifies the markup.
Suggested change
{items.map((item: AnyRecord, index: number) => {
const expanded = openIndex === index;
const question = textValue(item.question);
const answerId = `${answerIdPrefix}-faq-answer-${index}`;
return (
<div key={`${question}-${index}`} className={`faq-item ${expanded ? 'open' : ''}`}>
<button
className="faq-question"
aria-label={question}
aria-expanded={expanded}
aria-controls={answerId}
onClick={() => setOpenIndex(expanded ? null : index)}
>
<span>{question}</span>
<small aria-hidden="true">{index + 1} / {items.length}</small>
</button>
{expanded && <p id={answerId} className="faq-answer">{textValue(item.answer)}</p>}
</div>
);
})}
{items.map((item: AnyRecord, index: number) => {
const expanded = openIndex === index;
const question = textValue(item?.question);
const answerId = `${answerIdPrefix}-faq-answer-${index}`;
return (
<div key={`${question}-${index}`} className={`faq-item ${expanded ? 'open' : ''}`}>
<button
className="faq-question"
aria-expanded={expanded}
aria-controls={answerId}
onClick={() => setOpenIndex(expanded ? null : index)}
>
<span>{question}</span>
<small aria-hidden="true">{index + 1} / {items.length}</small>
</button>
{expanded && <p id={answerId} className="faq-answer">{textValue(item?.answer)}</p>}
</div>
);
})}
References
  1. Always use defensive programming patterns (such as optional chaining) when accessing properties of objects originating from external APIs or LLM payloads to prevent runtime crashes.

Comment on lines +238 to +243
const title = textValue(payload.title || 'Data table');
if (!columns.length) {
return <MarkdownView content={markdown || ''} className="artifact-preview" />;
}
return (
<div className="data-table-viewer">
<div className="data-table-viewer data-table-scroll" role="region" aria-label="横向滚动查看完整表格" tabIndex={0}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Internationalization (i18n)

The aria-label for the scrollable region is hardcoded in Chinese ("横向滚动查看完整表格"). Since the application supports both Chinese and English, we should dynamically localize this label using the useLanguage hook.

    const title = textValue(payload.title || 'Data table');
    const { lang } = useLanguage();
    if (!columns.length) {
        return <MarkdownView content={markdown || ''} className="artifact-preview" />;
    }
    return (
        <div className="data-table-viewer data-table-scroll" role="region" aria-label={lang === 'zh' ? '横向滚动查看完整表格' : 'Scroll horizontally to view full table'} tabIndex={0}>
References
  1. Avoid hardcoding user-facing strings in a single language when the application supports multiple locales. Use the established internationalization patterns to localize all UI labels.

Comment thread frontend/src/components/StudioPanel.tsx Outdated
{content.downloadJsonUrl && <a className="secondary-btn" href={content.downloadJsonUrl}><Download size={14} /> JSON</a>}
{content.downloadSvgUrl && <a className="secondary-btn" href={content.downloadSvgUrl}><Download size={14} /> SVG</a>}
{content.audioUrl && <a className="secondary-btn" href={content.audioUrl}><Download size={14} /> MP3</a>}
{content.transcriptUrl && <a className="secondary-btn" href={content.transcriptUrl}><Download size={14} /> Transcript</a>}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Internationalization (i18n)

The label "Transcript" is hardcoded in English. Since the application supports both Chinese and English, we should localize this label using the existing lang variable.

Suggested change
{content.transcriptUrl && <a className="secondary-btn" href={content.transcriptUrl}><Download size={14} /> Transcript</a>}
{content.transcriptUrl && <a className="secondary-btn" href={content.transcriptUrl}><Download size={14} /> {lang === 'zh' ? '转录文本' : 'Transcript'}</a>}
References
  1. Avoid hardcoding user-facing strings in a single language when the application supports multiple locales. Use the established internationalization patterns to localize all UI labels.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Studio artifact UX (interactive FAQ + better data table horizontal scrolling) and makes podcast generation more durable by persisting podcast scripts as first-class podcast_script artifacts, including restored download metadata via /api/artifacts.

Changes:

  • Add an interactive FAQ artifact viewer and enhance Data Table rendering for horizontal scrolling + accessibility affordances.
  • Persist generated podcast scripts as podcast_script artifacts and return artifact_id + structured fields from /api/podcast/generate.
  • Extend frontend state/tests/e2e flows to restore podcast audio/transcript download metadata after refresh.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_podcast_workflow.py Adds an API-level test asserting podcast script persistence as an artifact.
frontend/src/index.css Adds styling for scrollable data tables and FAQ cards.
frontend/src/components/StudioPanel.tsx Uses persisted podcast_script artifacts and preserves download metadata/links.
frontend/src/components/markdown-rendering.test.tsx Adds unit tests for FAQ rendering, restored podcast links, and scrollable tables.
frontend/src/components/ArtifactViewer.tsx Implements FAQ viewer and makes data tables focusable scroll regions with captions.
frontend/src/App.tsx Extends GeneratedContent to carry transcript/audio URLs/filenames and file refs from artifacts.
frontend/e2e/non-ppt-workflow.spec.ts Ensures podcast artifacts survive refresh and download links remain available.
frontend/e2e/interactive-artifacts.spec.ts Adds FAQ artifact flow + asserts data tables overflow in a scroll region.
backend/domain/artifact_schemas.py Expands PodcastScriptArtifactPayload to include persisted audio/transcript metadata.
backend/api/schemas/podcast.py Extends podcast generate response with artifact_id, title, speakers, and turns.
backend/api/routes/podcast.py Persists generated podcast scripts as podcast_script artifacts and returns artifact metadata.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 97 to 100
raise HTTPException(
status_code=400,
detail="请提供 source_text 或 document_ids"
)
Comment thread frontend/src/components/ArtifactViewer.tsx Outdated
@LRriver LRriver merged commit a461e98 into main Jul 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants