A new rail page keeps each MCP call as a step of a task (api/ai_tasks.py, one file per task in the user folder, the newest 300): named by the AI with the new begin_task tool, else one app's calls without a long pause. A step keeps what it did and what it made (models, the combo, the canvas change by its activity entry, the pictures a run made, watched in the history until it ends, spoken audio, scans); the new report tool leaves the AI's notes and pictures it made another way. The page shows the tasks and one task's results strip and timeline with links (picture viewer, model page, combo, find or undo on the canvas, audio), updates live, and gives the rail a dot while you are elsewhere; Home lists the last tasks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
4.7 KiB
JavaScript
97 lines
4.7 KiB
JavaScript
/**
|
|
* The home page: what you can do here, one card per task, each opening its page; the
|
|
* first-run setup above them while nothing is scanned (ui_first_run.js); and the first steps
|
|
* (set up, scan, the tour, what's new). Rendered again on each visit, so text follows the
|
|
* language.
|
|
*/
|
|
|
|
import { translate as t } from './locales.js';
|
|
import { PAGE_ICONS } from './ui_shell_rail.js';
|
|
import { TOOL_ICONS } from './tool_registry.js';
|
|
import { startSpotlightTour } from './ui_spotlight_tour.js';
|
|
import { showUpdateGuide } from './ui_update_guide.js';
|
|
import { renderRecentActivity } from './ui_activity.js';
|
|
import { renderRecentAiTasks } from './ui_ai_workbench.js';
|
|
import { copyDiagnostics, openRepoPage } from './feedback.js';
|
|
import { openFeedbackDialog } from './ui_feedback_dialog.js';
|
|
import { renderFirstRun, showFirstRun } from './ui_first_run.js';
|
|
|
|
// One card per task: where it goes and its text (`homeCard<Name>Title` / `…Body`).
|
|
const CARDS = Object.freeze([
|
|
{ name: 'Models', icon: PAGE_ICONS.models, open: owner => owner.goTo('models') },
|
|
{ name: 'Doctor', icon: TOOL_ICONS.DOCTOR, open: owner => owner.openDoctorPage() },
|
|
{ name: 'Voices', icon: PAGE_ICONS.script, open: owner => owner.goTo('script') },
|
|
{ name: 'Gallery', icon: PAGE_ICONS.gallery, open: owner => owner.goTo('gallery') },
|
|
{ name: 'Recipes', icon: PAGE_ICONS.recipes, open: owner => owner.goTo('recipes') },
|
|
{ name: 'Combos', icon: PAGE_ICONS.combos, open: owner => owner.goTo('combos') },
|
|
{ name: 'Prompts', icon: PAGE_ICONS.prompts, open: owner => owner.goTo('prompts') },
|
|
]);
|
|
|
|
function el(tag, className, text) {
|
|
const node = document.createElement(tag);
|
|
if (className) node.className = className;
|
|
if (text !== undefined) node.textContent = text;
|
|
return node;
|
|
}
|
|
|
|
function button(className, label, onClick) {
|
|
const node = el('button', className, label);
|
|
node.type = 'button';
|
|
node.onclick = onClick;
|
|
return node;
|
|
}
|
|
|
|
/** "Found a problem, or have an idea?": a report or a suggestion on GitHub, or the environment to paste elsewhere. */
|
|
function feedbackBand(owner) {
|
|
const band = el('section', 'anomalous-home-feedback');
|
|
const copy = el('div', 'anomalous-home-feedback-copy');
|
|
copy.append(el('span', 'anomalous-home-feedback-title', t('feedbackTitle')), el('span', 'anomalous-home-feedback-lead', t('feedbackLead')));
|
|
const actions = el('div', 'anomalous-home-feedback-actions');
|
|
const copyBtn = button('anomalous-home-feedback-btn', t('feedbackCopy'), async () => {
|
|
copyBtn.textContent = t(await copyDiagnostics(owner) ? 'feedbackCopied' : 'feedbackCopyFailed');
|
|
setTimeout(() => { copyBtn.textContent = t('feedbackCopy'); }, 1600);
|
|
});
|
|
copyBtn.title = t('feedbackCopyHint');
|
|
actions.append(
|
|
button('anomalous-home-feedback-btn is-primary', t('feedbackReport'), () => openFeedbackDialog(owner, 'bug')),
|
|
button('anomalous-home-feedback-btn', t('feedbackSuggest'), () => openFeedbackDialog(owner, 'idea')),
|
|
copyBtn,
|
|
);
|
|
const star = button('anomalous-home-feedback-btn', t('starButton'), openRepoPage);
|
|
star.title = t('starHint');
|
|
actions.append(star);
|
|
band.append(copy, actions);
|
|
return band;
|
|
}
|
|
|
|
export function renderHome(owner, panel) {
|
|
const page = el('div', 'anomalous-home');
|
|
page.append(el('h1', 'anomalous-home-title', t('homeTitle')), el('p', 'anomalous-home-lead', t('homeLead')));
|
|
|
|
const grid = el('div', 'anomalous-home-cards');
|
|
for (const card of CARDS) {
|
|
const node = button('anomalous-home-card', '', () => card.open(owner));
|
|
node.dataset.card = card.name.toLowerCase();
|
|
const icon = el('span', 'anomalous-home-card-icon');
|
|
icon.innerHTML = card.icon; // static markup only
|
|
const text = el('span', 'anomalous-home-card-text');
|
|
text.append(el('span', 'anomalous-home-card-title', t(`homeCard${card.name}Title`)),
|
|
el('span', 'anomalous-home-card-body', t(`homeCard${card.name}Body`)));
|
|
node.append(icon, text);
|
|
grid.appendChild(node);
|
|
}
|
|
|
|
const start = el('div', 'anomalous-home-start');
|
|
start.append(el('span', 'anomalous-home-start-label', t('homeStartLabel')),
|
|
button('anomalous-home-link', t('homeStartSetup'), () => void showFirstRun(owner, page, grid)),
|
|
button('anomalous-home-link', t('homeStartScan'), () => owner.openScanPage()),
|
|
button('anomalous-home-link', t('homeStartTour'), () => startSpotlightTour(owner)),
|
|
button('anomalous-home-link', t('homeStartNews'), () => showUpdateGuide(owner, { force: true })));
|
|
|
|
page.append(grid, start, feedbackBand(owner));
|
|
panel.replaceChildren(page);
|
|
void renderFirstRun(owner, page, grid);
|
|
renderRecentActivity(owner, page);
|
|
renderRecentAiTasks(owner, page);
|
|
}
|