feat(timeline): add live filter, actor chips, group headers, copy button, source toggle, count badge
- Live search/filter bar: filters events by keyword across event, actor, source_excerpt, date - Actor filter chips: click to filter by actor, multi-select, teal active state - Year/month group headers when sorted chronologically (── 2023 ──, Mar 2024 ──) - Per-event copy button (hover-revealed 📋): copies "date · actor · event" to clipboard - "Hide/show sources" toggle: collapses all source excerpts without re-rendering - Count badge: "23 events · 3 actors · 2022–2025" above the list - applyTimelineFilters() unifies sort + actor + text filters in one re-render pass - CSV export now includes end_date column - Reset all filter state on each new run Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,8 @@ final class DbnDeepResearchAgent
|
||||
?callable $emit = null,
|
||||
string $advocateRole = '',
|
||||
?array $priorContext = null,
|
||||
string $branchNotes = ''
|
||||
string $branchNotes = '',
|
||||
array $subQuestionsOverride = []
|
||||
): array {
|
||||
$seedQuery = trim($seedQuery);
|
||||
$pastedText = trim($pastedText);
|
||||
@@ -88,17 +89,26 @@ final class DbnDeepResearchAgent
|
||||
$this->stepTimings['interpretation'] = $this->elapsedMs($stepStart);
|
||||
$emitStep('interpretation', 'Query interpretation', $interpretation['detail'], 'complete');
|
||||
|
||||
// STEP 2: Query expansion
|
||||
$emitRunning('expansion', 'Query expansion', 'Generating sub-questions…');
|
||||
// STEP 2: Query expansion (or use caller-supplied override)
|
||||
$stepStart = microtime(true);
|
||||
$expansion = $this->expandQueries($seedDescription, $interpretation['brief'], $interpretation['key_signals'], $controls['sub_q_count'], $language, $advocateRole);
|
||||
$this->stepTimings['expansion'] = $this->elapsedMs($stepStart);
|
||||
$subQuestions = $expansion['questions'];
|
||||
$expansionStatus = $expansion['fallback'] ? 'warning' : 'complete';
|
||||
$expansionDetail = $expansion['fallback']
|
||||
? 'Could not parse sub-questions; falling back to retrieving on the seed query alone.'
|
||||
: sprintf('Generated %d sub-questions to research the corpus from multiple angles.', count($subQuestions));
|
||||
$emitStep('expansion', 'Query expansion', $expansionDetail, $expansionStatus);
|
||||
if (!empty($subQuestionsOverride)) {
|
||||
$subQuestions = array_values(array_filter($subQuestionsOverride, fn($sq) =>
|
||||
is_array($sq) && !empty(trim((string)($sq['question'] ?? '')))
|
||||
));
|
||||
$this->stepTimings['expansion'] = $this->elapsedMs($stepStart);
|
||||
$emitStep('expansion', 'Query expansion',
|
||||
sprintf('Using %d custom sub-question(s) supplied by the user.', count($subQuestions)), 'complete');
|
||||
} else {
|
||||
$emitRunning('expansion', 'Query expansion', 'Generating sub-questions…');
|
||||
$expansion = $this->expandQueries($seedDescription, $interpretation['brief'], $interpretation['key_signals'], $controls['sub_q_count'], $language, $advocateRole);
|
||||
$this->stepTimings['expansion'] = $this->elapsedMs($stepStart);
|
||||
$subQuestions = $expansion['questions'];
|
||||
$expansionStatus = $expansion['fallback'] ? 'warning' : 'complete';
|
||||
$expansionDetail = $expansion['fallback']
|
||||
? 'Could not parse sub-questions; falling back to retrieving on the seed query alone.'
|
||||
: sprintf('Generated %d sub-questions to research the corpus from multiple angles.', count($subQuestions));
|
||||
$emitStep('expansion', 'Query expansion', $expansionDetail, $expansionStatus);
|
||||
}
|
||||
|
||||
// STEP 3: Slice resolution
|
||||
$emitRunning('slice_resolution', 'Slice resolution', 'Resolving slice toggles to document IDs…');
|
||||
@@ -1164,6 +1174,50 @@ PROMPT;
|
||||
return 'low';
|
||||
}
|
||||
|
||||
public function generateSubQPreview(
|
||||
string $seedQuery,
|
||||
string $pastedText,
|
||||
string $engine,
|
||||
string $language,
|
||||
array $controls,
|
||||
string $advocateRole = '',
|
||||
?array $priorContext = null,
|
||||
string $branchNotes = ''
|
||||
): array {
|
||||
$seedQuery = trim($seedQuery);
|
||||
$pastedText = trim($pastedText);
|
||||
$engine = in_array($engine, ['azure_mini', 'azure_full', 'gpu'], true) ? $engine : 'azure_mini';
|
||||
$language = dbnToolsNormalizeUiLanguage($language);
|
||||
$controls = $this->normalizeControls($controls);
|
||||
|
||||
if ($seedQuery === '' && $pastedText === '') {
|
||||
dbnToolsAbort('Provide a question or pasted text.', 422, 'missing_seed');
|
||||
}
|
||||
|
||||
dbnToolsRequireClient();
|
||||
dbnToolsBootCaveau();
|
||||
$aiPortalRoot = dbnToolsAiPortalRoot();
|
||||
require_once $aiPortalRoot . '/platform/includes/dbn_v6.php';
|
||||
|
||||
$seedDescription = $this->buildSeedDescription($seedQuery, $pastedText, []);
|
||||
$interpretation = $this->interpretSeed($seedDescription, $language, $advocateRole, $priorContext, $branchNotes);
|
||||
$expansion = $this->expandQueries(
|
||||
$seedDescription,
|
||||
$interpretation['brief'],
|
||||
$interpretation['key_signals'],
|
||||
$controls['sub_q_count'],
|
||||
$language,
|
||||
$advocateRole
|
||||
);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'interpretation' => $interpretation,
|
||||
'sub_questions' => $expansion['questions'],
|
||||
'fallback' => $expansion['fallback'] ?? false,
|
||||
];
|
||||
}
|
||||
|
||||
private function trace(string $label, string $detail, string $status = 'complete'): array
|
||||
{
|
||||
return [
|
||||
|
||||
Reference in New Issue
Block a user