merge: land conversational rectification on main
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import path from "node:path";
|
||||
import {
|
||||
conversationalRectificationCommandSchema,
|
||||
type ConversationalRectificationCommand,
|
||||
@@ -25,6 +26,7 @@ import type { BirthTimeJourneyEngine, RectificationQuestionnaire } from "../../.
|
||||
import type { CandidateResult, LifeEvent } from "../../../lib/birth-time-evidence.ts";
|
||||
import type { CandidateDifferenceBuild } from "../../../lib/birth-time-dynamic-choice-internal.ts";
|
||||
import { conversationalRectificationCreationPolicyFromEnvironment } from "../../../lib/conversational-rectification/creation-policy.ts";
|
||||
import { MAXIMUM_SCOREABLE_EVENTS } from "../../../lib/conversational-rectification/convergence.ts";
|
||||
import {
|
||||
conversationalRectificationLatencyBucket,
|
||||
createConversationalRectificationTelemetry,
|
||||
@@ -37,6 +39,9 @@ import {
|
||||
export const runtime = "nodejs";
|
||||
export const maxDuration = 60;
|
||||
|
||||
const jyotishSkillPath = process.env.JYOTISH_SKILL_PATH?.trim()
|
||||
|| path.resolve(process.cwd(), "..", "skills", "jyotish-vedic-astrology");
|
||||
|
||||
type AuthenticatedRequest = Readonly<{
|
||||
userId: string;
|
||||
context: unknown;
|
||||
@@ -349,7 +354,7 @@ function scoreableLifeEvents(
|
||||
precision: item.datePrecision as "day" | "month" | "year",
|
||||
date: item.dateValue,
|
||||
} as LifeEvent];
|
||||
}).slice(-6);
|
||||
}).slice(-MAXIMUM_SCOREABLE_EVENTS);
|
||||
}
|
||||
|
||||
function sampleTimes(scan: RectificationQuestionnaire): readonly { readonly sampleIndex: number; readonly time: string }[] {
|
||||
@@ -665,7 +670,8 @@ async function productionNarrativeGenerator(): Promise<RectificationNarrativeGen
|
||||
id: `conversational-rectification-${model.id}`,
|
||||
name: "Conversational Rectification Narrator",
|
||||
model: model.model,
|
||||
instructions: "Return only the exact JSON object requested by the user prompt. Use only supplied packet facts. Never invent times, layers, references, scores, dates, or confirmation state.",
|
||||
skills: [jyotishSkillPath],
|
||||
instructions: "Return only the exact JSON object requested by the user prompt. Use the Jyotish Skill only to choose a natural, one-question-at-a-time evidence strategy and wording. Treat supplied packet facts as the exclusive source of candidate times, status, dates, layers, scores, references, and confirmation permissions. Never invent, recalculate, or confirm candidate data.",
|
||||
});
|
||||
return {
|
||||
modelId: model.id,
|
||||
|
||||
+602
-26
@@ -164,9 +164,9 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.message-markdown p { margin: 0 0 14px; }
|
||||
.message-markdown ul, .message-markdown ol { margin: 10px 0 16px; padding-left: 24px; }
|
||||
.message-markdown li { margin: 6px 0; }
|
||||
.thinking { height: 24px; display: flex; align-items: center; gap: 5px; }
|
||||
.thinking i:nth-child(2) { animation-delay: 100ms; }
|
||||
.thinking i:nth-child(3) { animation-delay: 200ms; }
|
||||
.agent-activity-status { min-height: 24px; display: flex; align-items: center; gap: var(--space-2); color: var(--color-ink-secondary); font-size: var(--type-body-sm); line-height: 1.5; }
|
||||
.agent-activity-status canvas { flex: 0 0 auto; }
|
||||
.agent-activity-status + .message-markdown { margin-top: var(--space-2); }
|
||||
.composer-suggestions::-webkit-scrollbar { display: none; }
|
||||
.composer textarea::placeholder { color: var(--color-ink-tertiary); }
|
||||
.composer button svg { width: 19px; height: 19px; }
|
||||
@@ -293,17 +293,19 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.sidebar-label { min-height: 44px; display: flex; align-items: center; color: var(--sidebar-muted-foreground); font-size: var(--type-overline); font-weight: 500; letter-spacing: 1.5px; }
|
||||
.session-nav-toggle { min-height: 44px; padding: 0 var(--space-2); border: 0; border-radius: var(--radius-md); background: transparent; color: var(--sidebar-muted-foreground); cursor: pointer; font-size: var(--type-overline); transition: background-color 120ms ease-out, color 120ms ease-out; }
|
||||
.session-list { min-height: 0; display: flex; flex-direction: column; gap: var(--space-1); }
|
||||
.session-row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 44px; align-items: center; border-radius: var(--radius-md); color: var(--sidebar-muted-foreground); }
|
||||
.session-row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 44px; align-items: center; border-radius: var(--radius-md); color: var(--sidebar-muted-foreground); transition: background-color 120ms ease-out, color 120ms ease-out; }
|
||||
.session-row:hover, .session-row:focus-within { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); }
|
||||
.session-row:has(.session-main[data-active="true"]) { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); }
|
||||
.session-main { position: relative; width: 100%; min-height: 52px; display: grid; gap: 2px; padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4); border: 0; border-radius: var(--radius-md); background: transparent; color: inherit; cursor: pointer; text-align: left; transition: background-color 120ms ease-out, color 120ms ease-out, transform 120ms ease-out; }
|
||||
.session-main[data-active="true"] { color: var(--sidebar-accent-foreground); background: var(--sidebar-accent); }
|
||||
.session-main[data-active="true"] { color: var(--sidebar-accent-foreground); background: transparent; }
|
||||
.session-main[data-active="true"]::before { position: absolute; border-radius: 3px; content: ""; top: var(--space-3); bottom: var(--space-3); left: var(--space-1); width: 2px; background: var(--sidebar-ring); }
|
||||
.session-title { min-width: 0; display: flex; align-items: center; gap: var(--space-1); overflow: hidden; line-height: 1.35; font-size: var(--type-body-sm); font-weight: 500; }
|
||||
.session-title > svg { width: var(--space-3); height: var(--space-3); flex: 0 0 auto; color: var(--sidebar-ring); }
|
||||
.session-main small { color: inherit; line-height: 1.3; opacity: .72; font-size: var(--type-overline); }
|
||||
.session-menu-trigger { width: 44px; height: 44px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--sidebar-muted-foreground); cursor: pointer; opacity: .64; transition: opacity 120ms ease-out, background-color 120ms ease-out, color 120ms ease-out; }
|
||||
.session-menu-trigger { width: 44px; height: 44px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 0; background: transparent; color: inherit; cursor: pointer; opacity: .64; transition: opacity 120ms ease-out, color 120ms ease-out; }
|
||||
.session-menu-trigger > svg { width: 18px; height: 18px; }
|
||||
.session-row:hover .session-menu-trigger, .session-row:focus-within .session-menu-trigger, .session-menu-trigger[aria-expanded="true"] { opacity: 1; }
|
||||
.session-menu-trigger:hover, .session-menu-trigger[aria-expanded="true"] { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); }
|
||||
.session-menu-trigger:hover, .session-menu-trigger[aria-expanded="true"] { background: transparent; color: inherit; }
|
||||
.session-actions { position: absolute; z-index: 4; top: calc(100% - var(--space-1)); right: 0; min-width: calc(var(--space-24) + var(--space-12)); display: grid; gap: var(--space-1); padding: var(--space-2); border: 1px solid var(--sidebar-border); border-radius: var(--radius-md); background: var(--color-canvas); box-shadow: var(--shadow-elevated); }
|
||||
.session-actions button { width: 100%; min-height: 44px; display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-3); border: 0; border-radius: var(--radius-md); background: transparent; color: var(--color-ink-secondary); cursor: pointer; text-align: left; font-size: var(--type-body-sm); }
|
||||
.session-actions button > svg { width: 16px; height: 16px; }
|
||||
@@ -464,13 +466,6 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.conversational-narrative .message-markdown > :first-child { margin-top: 0; }
|
||||
.conversational-narrative .message-markdown > :last-child { margin-bottom: 0; }
|
||||
.conversational-narrative .message-markdown h2, .conversational-narrative .message-markdown h3 { margin: var(--space-4) 0 var(--space-2); font-family: var(--font-sans); font-size: var(--type-body-md); font-weight: 600; letter-spacing: 0; }
|
||||
.conversational-domain-picker { display: grid; gap: var(--space-3); margin: 0; padding: 0; border: 0; }
|
||||
.conversational-domain-picker .conversational-domain-question { margin-bottom: var(--space-2); color: var(--color-ink); font-size: var(--type-body-md); font-weight: 600; line-height: 1.5; }
|
||||
.conversational-domain-picker > div { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--space-2); }
|
||||
.conversational-domain-picker button { display: grid; gap: var(--space-1); padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas); color: var(--color-ink); cursor: pointer; text-align: left; }
|
||||
.conversational-domain-picker button > span { font-size: var(--type-body-sm); font-weight: 600; }
|
||||
.conversational-domain-picker button > small { color: var(--color-ink-secondary); font-size: var(--type-overline); font-weight: 400; }
|
||||
.conversational-domain-picker button[aria-pressed="true"] { border-color: var(--color-action); background: var(--color-action-soft); color: var(--color-action); }
|
||||
.conversational-answer-pending { min-width: 0; display: grid; grid-template-columns: 40px minmax(0, 1fr); align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border: 1px solid color-mix(in srgb, var(--color-action) 24%, var(--color-border)); border-radius: var(--radius-lg); background: var(--color-action-soft); }
|
||||
.conversational-answer-pending .app-loading-symbol { width: 40px; height: 40px; }
|
||||
.conversational-answer-pending .app-loading-mark { width: 22px; height: 22px; }
|
||||
@@ -480,8 +475,6 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.conversational-composer { display: grid; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-canvas); }
|
||||
.conversational-composer label { display: grid; gap: var(--space-1); font-size: var(--type-body-sm); font-weight: 600; }
|
||||
.conversational-composer label span { color: var(--color-ink-secondary); font-size: var(--type-caption); font-weight: 400; line-height: 1.5; }
|
||||
.conversational-event-date { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
|
||||
.conversational-event-date label { gap: var(--space-2); color: var(--color-ink-secondary); font-size: var(--type-caption); font-weight: 500; }
|
||||
.conversational-composer textarea { width: 100%; min-width: 0; min-height: 112px; resize: vertical; padding: var(--space-3); border: 1px solid var(--color-border-strong); border-radius: var(--radius-md); background: var(--color-canvas); color: var(--color-ink); line-height: 1.55; }
|
||||
.conversational-composer-footer { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
|
||||
.conversational-composer-footer small { color: var(--color-ink-tertiary); font-size: var(--type-overline); }
|
||||
@@ -542,14 +535,6 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.product-entrypoint-action .starter-arrow { width: 15px; height: 15px; color: currentColor; }
|
||||
.starter-loading { color: var(--color-ink-secondary); margin-left: 0; padding: var(--space-5); border-radius: var(--radius-lg); background: var(--color-canvas-muted); font-size: 14px; }
|
||||
.starter-note { margin: 10px 0 0; color: var(--color-ink-secondary); line-height: 1.5; grid-column: 1 / -1; font-size: 13px; }
|
||||
.parameter-freeze-panel { display: grid; grid-column: 1 / -1; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-canvas); }
|
||||
.parameter-freeze-panel > div { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-2); color: var(--color-ink-secondary); font-size: var(--type-caption); }
|
||||
.parameter-freeze-panel b { color: var(--color-ink); font-size: var(--type-title-sm); }
|
||||
.parameter-freeze-panel dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: var(--space-2); margin: 0; }
|
||||
.parameter-freeze-panel dl > div { min-width: 0; padding: var(--space-2); border-radius: var(--radius-md); background: var(--color-canvas-soft); }
|
||||
.parameter-freeze-panel dt { color: var(--color-ink-tertiary); font-size: 11px; }
|
||||
.parameter-freeze-panel dd { overflow: hidden; margin: 2px 0 0; color: var(--color-ink); text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
||||
|
||||
.message-list { margin: 0 auto; width: min(900px, 100%); padding: var(--space-8) var(--space-8) var(--space-16); }
|
||||
.message { display: flex; animation: message-enter 160ms var(--ease-out) both; padding: var(--space-2) 0; }
|
||||
.agent-avatar { width: 32px; height: 32px; display: block; flex: 0 0 32px; margin-top: var(--space-2); border-radius: 50%; background: var(--color-canvas) url("/jyotish-logo.png") center / contain no-repeat; box-shadow: 0 0 0 1px var(--color-border); }
|
||||
@@ -565,7 +550,6 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.message-markdown strong { color: var(--color-ink); font-weight: 500; }
|
||||
.message-markdown a { text-underline-offset: 3px; color: var(--color-action); }
|
||||
.message-markdown code { padding: 2px 5px; font-size: .9em; border-radius: var(--radius-xs); background: var(--color-canvas-muted); font-family: var(--font-mono); }
|
||||
.thinking i { width: 5px; height: 5px; border-radius: 50%; animation: pulse 850ms ease-in-out infinite alternate; background: var(--color-action); }
|
||||
.error-message { margin: 12px 0 0; padding: 12px 14px; border-left: 3px solid var(--color-danger); background: var(--color-danger-muted); color: var(--color-danger); font-size: 13px; line-height: 1.6; border-color: var(--color-danger); border-radius: 0 var(--radius-md) var(--radius-md) 0; }
|
||||
|
||||
.composer-wrap { position: sticky; bottom: 0; z-index: 2; min-width: 0; border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); padding: var(--space-3) var(--space-6) var(--space-4); background: var(--color-frosted); backdrop-filter: saturate(130%) blur(20px); }
|
||||
@@ -790,7 +774,599 @@ input:disabled, select:disabled { color: var(--color-ink-tertiary); background:
|
||||
[data-sonner-toaster] { right: var(--space-4) !important; left: var(--space-4) !important; width: auto !important; }
|
||||
.conversational-rectification { width: 100%; max-width: 100%; gap: var(--space-4); overflow-x: clip; }
|
||||
.conversational-narrative-body, .conversational-composer, .conversational-evidence-recap, .conversational-candidate, .conversational-confirmation, .conversational-original-question, .conversational-empty-state { padding: var(--space-3); }
|
||||
.conversational-domain-picker > div, .conversational-candidate dl { grid-template-columns: minmax(0, 1fr); }
|
||||
.conversational-candidate dl { grid-template-columns: minmax(0, 1fr); }
|
||||
.conversational-composer-footer { align-items: stretch; flex-direction: column; }
|
||||
.conversational-composer-footer button, .conversational-confirmation button, .conversational-original-question button { width: 100%; }
|
||||
}
|
||||
|
||||
/* Conversational rectification shell */
|
||||
.conversation.is-rectification { overflow: hidden; padding-bottom: 0; }
|
||||
.rectification-chat { height: 100%; min-height: 0; display: flex; flex-direction: column; color: var(--color-ink); }
|
||||
.rectification-message-list { min-height: 0; flex: 1; overflow-y: auto; overscroll-behavior: contain; padding-bottom: var(--space-6); }
|
||||
.rectification-message-details { margin-top: var(--space-3); padding-top: var(--space-2); border-top: 1px solid var(--color-border); color: var(--color-ink-secondary); font-size: var(--type-caption); }
|
||||
.rectification-message-details summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; font-size: var(--type-body-sm); font-weight: 600; }
|
||||
.rectification-message-details p { margin: 0 0 var(--space-2); font-size: var(--type-caption); line-height: 1.6; }
|
||||
.rectification-message-details ul { display: grid; gap: var(--space-2); margin: var(--space-3) 0 0; padding: 0; list-style: none; }
|
||||
.rectification-message-details li { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-3); padding-top: var(--space-2); border-top: 1px solid var(--color-border); }
|
||||
.rectification-message-details button, .rectification-correction-target button { min-height: 44px; padding-inline: var(--space-3); border: 0; background: transparent; color: var(--color-action); cursor: pointer; }
|
||||
.rectification-composer-wrap { position: static; flex: 0 0 auto; }
|
||||
.rectification-correction-target { width: min(760px, 100%); display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin: 0 auto var(--space-2); color: var(--color-ink-secondary); font-size: var(--type-caption); }
|
||||
.rectification-correction-target p { margin: 0; }
|
||||
.conversational-empty-state { width: min(760px, 100%); margin: var(--space-8) auto; color: var(--color-ink-secondary); }
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.rectification-message-details li { align-items: stretch; grid-template-columns: minmax(0, 1fr); }
|
||||
.rectification-correction-target { align-items: stretch; display: grid; }
|
||||
}
|
||||
|
||||
/* Starter workbench */
|
||||
.welcome {
|
||||
width: min(1040px, 100%);
|
||||
padding: var(--space-8) 0 var(--space-12);
|
||||
}
|
||||
|
||||
.starter-list {
|
||||
width: min(1040px, 100%);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--space-6);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.starter-hero,
|
||||
.product-entrypoints,
|
||||
.starter-themes {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.starter-hero {
|
||||
position: relative;
|
||||
min-height: 220px;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 250px;
|
||||
align-items: center;
|
||||
gap: var(--space-8);
|
||||
padding: var(--space-10);
|
||||
border: 1px solid color-mix(in srgb, var(--color-action) 14%, var(--color-border));
|
||||
border-radius: var(--radius-xl);
|
||||
background: radial-gradient(circle at 82% 36%, rgba(133, 67, 47, .12), transparent 33%), linear-gradient(135deg, #fbfaf7 0%, #f5f1eb 100%);
|
||||
}
|
||||
|
||||
.starter-hero::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: .2;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(rgba(29, 29, 31, .24) .45px, transparent .45px);
|
||||
background-size: 6px 6px;
|
||||
content: "";
|
||||
mask-image: linear-gradient(to right, transparent 8%, #000 72%);
|
||||
}
|
||||
|
||||
.starter-hero-copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.starter-greeting {
|
||||
margin: 0 0 var(--space-3);
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-body-sm);
|
||||
font-weight: 600;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
|
||||
.starter-hero h1 {
|
||||
max-width: 980px;
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(42px, 5.4vw, 68px);
|
||||
font-weight: 400;
|
||||
letter-spacing: -.055em;
|
||||
line-height: .98;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.starter-hero-note {
|
||||
max-width: 590px;
|
||||
margin: var(--space-5) 0 0;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-body-sm);
|
||||
line-height: 1.7;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.starter-celestial-visual {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 232px;
|
||||
height: 178px;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.starter-orbit {
|
||||
position: absolute;
|
||||
border: 1px solid color-mix(in srgb, var(--color-action) 34%, transparent);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.starter-orbit-outer {
|
||||
inset: 8px 10px;
|
||||
transform: rotate(-12deg);
|
||||
}
|
||||
|
||||
.starter-orbit-inner {
|
||||
inset: 38px 50px;
|
||||
border-color: color-mix(in srgb, var(--color-ink-tertiary) 42%, transparent);
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
.starter-orbit-dot {
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-action);
|
||||
box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-action-soft) 72%, transparent);
|
||||
}
|
||||
|
||||
.starter-orbit-dot-primary {
|
||||
top: 26px;
|
||||
right: 37px;
|
||||
}
|
||||
|
||||
.starter-orbit-dot-secondary {
|
||||
bottom: 31px;
|
||||
left: 31px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--color-ink-tertiary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.starter-celestial-mark {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border: 1px solid color-mix(in srgb, var(--color-action) 18%, var(--color-border));
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--color-canvas) 88%, transparent) url("/jyotish-logo.png") center / 32px 32px no-repeat;
|
||||
box-shadow: 0 14px 36px -24px rgba(29, 29, 31, .48);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.product-entrypoints {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--color-canvas);
|
||||
}
|
||||
|
||||
.product-entrypoints small {
|
||||
color: var(--color-ink-secondary);
|
||||
line-height: 1.5;
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
|
||||
.daily-starlanguage-card,
|
||||
.birth-rectification-card {
|
||||
min-height: 228px;
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
gap: var(--space-6);
|
||||
padding: var(--space-8);
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.birth-rectification-card {
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.product-entrypoint-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: background-color 180ms ease-out;
|
||||
}
|
||||
|
||||
.product-entrypoint-card::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(112deg, transparent 22%, rgba(255, 255, 255, .54) 46%, transparent 68%);
|
||||
content: "";
|
||||
transform: translateX(-48%);
|
||||
transition: opacity 220ms ease-out, transform 520ms var(--ease-out);
|
||||
}
|
||||
|
||||
.product-entrypoint-hitarea {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.product-entrypoint-card > :not(.product-entrypoint-hitarea) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) {
|
||||
border-color: transparent;
|
||||
background: color-mix(in srgb, var(--color-action-soft) 58%, var(--color-canvas));
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover)::after {
|
||||
opacity: .65;
|
||||
transform: translateX(48%);
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) .product-entrypoint-action {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:focus-visible) {
|
||||
outline: 3px solid color-mix(in srgb, var(--color-focus) 56%, transparent);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:disabled) {
|
||||
opacity: .65;
|
||||
}
|
||||
|
||||
.product-entrypoint-hitarea:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.product-entrypoint-copy {
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.product-entrypoint-kicker {
|
||||
display: block;
|
||||
margin-bottom: var(--space-4);
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-overline);
|
||||
font-weight: 600;
|
||||
letter-spacing: .12em;
|
||||
}
|
||||
|
||||
.product-entrypoint-copy h2 {
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(26px, 2.6vw, 34px);
|
||||
font-weight: 400;
|
||||
letter-spacing: -.035em;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.product-entrypoint-copy p {
|
||||
max-width: 460px;
|
||||
margin: var(--space-3) 0 0;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-body-sm);
|
||||
line-height: 1.65;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.product-entrypoint-footer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-5);
|
||||
padding-top: var(--space-5);
|
||||
border-top: 1px solid color-mix(in srgb, var(--color-border) 76%, transparent);
|
||||
}
|
||||
|
||||
.product-entrypoint-footer small {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-entrypoint-action {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-1);
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-caption);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition: transform 180ms var(--ease-out);
|
||||
}
|
||||
|
||||
.product-entrypoint-action .starter-arrow {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.starter-themes {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.starter-section-heading {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.starter-section-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--type-title-lg);
|
||||
font-weight: 400;
|
||||
letter-spacing: -.025em;
|
||||
}
|
||||
|
||||
.starter-section-heading p {
|
||||
margin: 0;
|
||||
color: var(--color-ink-tertiary);
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
|
||||
.starter-theme-accordion {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--color-canvas-soft);
|
||||
}
|
||||
|
||||
.starter-theme-card {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 170px;
|
||||
flex: 1 1 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 20px;
|
||||
align-items: end;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-5);
|
||||
border: 0;
|
||||
border-right: 1px solid var(--color-border);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background-color 220ms ease-out, flex-grow 360ms var(--ease-out);
|
||||
}
|
||||
|
||||
.starter-theme-card:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.starter-theme-card:not(:disabled):hover,
|
||||
.starter-theme-card:focus-visible {
|
||||
flex-grow: 1.62;
|
||||
background: color-mix(in srgb, var(--color-action-soft) 56%, var(--color-canvas));
|
||||
}
|
||||
|
||||
.starter-theme-accordion:has(.starter-theme-card:not(:disabled):hover) .starter-theme-card:not(:hover),
|
||||
.starter-theme-accordion:has(.starter-theme-card:focus-visible) .starter-theme-card:not(:focus-visible) {
|
||||
flex-grow: .78;
|
||||
}
|
||||
|
||||
.starter-theme-card:not(:disabled):hover .starter-arrow,
|
||||
.starter-theme-card:focus-visible .starter-arrow {
|
||||
color: var(--color-action);
|
||||
transform: translate(2px, -2px);
|
||||
}
|
||||
|
||||
.starter-content {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.starter-content b {
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-body-sm);
|
||||
font-weight: 600;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
.starter-content span {
|
||||
max-width: 245px;
|
||||
overflow: visible;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--type-title-md);
|
||||
line-height: 1.38;
|
||||
text-overflow: clip;
|
||||
text-wrap: pretty;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.starter-arrow {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
color: var(--color-ink-secondary);
|
||||
transition: color 180ms ease-out, transform 180ms var(--ease-out);
|
||||
}
|
||||
|
||||
.starter-loading {
|
||||
width: min(1040px, 100%);
|
||||
margin-left: 0;
|
||||
padding: var(--space-6);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--color-canvas-soft);
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-body-sm);
|
||||
}
|
||||
|
||||
.starter-note {
|
||||
margin: calc(var(--space-2) * -1) 0 0;
|
||||
color: var(--color-ink-secondary);
|
||||
line-height: 1.5;
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
|
||||
.composer-wrap-starter .composer,
|
||||
.composer-wrap-starter .composer-footer,
|
||||
.composer-wrap-starter .composer-suggestions {
|
||||
width: min(1040px, 100%);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 900px) {
|
||||
.starter-hero {
|
||||
grid-template-columns: minmax(0, 1fr) 180px;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
|
||||
.starter-celestial-visual {
|
||||
width: 180px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.product-entrypoints {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.birth-rectification-card {
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.starter-theme-accordion {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.starter-theme-card {
|
||||
min-height: 136px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.starter-theme-card:nth-child(2n) {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.starter-theme-card:nth-last-child(-n + 2) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.welcome {
|
||||
padding: var(--space-4) 0 var(--space-8);
|
||||
}
|
||||
|
||||
.starter-list {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.starter-hero {
|
||||
min-height: 0;
|
||||
grid-template-columns: 1fr;
|
||||
padding: var(--space-8) var(--space-6);
|
||||
}
|
||||
|
||||
.starter-hero h1 {
|
||||
font-size: clamp(38px, 11vw, 54px);
|
||||
}
|
||||
|
||||
.starter-celestial-visual {
|
||||
position: absolute;
|
||||
top: var(--space-4);
|
||||
right: calc(var(--space-8) * -1);
|
||||
width: 180px;
|
||||
height: 150px;
|
||||
opacity: .34;
|
||||
}
|
||||
|
||||
.product-entrypoints {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.daily-starlanguage-card,
|
||||
.birth-rectification-card {
|
||||
min-height: 210px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
|
||||
.birth-rectification-card {
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.starter-section-heading {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.starter-theme-accordion {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.starter-theme-card {
|
||||
min-height: 116px;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.starter-theme-card:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.starter-theme-accordion:has(.starter-theme-card:not(:disabled):hover) .starter-theme-card:not(:hover),
|
||||
.starter-theme-accordion:has(.starter-theme-card:focus-visible) .starter-theme-card:not(:focus-visible),
|
||||
.starter-theme-card:not(:disabled):hover,
|
||||
.starter-theme-card:focus-visible {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.starter-content span {
|
||||
max-width: 440px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.starter-hero {
|
||||
padding: var(--space-6) var(--space-5);
|
||||
}
|
||||
|
||||
.starter-hero-note {
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.product-entrypoint-footer {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.product-entrypoint-action {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.starter-content span {
|
||||
font-size: var(--type-title-sm);
|
||||
}
|
||||
}
|
||||
|
||||
+160
-87
@@ -3,6 +3,8 @@
|
||||
import Link from "next/link";
|
||||
import dynamic from "next/dynamic";
|
||||
import { ArrowUp, ArrowUpRight, Sparkles, Square, X } from "lucide-react";
|
||||
import { useGSAP } from "@gsap/react";
|
||||
import gsap from "gsap";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { FormEvent, KeyboardEvent } from "react";
|
||||
import { AppSidebar } from "@/components/app-sidebar";
|
||||
@@ -15,7 +17,6 @@ import { ConversationalBirthTimeRectification } from "@/components/conversationa
|
||||
import { ChatMessageContent } from "@/components/chat-message-content";
|
||||
import { AgentAvatar, ChatMessageRow } from "@/components/chat-message-row";
|
||||
import { ModelSelector } from "@/components/model-selector";
|
||||
import { ParameterFreezePanel, type ParameterFreezeRow } from "@/components/parameter-freeze-panel";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
@@ -72,7 +73,6 @@ import { consultationReportMarkdown } from "@/lib/consultation-report-export";
|
||||
import {
|
||||
OnboardingAuthenticationError,
|
||||
type OnboardingContent,
|
||||
createOnboardingFallbackGreeting,
|
||||
createStartGreeting,
|
||||
isCurrentOnboardingRequest,
|
||||
onboardingProfileFingerprint,
|
||||
@@ -92,6 +92,8 @@ import {
|
||||
import { selfHostedOtpActions } from "@/modules/identity/client";
|
||||
import { createBrowserSupabaseClient } from "@/lib/supabase/client";
|
||||
|
||||
gsap.registerPlugin(useGSAP);
|
||||
|
||||
const BirthTimeRectification = dynamic(
|
||||
() => import("@/components/birth-time-rectification").then((module) => module.BirthTimeRectification),
|
||||
{
|
||||
@@ -239,6 +241,12 @@ const dailyStarlanguageCards: DailyStarlanguageCard[] = [
|
||||
{ trend: "适合观察资源流向:时间、注意力、金钱都算。", action: "检查一个正在消耗你的习惯,并给它设上限。", caution: "不要为了短期安心做长期成本高的选择。" },
|
||||
];
|
||||
|
||||
function greetingForHour(hour: number): string {
|
||||
if (hour < 11) return "早上好";
|
||||
if (hour < 18) return "下午好";
|
||||
return "晚上好";
|
||||
}
|
||||
|
||||
const emptyProfile: Profile = {
|
||||
name: "",
|
||||
date: "",
|
||||
@@ -836,6 +844,7 @@ export default function Home() {
|
||||
const closeButton = useRef<HTMLButtonElement>(null);
|
||||
const redeemInput = useRef<HTMLInputElement>(null);
|
||||
const composerInput = useRef<HTMLTextAreaElement>(null);
|
||||
const starterWorkbench = useRef<HTMLDivElement>(null);
|
||||
const pendingConsultation = useRef<PendingConsultation | null>(null);
|
||||
const cancellationRequests = useRef(new Map<string, Promise<void>>());
|
||||
const cancellationFeedbackRequest = useRef<string | null>(null);
|
||||
@@ -856,6 +865,7 @@ export default function Home() {
|
||||
createDurableRectificationQuestionHandoffClient(),
|
||||
);
|
||||
const resumeRectificationSession = useRef<(session: ChatSession) => void>(() => undefined);
|
||||
const rectificationOpenInFlight = useRef(false);
|
||||
const rectificationContinuationInFlight = useRef(false);
|
||||
const uiPreview = useRef(false);
|
||||
const uiPreviewMode = useRef<string | null>(null);
|
||||
@@ -873,7 +883,8 @@ export default function Home() {
|
||||
const visibleRectificationTurn = activeSession?.id === rectificationSessionId
|
||||
? rectificationInitialTurn
|
||||
: null;
|
||||
const rectificationSurfaceOpen = activeRectificationSession && visibleRectificationTurn !== null;
|
||||
const rectificationSurfaceOpen = activeRectificationSession
|
||||
&& activeSession.id === rectificationSessionId;
|
||||
const visibleSessions = sessions
|
||||
.filter((session) => showArchivedSessions ? archivedSessionIds.includes(session.id) : !archivedSessionIds.includes(session.id))
|
||||
.sort((left, right) => Number(pinnedSessionIds.includes(right.id)) - Number(pinnedSessionIds.includes(left.id)));
|
||||
@@ -1004,17 +1015,6 @@ export default function Home() {
|
||||
}, [accountId, profile]);
|
||||
|
||||
const profileComplete = isProfileComplete(profile);
|
||||
const birthTimeDisplay = birthTimeDisplayState(profile);
|
||||
const profileBirthPlace = selectedBirthPlace(profile);
|
||||
const parameterFreezeRows: ParameterFreezeRow[] = profileComplete && profileBirthPlace ? [
|
||||
{ label: "出生时间", value: `${profile.date} ${profile.time || "未定"}` },
|
||||
{ label: "出生地点", value: profileBirthPlace.label },
|
||||
{ label: "经纬度", value: `${profileBirthPlace.lat.toFixed(4)}, ${profileBirthPlace.lon.toFixed(4)}` },
|
||||
{ label: "时区", value: `UTC+${profileBirthPlace.tz}` },
|
||||
{ label: "Ayanamsa", value: "Lahiri / Sidereal" },
|
||||
{ label: "Node mode", value: "True Node" },
|
||||
{ label: "出生时间精度", value: birthTimeDisplay?.kind === "candidate" ? "候选时间" : birthTimeDisplay?.kind === "confirmed" ? "已确认" : "待校正" },
|
||||
] : [];
|
||||
const dailyStarlanguage = dailyStarlanguageCard ?? (profileComplete ? buildDailyStarlanguageCard(profile) : null);
|
||||
const onboardingPending = profileComplete && !onboarding && !onboardingError;
|
||||
const currentOnboardingMessage = onboardingJustCompleted
|
||||
@@ -1029,6 +1029,14 @@ export default function Home() {
|
||||
const shouldStreamOnboarding = !profileComplete;
|
||||
const presetMessageFinished = !shouldStreamOnboarding || presetMessageLength >= currentOnboardingMessage.length;
|
||||
const onboardingCardReady = presetMessageFinished || birthTimeAssessmentPhase !== null;
|
||||
const starterSuggestions = themes.map((theme) => onboarding?.suggestions.find((item) => item.theme === theme.id)
|
||||
?? { theme: theme.id, text: theme.prompt });
|
||||
const starterHomeVisible = profileComplete
|
||||
&& presetMessageFinished
|
||||
&& !onboardingPending
|
||||
&& !rectificationSurfaceOpen
|
||||
&& !activeSession?.messages.length;
|
||||
const daypartGreeting = greetingForHour(new Date().getHours());
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
@@ -1745,7 +1753,7 @@ export default function Home() {
|
||||
event.preventDefault();
|
||||
if (!selectedBirthPlace(profileDraft) || !account || profileSaving) return;
|
||||
setProfileSaving(true);
|
||||
setBirthTimeAssessmentPhase("saving_profile");
|
||||
setBirthTimeAssessmentPhase("entering_home");
|
||||
setAccountError("");
|
||||
try {
|
||||
await persistProfile(profileDraft);
|
||||
@@ -1896,7 +1904,8 @@ export default function Home() {
|
||||
pendingConsultationQuestion: string | null = null,
|
||||
sourceSessionOverride: ChatSession | null = null,
|
||||
) {
|
||||
if (!account || !modelCatalog || creatingSession || rectificationLoading || rectificationMutationPending
|
||||
if (!account || !modelCatalog || creatingSession || rectificationLoading || rectificationOpenInFlight.current
|
||||
|| rectificationMutationPending
|
||||
|| rectificationContinuationInFlight.current) return;
|
||||
const sourceSession = sourceSessionOverride ?? activeSession;
|
||||
if (!sourceSession) return;
|
||||
@@ -1926,6 +1935,7 @@ export default function Home() {
|
||||
? null
|
||||
: rectificationQuestionHandoff.current.peek()?.question)
|
||||
?? null;
|
||||
rectificationOpenInFlight.current = true;
|
||||
setDraft("");
|
||||
setDraftTheme(null);
|
||||
setDraftEntrypoint(null);
|
||||
@@ -1933,6 +1943,18 @@ export default function Home() {
|
||||
setRectificationInitialTurn(null);
|
||||
setRectificationError("");
|
||||
setRectificationLoading(true);
|
||||
if (!reusingRectificationSession) {
|
||||
setSessions((current) => [
|
||||
rectificationSession,
|
||||
...current.filter((session) => session.id !== rectificationSession.id),
|
||||
]);
|
||||
}
|
||||
if (sourceSession.id !== rectificationSession.id) {
|
||||
setRectificationReturnSessionId(sourceSession.id);
|
||||
}
|
||||
setRectificationSessionId(rectificationSession.id);
|
||||
activeSessionIdRef.current = rectificationSession.id;
|
||||
setActiveSessionId(rectificationSession.id);
|
||||
try {
|
||||
let turn: ConversationalRectificationTurn;
|
||||
if (action !== "resume" || !account.rectificationCase) {
|
||||
@@ -1998,12 +2020,6 @@ export default function Home() {
|
||||
}
|
||||
setRectificationInitialTurn(turn);
|
||||
synchronizeRectificationQuestion(turn, sourceSession);
|
||||
if (sourceSession.id !== boundSession.id) {
|
||||
setRectificationReturnSessionId(sourceSession.id);
|
||||
}
|
||||
setRectificationSessionId(boundSession.id);
|
||||
activeSessionIdRef.current = boundSession.id;
|
||||
setActiveSessionId(boundSession.id);
|
||||
setComposerNotice(sessionSyncFailed ? "校正已经开始,但会话关联暂时未同步到云端。" : "");
|
||||
} catch (caught) {
|
||||
const message = caught instanceof Error
|
||||
@@ -2011,7 +2027,14 @@ export default function Home() {
|
||||
: "生时校正暂时无法继续,请稍后重试。";
|
||||
setRectificationError(message);
|
||||
setComposerNotice(message);
|
||||
if (!reusingRectificationSession) {
|
||||
setSessions((current) => current.filter((session) => session.id !== rectificationSession.id));
|
||||
setRectificationSessionId(null);
|
||||
activeSessionIdRef.current = sourceSession.id;
|
||||
setActiveSessionId(sourceSession.id);
|
||||
}
|
||||
} finally {
|
||||
rectificationOpenInFlight.current = false;
|
||||
setRectificationLoading(false);
|
||||
}
|
||||
}
|
||||
@@ -2691,6 +2714,39 @@ export default function Home() {
|
||||
continueRectificationQuestion.current(question);
|
||||
}, [rectificationInitialTurn, rectificationLoading, rectificationMutationPending]);
|
||||
|
||||
useGSAP(() => {
|
||||
if (!starterHomeVisible || !starterWorkbench.current) return;
|
||||
const motion = gsap.matchMedia();
|
||||
motion.add("(prefers-reduced-motion: no-preference)", () => {
|
||||
const timeline = gsap.timeline({ defaults: { ease: "power3.out" } });
|
||||
timeline
|
||||
.from(".starter-hero-copy > *", {
|
||||
autoAlpha: 0,
|
||||
duration: 0.62,
|
||||
stagger: 0.07,
|
||||
y: 18,
|
||||
})
|
||||
.from(".starter-celestial-visual", {
|
||||
autoAlpha: 0,
|
||||
duration: 0.76,
|
||||
scale: 0.94,
|
||||
}, 0.08)
|
||||
.from(".product-entrypoint-card", {
|
||||
autoAlpha: 0,
|
||||
duration: 0.54,
|
||||
stagger: 0.08,
|
||||
y: 20,
|
||||
}, 0.2)
|
||||
.from(".starter-theme-card", {
|
||||
autoAlpha: 0,
|
||||
duration: 0.46,
|
||||
stagger: 0.055,
|
||||
y: 14,
|
||||
}, 0.34);
|
||||
});
|
||||
return () => motion.revert();
|
||||
}, { dependencies: [activeSession?.id, starterHomeVisible], scope: starterWorkbench });
|
||||
|
||||
function submit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!profileComplete) {
|
||||
@@ -2831,7 +2887,7 @@ export default function Home() {
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div className={`conversation ${activeSession?.messages.length ? "" : "is-empty"}`}>
|
||||
<div className={`conversation ${activeSession?.messages.length ? "" : "is-empty"} ${rectificationSurfaceOpen ? "is-rectification" : ""}`}>
|
||||
{!rectificationSurfaceOpen && (!activeSession?.messages.length ? (
|
||||
<div className="welcome">
|
||||
{!profileComplete ? (
|
||||
@@ -2846,12 +2902,7 @@ export default function Home() {
|
||||
{profileComplete && onboardingJustCompleted && selectedBirthPlace(profileDraft) && <OnboardingChatMessage role="user" text={selectedBirthPlace(profileDraft)?.label ?? ""} />}
|
||||
{profileComplete && onboardingJustCompleted && <OnboardingChatMessage role="assistant" text={currentOnboardingMessage} streaming length={presetMessageLength} />}
|
||||
</>
|
||||
) : (
|
||||
<OnboardingChatMessage role="assistant" text={onboarding?.greeting
|
||||
|| (onboardingPending
|
||||
? `${profile.name.trim()},稍等一下,我正在准备几个适合开始的问题。`
|
||||
: createOnboardingFallbackGreeting(profile.name))} />
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{!profileComplete && onboardingStep === "birth" && onboardingCardReady && (
|
||||
<div className="onboarding-card-reveal">
|
||||
@@ -2910,10 +2961,24 @@ export default function Home() {
|
||||
{profileComplete && presetMessageFinished && !rectificationSurfaceOpen && (onboardingPending ? (
|
||||
<div className="starter-loading" role="status">正在准备三个入门问题…</div>
|
||||
) : (
|
||||
<div className="starter-list" aria-label="Jyotisha 推荐的初始问题">
|
||||
{parameterFreezeRows.length > 0 && <ParameterFreezePanel rows={parameterFreezeRows} />}
|
||||
<div className="product-entrypoints" aria-label="常用占星入口">
|
||||
<article className="daily-starlanguage-card product-entrypoint-card" aria-label="今日星语">
|
||||
<div className="starter-list starter-workbench" ref={starterWorkbench} aria-label="Jyotisha 推荐的初始问题">
|
||||
<section className="starter-hero" aria-labelledby="starter-heading">
|
||||
<div className="starter-hero-copy">
|
||||
<p className="starter-greeting">{daypartGreeting},{profile.name.trim()}。</p>
|
||||
<h1 id="starter-heading">今天想先理清什么?</h1>
|
||||
<p className="starter-hero-note">从此刻最在意的事开始,我会结合你的星盘证据,帮你把问题拆得更清楚。</p>
|
||||
</div>
|
||||
<div className="starter-celestial-visual" aria-hidden="true">
|
||||
<span className="starter-orbit starter-orbit-outer" />
|
||||
<span className="starter-orbit starter-orbit-inner" />
|
||||
<span className="starter-orbit-dot starter-orbit-dot-primary" />
|
||||
<span className="starter-orbit-dot starter-orbit-dot-secondary" />
|
||||
<span className="starter-celestial-mark" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="product-entrypoints" aria-label="常用占星入口">
|
||||
<article className="daily-starlanguage-card product-entrypoint-card" aria-labelledby="daily-starlanguage-title">
|
||||
<button
|
||||
className="product-entrypoint-hitarea"
|
||||
type="button"
|
||||
@@ -2921,20 +2986,17 @@ export default function Home() {
|
||||
disabled={productEntrypointsDisabled}
|
||||
onClick={draftDailyStarlanguageQuestion}
|
||||
/>
|
||||
<div className="daily-starlanguage-heading">
|
||||
<span>今日星语</span>
|
||||
<div className="product-entrypoint-copy">
|
||||
<span className="product-entrypoint-kicker">每日观察</span>
|
||||
<h2 id="daily-starlanguage-title">今日星语</h2>
|
||||
<p>{dailyStarlanguage?.trend}</p>
|
||||
</div>
|
||||
<dl>
|
||||
<div><dt>今日趋势</dt><dd>{dailyStarlanguage?.trend}</dd></div>
|
||||
<div><dt>行动建议</dt><dd>{dailyStarlanguage?.action}</dd></div>
|
||||
<div><dt>今日提醒</dt><dd>{dailyStarlanguage?.caution}</dd></div>
|
||||
</dl>
|
||||
<div className="product-entrypoint-footer">
|
||||
<small>探索性日提示,不是确定预测。</small>
|
||||
<small>{dailyStarlanguage?.action}</small>
|
||||
<span className="product-entrypoint-action" aria-hidden="true">深入看今日 <ArrowUpRight className="starter-arrow" /></span>
|
||||
</div>
|
||||
</article>
|
||||
<article className="birth-rectification-card product-entrypoint-card" aria-label="生时校正">
|
||||
<article className="birth-rectification-card product-entrypoint-card" aria-labelledby="birth-rectification-title">
|
||||
<button
|
||||
className="product-entrypoint-hitarea"
|
||||
type="button"
|
||||
@@ -2942,44 +3004,50 @@ export default function Home() {
|
||||
disabled={productEntrypointsDisabled || rectificationLoading || rectificationMutationPending}
|
||||
onClick={() => void openBirthTimeRectification()}
|
||||
/>
|
||||
<div className="daily-starlanguage-heading">
|
||||
<span>生时校正</span>
|
||||
<div className="product-entrypoint-copy">
|
||||
<span className="product-entrypoint-kicker">出生资料</span>
|
||||
<h2 id="birth-rectification-title">生时校正</h2>
|
||||
<p>{rectificationCardAction === "resume"
|
||||
? "你有一段未完成的校正记录,可以从上次的位置继续。"
|
||||
: "不确定准确出生时间时,可通过已经发生的人生事件逐步缩小范围。"}</p>
|
||||
</div>
|
||||
<dl>
|
||||
{birthTimeDisplay ? (
|
||||
<>
|
||||
<div><dt>{birthTimeDisplay.kind === "candidate" ? "待验证候选时间" : "当前排盘时间"}</dt><dd>{birthTimeDisplay.activeTime}</dd></div>
|
||||
<div><dt>结果状态</dt><dd>{birthTimeDisplay.kind === "candidate" ? `未确认;${birthTimeConsultationOptionsCopy(profile)}` : "已确认"}</dd></div>
|
||||
<div><dt>原始填报</dt><dd>{birthTimeDisplay.reportedLabel}</dd></div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div><dt>填报资料</dt><dd>{formatBirthMoment(profile)}</dd></div>
|
||||
<div><dt>案例状态</dt><dd>{rectificationCardAction === "resume" ? "已有进度,可继续" : "尚未建立进行中的校正案例"}</dd></div>
|
||||
<div><dt>校正方式</dt><dd>星盘差异解释 + 已发生事件验证</dd></div>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
<div className="product-entrypoint-footer">
|
||||
<small>{rectificationCardAction === "resume"
|
||||
? account.rectificationCase?.preservesActiveTime
|
||||
? "新校正进行中;旧确认时间继续有效,继续同一案例不重复收费。"
|
||||
: "继续同一案例不重复收费。"
|
||||
: `固定费用 ${account.rectificationPriceCredits} 点;首轮有效分析生成后收取。`}</small>
|
||||
? "继续同一案例,不重复收费。"
|
||||
: `固定费用 ${account.rectificationPriceCredits} 点;首轮有效分析后收取。`}</small>
|
||||
<span className="product-entrypoint-action" aria-hidden="true">{rectificationCardLabel} <ArrowUpRight className="starter-arrow" /></span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{(onboarding?.suggestions ?? themes.map((item) => ({ theme: item.id, text: item.prompt }))).map((item) => {
|
||||
const theme = themes.find((candidate) => candidate.id === item.theme);
|
||||
return (
|
||||
<button key={`${item.theme}-${item.text}`} type="button" disabled={!hydrated || Boolean(pendingSessionId) || cancellationPending || !account || !modelCatalog} onClick={() => chooseSuggestedQuestion(item.text, item.theme)}>
|
||||
<span className="starter-content"><b>{theme?.label || "开始"}</b><span>{item.text}</span>{theme && <small>{theme.evidencePreview.join(" / ")} · {theme.claimBoundary}</small>}</span>
|
||||
<ArrowUpRight className="starter-arrow" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{onboardingError && <p className="starter-note">Agent 的个性化入门问题暂时不可用,已显示安全的默认问题。</p>}
|
||||
</section>
|
||||
|
||||
<section className="starter-themes" aria-labelledby="starter-themes-heading">
|
||||
<div className="starter-section-heading">
|
||||
<h2 id="starter-themes-heading">或者,从一个主题开始</h2>
|
||||
<p>选择最接近你当下的问题。</p>
|
||||
</div>
|
||||
<div className="starter-theme-accordion">
|
||||
{starterSuggestions.map((item) => {
|
||||
const theme = themes.find((candidate) => candidate.id === item.theme);
|
||||
return (
|
||||
<button
|
||||
className="starter-theme-card"
|
||||
key={`${item.theme}-${item.text}`}
|
||||
type="button"
|
||||
aria-label={`${theme?.label || "开始"}:${item.text}`}
|
||||
disabled={!hydrated || Boolean(pendingSessionId) || cancellationPending || !account || !modelCatalog}
|
||||
onClick={() => chooseSuggestedQuestion(item.text, item.theme)}
|
||||
>
|
||||
<span className="starter-content">
|
||||
<b>{theme?.label || "开始"}</b>
|
||||
<span>{item.text}</span>
|
||||
</span>
|
||||
<ArrowUpRight className="starter-arrow" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
{onboardingError && <p className="starter-note">个性化问题暂时不可用,已显示安全的默认问题。</p>}
|
||||
</div>
|
||||
))}
|
||||
<div ref={conversationEnd} />
|
||||
@@ -2994,21 +3062,26 @@ export default function Home() {
|
||||
<div ref={conversationEnd} />
|
||||
</div>
|
||||
))}
|
||||
{rectificationSurfaceOpen && visibleRectificationTurn && (
|
||||
<section className="rectification-session-surface" aria-label="生时校正">
|
||||
<ConversationalBirthTimeRectification
|
||||
initialTurn={visibleRectificationTurn}
|
||||
pendingConsultationQuestion={rectificationPendingQuestion}
|
||||
continuationPending={rectificationContinuationPending}
|
||||
onPendingChange={setRectificationMutationPending}
|
||||
onTurn={handleConversationalRectificationTurn}
|
||||
onContinueOriginalQuestion={(question) => void continueRectificationOriginalQuestion(question)}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
{rectificationSurfaceOpen && (!visibleRectificationTurn && rectificationError ? (
|
||||
<div className="conversational-rectification-start-error" role="alert">
|
||||
<p>{rectificationError}</p>
|
||||
<button type="button" disabled={rectificationLoading} onClick={() => void openBirthTimeRectification(null, activeSession ?? null)}>
|
||||
{rectificationLoading ? "正在重试…" : "重新建立校正记录"}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<ConversationalBirthTimeRectification
|
||||
initialTurn={visibleRectificationTurn}
|
||||
pendingConsultationQuestion={rectificationPendingQuestion}
|
||||
continuationPending={rectificationContinuationPending}
|
||||
onPendingChange={setRectificationMutationPending}
|
||||
onTurn={handleConversationalRectificationTurn}
|
||||
onContinueOriginalQuestion={(question) => void continueRectificationOriginalQuestion(question)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!rectificationSurfaceOpen && <div className="composer-wrap">
|
||||
{!rectificationSurfaceOpen && <div className={`composer-wrap ${starterHomeVisible ? "composer-wrap-starter" : ""}`}>
|
||||
{activeSuggestions.length > 0 && (
|
||||
<div className="composer-suggestions" aria-label="推荐继续提问">
|
||||
{activeSuggestions.map((question) => (
|
||||
|
||||
Reference in New Issue
Block a user