Files
2026-11-07 10:43:06 +08:00

5185 lines
255 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#F5F5F7" />
<title>开始创作|来秒AI</title>
<meta name="description" content="来秒AI 创作工作台:输入需求或上传产品图,一键生成行业图片与短片。" />
<meta name="referrer" content="no-referrer" />
<meta name="skill-agent-api" content="%VITE_SKILL_AGENT_URL%" />
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='9' fill='%2308482D'/%3E%3Cpath d='M18.6 3 7.5 18h6.7l-1.7 11L24.5 13h-6.6l.7-10Z' fill='%23DAE7C7'/%3E%3C/svg%3E" />
<link rel="preconnect" href="https://fonts.googleapis.cn" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.cn/css2?family=Inter:wght@400;500;600;700;800;900&family=Noto+Sans+SC:wght@400;500;700;900&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.cn/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet" />
<script>window.__TEAM_API_BASE__ = "%VITE_TEAM_API_URL%";</script>
<style>
html, body { height: 100%; margin: 0; }
.material-symbols-outlined { font-family: "Material Symbols Outlined"; font-weight: normal; font-style: normal; font-size: 24px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; overflow-wrap: normal; direction: ltr; font-feature-settings: "liga"; -webkit-font-smoothing: antialiased; opacity: 0; }
.material-symbols-outloaded .material-symbols-outlined { opacity: 1; }
@keyframes appBootSpin {
100% { transform: rotate(360deg); }
}
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
:root, :host { --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
"Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji"; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace; --color-red-50: oklch(97.1% 0.013 17.38); --color-red-100: oklch(93.6% 0.032 17.717); --color-red-200: oklch(88.5% 0.062 18.334); --color-red-300: oklch(80.8% 0.114 19.571); --color-red-400: oklch(70.4% 0.191 22.216); --color-red-500: oklch(63.7% 0.237 25.331); --color-red-600: oklch(57.7% 0.245 27.325); --color-red-700: oklch(50.5% 0.213 27.518); --color-orange-50: oklch(98% 0.016 73.684); --color-orange-100: oklch(95.4% 0.038 75.164); --color-orange-400: oklch(75% 0.183 55.934); --color-orange-500: oklch(70.5% 0.213 47.604); --color-orange-600: oklch(64.6% 0.222 41.116); --color-orange-700: oklch(55.3% 0.195 38.402); --color-amber-50: oklch(98.7% 0.022 95.277); --color-amber-100: oklch(96.2% 0.059 95.617); --color-amber-200: oklch(92.4% 0.12 95.746); --color-amber-300: oklch(87.9% 0.169 91.605); --color-amber-400: oklch(82.8% 0.189 84.429); --color-amber-500: oklch(76.9% 0.188 70.08); --color-amber-600: oklch(66.6% 0.179 58.318); --color-amber-700: oklch(55.5% 0.163 48.998); --color-amber-800: oklch(47.3% 0.137 46.201); --color-amber-900: oklch(41.4% 0.112 45.904); --color-yellow-100: oklch(97.3% 0.071 103.193); --color-yellow-400: oklch(85.2% 0.199 91.936); --color-yellow-600: oklch(68.1% 0.162 75.834); --color-green-50: oklch(98.2% 0.018 155.826); --color-green-100: oklch(96.2% 0.044 156.743); --color-green-500: oklch(72.3% 0.219 149.579); --color-green-600: oklch(62.7% 0.194 149.214); --color-green-700: oklch(52.7% 0.154 150.069); --color-green-800: oklch(44.8% 0.119 151.328); --color-emerald-200: oklch(90.5% 0.093 164.15); --color-emerald-500: oklch(69.6% 0.17 162.48); --color-emerald-600: oklch(59.6% 0.145 163.225); --color-emerald-800: oklch(43.2% 0.095 166.913); --color-sky-200: oklch(90.1% 0.058 230.902); --color-blue-50: oklch(97% 0.014 254.604); --color-blue-100: oklch(93.2% 0.032 255.585); --color-blue-500: oklch(62.3% 0.214 259.815); --color-blue-600: oklch(54.6% 0.245 262.881); --color-blue-700: oklch(48.8% 0.243 264.376); --color-indigo-100: oklch(93% 0.034 272.788); --color-indigo-600: oklch(51.1% 0.262 276.966); --color-indigo-900: oklch(35.9% 0.144 278.697); --color-purple-100: oklch(94.6% 0.033 307.174); --color-purple-500: oklch(62.7% 0.265 303.9); --color-purple-600: oklch(55.8% 0.288 302.321); --color-purple-700: oklch(49.6% 0.265 301.924); --color-pink-100: oklch(94.8% 0.028 342.258); --color-pink-600: oklch(59.2% 0.249 0.584); --color-slate-200: oklch(92.9% 0.013 255.508); --color-slate-300: oklch(86.9% 0.022 252.894); --color-slate-400: oklch(70.4% 0.04 256.788); --color-slate-500: oklch(55.4% 0.046 257.417); --color-slate-600: oklch(44.6% 0.043 257.281); --color-slate-900: oklch(20.8% 0.042 265.755); --color-gray-50: oklch(98.5% 0.002 247.839); --color-gray-100: oklch(96.7% 0.003 264.542); --color-gray-200: oklch(92.8% 0.006 264.531); --color-gray-300: oklch(87.2% 0.01 258.338); --color-gray-400: oklch(70.7% 0.022 261.325); --color-gray-500: oklch(55.1% 0.027 264.364); --color-gray-600: oklch(44.6% 0.03 256.802); --color-gray-700: oklch(37.3% 0.034 259.733); --color-gray-800: oklch(27.8% 0.033 256.848); --color-gray-900: oklch(21% 0.034 264.665); --color-neutral-50: oklch(98.5% 0 none); --color-neutral-100: oklch(97% 0 none); --color-neutral-200: oklch(92.2% 0 none); --color-neutral-300: oklch(87% 0 none); --color-neutral-400: oklch(70.8% 0 none); --color-neutral-500: oklch(55.6% 0 none); --color-neutral-600: oklch(43.9% 0 none); --color-neutral-700: oklch(37.1% 0 none); --color-neutral-800: oklch(26.9% 0 none); --color-neutral-900: oklch(20.5% 0 none); --color-neutral-950: oklch(14.5% 0 none); --color-black: #000; --color-white: #fff; --spacing: 0.25rem; --container-md: 28rem; --container-lg: 32rem; --container-xl: 36rem; --container-2xl: 42rem; --container-4xl: 56rem; --container-5xl: 64rem; --container-7xl: 80rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); --text-sm: 0.875rem; --text-sm--line-height: calc(1.25 / 0.875); --text-base: 1rem; --text-base--line-height: calc(1.5 / 1); --text-lg: 1.125rem; --text-lg--line-height: calc(1.75 / 1.125); --text-xl: 1.25rem; --text-xl--line-height: calc(1.75 / 1.25); --text-2xl: 1.5rem; --text-2xl--line-height: calc(2 / 1.5); --text-3xl: 1.875rem; --text-3xl--line-height: calc(2.25 / 1.875); --text-4xl: 2.25rem; --text-4xl--line-height: calc(2.5 / 2.25); --text-5xl: 3rem; --text-5xl--line-height: 1; --text-6xl: 3.75rem; --text-6xl--line-height: 1; --text-9xl: 8rem; --text-9xl--line-height: 1; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --font-weight-black: 900; --tracking-tight: -0.025em; --tracking-wide: 0.025em; --tracking-wider: 0.05em; --tracking-widest: 0.1em; --leading-snug: 1.375; --leading-relaxed: 1.625; --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; --radius-2xl: 1rem; --radius-3xl: 1.5rem; --ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-out: cubic-bezier(0, 0, 0.2, 1); --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); --animate-spin: spin 1s linear infinite; --blur-sm: 8px; --blur-md: 12px; --blur-3xl: 64px; --default-transition-duration: 150ms; --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); --font-display: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif; }
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button { box-sizing: border-box; margin: 0px; padding: 0px; border: 0px solid; }
html, :host { line-height: 1.5; text-size-adjust: 100%; tab-size: 4; font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); font-feature-settings: var(--default-font-feature-settings, normal); font-variation-settings: var(--default-font-variation-settings, normal); -webkit-tap-highlight-color: transparent; }
hr { height: 0px; color: inherit; border-top-width: 1px; }
abbr:where([title]) { text-decoration: underline dotted; }
h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
a { color: inherit; text-decoration: inherit; }
b, strong { font-weight: bolder; }
code, kbd, samp, pre { font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); font-feature-settings: var(--default-mono-font-feature-settings, normal); font-variation-settings: var(--default-mono-font-variation-settings, normal); font-size: 1em; }
small { font-size: 80%; }
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
sub { bottom: -0.25em; }
sup { top: -0.5em; }
table { text-indent: 0px; border-color: inherit; border-collapse: collapse; }
progress { vertical-align: baseline; }
summary { display: list-item; }
ol, ul, menu { list-style: none; }
img, svg, video, canvas, audio, iframe, embed, object { display: block; vertical-align: middle; }
img, video { max-width: 100%; height: auto; }
button, input, select, optgroup, textarea, ::file-selector-button { font: inherit; letter-spacing: inherit; color: inherit; border-radius: 0px; background-color: transparent; opacity: 1; }
:where(select:is([multiple], [size])) optgroup { font-weight: bolder; }
:where(select:is([multiple], [size])) optgroup option { padding-inline-start: 20px; }
::file-selector-button { margin-inline-end: 4px; }
::placeholder { opacity: 1; }
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
::placeholder {
color: currentcolor;
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(currentcolor, transparent);
}
}
}
textarea { resize: vertical; }
::-webkit-search-decoration { appearance: none; }
::-webkit-date-and-time-value { min-height: 1lh; text-align: inherit; }
::-webkit-datetime-edit { display: inline-flex; }
::-webkit-datetime-edit-fields-wrapper { padding: 0px; }
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { padding-block: 0px; }
::-webkit-calendar-picker-indicator { line-height: 1; }
button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { appearance: button; }
::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; }
[hidden]:where(:not([hidden="until-found"])) { display: none !important; }
}
@layer utilities {
.pointer-events-auto { pointer-events: auto; }
.pointer-events-none { pointer-events: none; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border-width: 0px; }
.absolute { position: absolute; }
.fixed { position: fixed; }
.relative { position: relative; }
.sticky { position: sticky; }
.inset-0 { inset: 0px; }
.inset-x-0 { inset-inline: 0px; }
.inset-y-0 { inset-block: 0px; }
.-top-2 { top: calc(var(--spacing) * -2); }
.top-0 { top: 0px; }
.top-0\.5 { top: calc(var(--spacing) * 0.5); }
.top-1 { top: var(--spacing); }
.top-1\/2 { top: calc(50%); }
.top-2 { top: calc(var(--spacing) * 2); }
.top-2\.5 { top: calc(var(--spacing) * 2.5); }
.top-3 { top: calc(var(--spacing) * 3); }
.top-4 { top: calc(var(--spacing) * 4); }
.top-5 { top: calc(var(--spacing) * 5); }
.top-6 { top: calc(var(--spacing) * 6); }
.top-8 { top: calc(var(--spacing) * 8); }
.top-10 { top: calc(var(--spacing) * 10); }
.top-12 { top: calc(var(--spacing) * 12); }
.top-14 { top: calc(var(--spacing) * 14); }
.top-16 { top: calc(var(--spacing) * 16); }
.-right-2 { right: calc(var(--spacing) * -2); }
.right-0 { right: 0px; }
.right-0\.5 { right: calc(var(--spacing) * 0.5); }
.right-1 { right: var(--spacing); }
.right-2 { right: calc(var(--spacing) * 2); }
.right-2\.5 { right: calc(var(--spacing) * 2.5); }
.right-4 { right: calc(var(--spacing) * 4); }
.right-5 { right: calc(var(--spacing) * 5); }
.right-8 { right: calc(var(--spacing) * 8); }
.-bottom-2 { bottom: calc(var(--spacing) * -2); }
.bottom-0 { bottom: 0px; }
.bottom-0\.5 { bottom: calc(var(--spacing) * 0.5); }
.bottom-2 { bottom: calc(var(--spacing) * 2); }
.bottom-2\.5 { bottom: calc(var(--spacing) * 2.5); }
.bottom-4 { bottom: calc(var(--spacing) * 4); }
.bottom-10 { bottom: calc(var(--spacing) * 10); }
.left-0 { left: 0px; }
.left-0\.5 { left: calc(var(--spacing) * 0.5); }
.left-1\/2 { left: calc(50%); }
.left-2 { left: calc(var(--spacing) * 2); }
.left-2\.5 { left: calc(var(--spacing) * 2.5); }
.left-3 { left: calc(var(--spacing) * 3); }
.left-4 { left: calc(var(--spacing) * 4); }
.left-12 { left: calc(var(--spacing) * 12); }
.z-10 { z-index: 10; }
.z-40 { z-index: 40; }
.z-50 { z-index: 50; }
.z-\[5\] { z-index: 5; }
.z-\[60\] { z-index: 60; }
.z-\[80\] { z-index: 80; }
.z-\[100\] { z-index: 100; }
.z-\[200\] { z-index: 200; }
.z-\[210\] { z-index: 210; }
.z-\[220\] { z-index: 220; }
.z-\[10000\] { z-index: 10000; }
.container {
width: 100%;
@media (width >= 40rem) {
max-width: 40rem;
}
@media (width >= 48rem) {
max-width: 48rem;
}
@media (width >= 64rem) {
max-width: 64rem;
}
@media (width >= 80rem) {
max-width: 80rem;
}
@media (width >= 96rem) {
max-width: 96rem;
}
}
.mx-1 { margin-inline: var(--spacing); }
.mx-2 { margin-inline: calc(var(--spacing) * 2); }
.mx-4 { margin-inline: calc(var(--spacing) * 4); }
.mx-auto { margin-inline: auto; }
.my-2 { margin-block: calc(var(--spacing) * 2); }
.my-3 { margin-block: calc(var(--spacing) * 3); }
.my-4 { margin-block: calc(var(--spacing) * 4); }
.my-8 { margin-block: calc(var(--spacing) * 8); }
.-mt-16 { margin-top: calc(var(--spacing) * -16); }
.mt-0\.5 { margin-top: calc(var(--spacing) * 0.5); }
.mt-1 { margin-top: var(--spacing); }
.mt-1\.5 { margin-top: calc(var(--spacing) * 1.5); }
.mt-2 { margin-top: calc(var(--spacing) * 2); }
.mt-3 { margin-top: calc(var(--spacing) * 3); }
.mt-4 { margin-top: calc(var(--spacing) * 4); }
.mt-6 { margin-top: calc(var(--spacing) * 6); }
.mt-8 { margin-top: calc(var(--spacing) * 8); }
.mt-10 { margin-top: calc(var(--spacing) * 10); }
.mt-12 { margin-top: calc(var(--spacing) * 12); }
.mt-16 { margin-top: calc(var(--spacing) * 16); }
.-mr-1 { margin-right: calc(var(--spacing) * -1); }
.-mr-16 { margin-right: calc(var(--spacing) * -16); }
.mr-2 { margin-right: calc(var(--spacing) * 2); }
.mb-0\.5 { margin-bottom: calc(var(--spacing) * 0.5); }
.mb-1 { margin-bottom: var(--spacing); }
.mb-1\.5 { margin-bottom: calc(var(--spacing) * 1.5); }
.mb-2 { margin-bottom: calc(var(--spacing) * 2); }
.mb-3 { margin-bottom: calc(var(--spacing) * 3); }
.mb-4 { margin-bottom: calc(var(--spacing) * 4); }
.mb-5 { margin-bottom: calc(var(--spacing) * 5); }
.mb-6 { margin-bottom: calc(var(--spacing) * 6); }
.mb-8 { margin-bottom: calc(var(--spacing) * 8); }
.mb-10 { margin-bottom: calc(var(--spacing) * 10); }
.mb-12 { margin-bottom: calc(var(--spacing) * 12); }
.mb-16 { margin-bottom: calc(var(--spacing) * 16); }
.ml-1\.5 { margin-left: calc(var(--spacing) * 1.5); }
.ml-2 { margin-left: calc(var(--spacing) * 2); }
.ml-3 { margin-left: calc(var(--spacing) * 3); }
.ml-4 { margin-left: calc(var(--spacing) * 4); }
.ml-6 { margin-left: calc(var(--spacing) * 6); }
.ml-\[21px\] { margin-left: 21px; }
.ml-auto { margin-left: auto; }
.block { display: block; }
.flex { display: flex; }
.grid { display: grid; }
.hidden { display: none; }
.inline-block { display: inline-block; }
.inline-flex { display: inline-flex; }
.table { display: table; }
.aspect-\[2\/3\] { aspect-ratio: 2 / 3; }
.aspect-\[3\/2\] { aspect-ratio: 3 / 2; }
.aspect-\[3\/4\] { aspect-ratio: 3 / 4; }
.aspect-\[4\/3\] { aspect-ratio: 4 / 3; }
.aspect-\[4\/5\] { aspect-ratio: 4 / 5; }
.aspect-\[9\/16\] { aspect-ratio: 9 / 16; }
.aspect-square { aspect-ratio: 1 / 1; }
.h-0\.5 { height: calc(var(--spacing) * 0.5); }
.h-1 { height: var(--spacing); }
.h-1\.5 { height: calc(var(--spacing) * 1.5); }
.h-3 { height: calc(var(--spacing) * 3); }
.h-3\.5 { height: calc(var(--spacing) * 3.5); }
.h-4 { height: calc(var(--spacing) * 4); }
.h-5 { height: calc(var(--spacing) * 5); }
.h-6 { height: calc(var(--spacing) * 6); }
.h-7 { height: calc(var(--spacing) * 7); }
.h-8 { height: calc(var(--spacing) * 8); }
.h-9 { height: calc(var(--spacing) * 9); }
.h-10 { height: calc(var(--spacing) * 10); }
.h-11 { height: calc(var(--spacing) * 11); }
.h-12 { height: calc(var(--spacing) * 12); }
.h-14 { height: calc(var(--spacing) * 14); }
.h-16 { height: calc(var(--spacing) * 16); }
.h-20 { height: calc(var(--spacing) * 20); }
.h-24 { height: calc(var(--spacing) * 24); }
.h-28 { height: calc(var(--spacing) * 28); }
.h-32 { height: calc(var(--spacing) * 32); }
.h-36 { height: calc(var(--spacing) * 36); }
.h-48 { height: calc(var(--spacing) * 48); }
.h-56 { height: calc(var(--spacing) * 56); }
.h-60 { height: calc(var(--spacing) * 60); }
.h-\[1px\] { height: 1px; }
.h-\[38px\] { height: 38px; }
.h-\[85vh\] { height: 85vh; }
.h-\[90\%\] { height: 90%; }
.h-auto { height: auto; }
.h-full { height: 100%; }
.h-px { height: 1px; }
.h-screen { height: 100vh; }
.max-h-24 { max-height: calc(var(--spacing) * 24); }
.max-h-40 { max-height: calc(var(--spacing) * 40); }
.max-h-48 { max-height: calc(var(--spacing) * 48); }
.max-h-53 { max-height: calc(var(--spacing) * 53); }
.max-h-56 { max-height: calc(var(--spacing) * 56); }
.max-h-\[8em\] { max-height: 8em; }
.max-h-\[78vh\] { max-height: 78vh; }
.max-h-\[85vh\] { max-height: 85vh; }
.max-h-\[90vh\] { max-height: 90vh; }
.max-h-full { max-height: 100%; }
.min-h-0 { min-height: 0px; }
.min-h-\[1\.75em\] { min-height: 1.75em; }
.min-h-\[192px\] { min-height: 192px; }
.min-h-\[280px\] { min-height: 280px; }
.min-h-\[300px\] { min-height: 300px; }
.min-h-\[max\(960px\,100vh\)\] { min-height: max(960px, 100vh); }
.min-h-full { min-height: 100%; }
.min-h-screen { min-height: 100vh; }
.w-1 { width: var(--spacing); }
.w-1\.5 { width: calc(var(--spacing) * 1.5); }
.w-2 { width: calc(var(--spacing) * 2); }
.w-3 { width: calc(var(--spacing) * 3); }
.w-3\.5 { width: calc(var(--spacing) * 3.5); }
.w-4 { width: calc(var(--spacing) * 4); }
.w-5 { width: calc(var(--spacing) * 5); }
.w-6 { width: calc(var(--spacing) * 6); }
.w-7 { width: calc(var(--spacing) * 7); }
.w-8 { width: calc(var(--spacing) * 8); }
.w-9 { width: calc(var(--spacing) * 9); }
.w-10 { width: calc(var(--spacing) * 10); }
.w-11 { width: calc(var(--spacing) * 11); }
.w-12 { width: calc(var(--spacing) * 12); }
.w-14 { width: calc(var(--spacing) * 14); }
.w-16 { width: calc(var(--spacing) * 16); }
.w-20 { width: calc(var(--spacing) * 20); }
.w-24 { width: calc(var(--spacing) * 24); }
.w-28 { width: calc(var(--spacing) * 28); }
.w-32 { width: calc(var(--spacing) * 32); }
.w-36 { width: calc(var(--spacing) * 36); }
.w-40 { width: calc(var(--spacing) * 40); }
.w-48 { width: calc(var(--spacing) * 48); }
.w-60 { width: calc(var(--spacing) * 60); }
.w-64 { width: calc(var(--spacing) * 64); }
.w-\[70\%\] { width: 70%; }
.w-\[90\%\] { width: 90%; }
.w-\[92vw\] { width: 92vw; }
.w-\[95vw\] { width: 95vw; }
.w-auto { width: auto; }
.w-fit { width: fit-content; }
.w-full { width: 100%; }
.w-px { width: 1px; }
.max-w-2xl { max-width: var(--container-2xl); }
.max-w-4xl { max-width: var(--container-4xl); }
.max-w-5xl { max-width: var(--container-5xl); }
.max-w-7xl { max-width: var(--container-7xl); }
.max-w-53 { max-width: calc(var(--spacing) * 53); }
.max-w-\[10em\] { max-width: 10em; }
.max-w-\[66\.666\%\] { max-width: 66.666%; }
.max-w-\[85\%\] { max-width: 85%; }
.max-w-\[92vw\] { max-width: 92vw; }
.max-w-\[120px\] { max-width: 120px; }
.max-w-\[160px\] { max-width: 160px; }
.max-w-\[420px\] { max-width: 420px; }
.max-w-\[480px\] { max-width: 480px; }
.max-w-\[1000px\] { max-width: 1000px; }
.max-w-\[calc\(100\%-24px\)\] { max-width: calc(100% - 24px); }
.max-w-full { max-width: 100%; }
.max-w-lg { max-width: var(--container-lg); }
.max-w-md { max-width: var(--container-md); }
.max-w-xl { max-width: var(--container-xl); }
.min-w-0 { min-width: 0px; }
.min-w-\[80px\] { min-width: 80px; }
.min-w-full { min-width: 100%; }
.flex-1 { flex: 1 1 0%; }
.flex-none { flex: 0 0 auto; }
.flex-shrink { flex-shrink: 1; }
.flex-shrink-0 { flex-shrink: 0; }
.shrink-0 { flex-shrink: 0; }
.grow { flex-grow: 1; }
.border-collapse { border-collapse: collapse; }
.-translate-x-1\/2 { --tw-translate-x: calc(calc(1 / 2 * 100%) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); }
.-translate-y-1\/2 { --tw-translate-y: calc(calc(1 / 2 * 100%) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); }
.-translate-y-2 { --tw-translate-y: calc(var(--spacing) * -2); translate: var(--tw-translate-x) var(--tw-translate-y); }
.-translate-y-5 { --tw-translate-y: calc(var(--spacing) * -5); translate: var(--tw-translate-x) var(--tw-translate-y); }
.translate-y-0 { --tw-translate-y: 0px; translate: var(--tw-translate-x) var(--tw-translate-y); }
.rotate-180 { rotate: 180deg; }
.transform { transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); }
.animate-spin { animation: var(--animate-spin); }
.cursor-grab { cursor: grab; }
.cursor-grabbing { cursor: grabbing; }
.cursor-not-allowed { cursor: not-allowed; }
.cursor-nwse-resize { cursor: nwse-resize; }
.cursor-pointer { cursor: pointer; }
.cursor-zoom-in { cursor: zoom-in; }
.touch-none { touch-action: none; }
.resize { resize: both; }
.resize-none { resize: none; }
.resize-y { resize: vertical; }
.scroll-mt-20 { scroll-margin-top: calc(var(--spacing) * 20); }
.list-inside { list-style-position: inside; }
.list-disc { list-style-type: disc; }
.columns-1 { columns: 1; }
.columns-2 { columns: 2; }
.columns-3 { columns: 3; }
.break-inside-avoid { break-inside: avoid; }
.break-inside-avoid-column { break-inside: avoid-column; }
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0px, 1fr)); }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0px, 1fr)); }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0px, 1fr)); }
.grid-rows-2 { grid-template-rows: repeat(2, minmax(0px, 1fr)); }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.items-baseline { align-items: baseline; }
.items-center { align-items: center; }
.items-end { align-items: flex-end; }
.items-start { align-items: flex-start; }
.items-stretch { align-items: stretch; }
.justify-between { justify-content: space-between; }
.justify-center { justify-content: center; }
.justify-end { justify-content: flex-end; }
.gap-0\.5 { gap: calc(var(--spacing) * 0.5); }
.gap-1 { gap: var(--spacing); }
.gap-1\.5 { gap: calc(var(--spacing) * 1.5); }
.gap-2 { gap: calc(var(--spacing) * 2); }
.gap-2\.5 { gap: calc(var(--spacing) * 2.5); }
.gap-3 { gap: calc(var(--spacing) * 3); }
.gap-4 { gap: calc(var(--spacing) * 4); }
.gap-6 { gap: calc(var(--spacing) * 6); }
.gap-8 { gap: calc(var(--spacing) * 8); }
.gap-12 { gap: calc(var(--spacing) * 12); }
:where(.space-y-1 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(var(--spacing) * var(--tw-space-y-reverse)); margin-block-end: calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse))); }
:where(.space-y-1\.5 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse))); }
:where(.space-y-2 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse))); }
:where(.space-y-3 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse))); }
:where(.space-y-4 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); }
:where(.space-y-5 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse))); }
:where(.space-y-6 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); }
:where(.space-y-8 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse))); }
:where(.space-y-10 > :not(:last-child)) { --tw-space-y-reverse: 0; margin-block-start: calc(calc(var(--spacing) * 10) * var(--tw-space-y-reverse)); margin-block-end: calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-y-reverse))); }
.gap-x-12 { column-gap: calc(var(--spacing) * 12); }
:where(.space-x-1\.5 > :not(:last-child)) { --tw-space-x-reverse: 0; margin-inline-start: calc(calc(var(--spacing) * 1.5) * var(--tw-space-x-reverse)); margin-inline-end: calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-x-reverse))); }
:where(.space-x-3 > :not(:last-child)) { --tw-space-x-reverse: 0; margin-inline-start: calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse)); margin-inline-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse))); }
:where(.space-x-4 > :not(:last-child)) { --tw-space-x-reverse: 0; margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse)); margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); }
.gap-y-8 { row-gap: calc(var(--spacing) * 8); }
:where(.divide-y > :not(:last-child)) { --tw-divide-y-reverse: 0; border-bottom-style: var(--tw-border-style); border-top-style: var(--tw-border-style); border-top-width: calc(1px * var(--tw-divide-y-reverse)); border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); }
:where(.divide-gray-50 > :not(:last-child)) { border-color: var(--color-gray-50); }
:where(.divide-gray-200 > :not(:last-child)) { border-color: var(--color-gray-200); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.overflow-hidden { overflow: hidden; }
.overflow-x-auto { overflow-x: auto; }
.overflow-x-hidden { overflow-x: hidden; }
.overflow-y-auto { overflow-y: auto; }
.rounded { border-radius: 0.25rem; }
.rounded-2xl { border-radius: var(--radius-2xl); }
.rounded-3xl { border-radius: var(--radius-3xl); }
.rounded-\[32px\] { border-radius: 32px; }
.rounded-full { border-radius: calc(infinity * 1px); }
.rounded-lg { border-radius: var(--radius-lg); }
.rounded-md { border-radius: var(--radius-md); }
.rounded-sm { border-radius: var(--radius-sm); }
.rounded-xl { border-radius: var(--radius-xl); }
.rounded-l-lg { border-top-left-radius: var(--radius-lg); border-bottom-left-radius: var(--radius-lg); }
.rounded-r-lg { border-top-right-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-lg); }
.border { border-style: var(--tw-border-style); border-width: 1px; }
.border-2 { border-style: var(--tw-border-style); border-width: 2px; }
.border-4 { border-style: var(--tw-border-style); border-width: 4px; }
.border-\[3px\] { border-style: var(--tw-border-style); border-width: 3px; }
.border-\[4px\] { border-style: var(--tw-border-style); border-width: 4px; }
.border-t { border-top-style: var(--tw-border-style); border-top-width: 1px; }
.border-r { border-right-style: var(--tw-border-style); border-right-width: 1px; }
.border-b { border-bottom-style: var(--tw-border-style); border-bottom-width: 1px; }
.border-b-2 { border-bottom-style: var(--tw-border-style); border-bottom-width: 2px; }
.border-l-4 { border-left-style: var(--tw-border-style); border-left-width: 4px; }
.border-dashed { --tw-border-style: dashed; border-style: dashed; }
.border-\[\#FF8C00\] { border-color: rgb(255, 140, 0); }
.border-\[\#eeeeee\] { border-color: rgb(238, 238, 238); }
.border-amber-200 { border-color: var(--color-amber-200); }
.border-amber-300 { border-color: var(--color-amber-300); }
.border-amber-400 { border-color: var(--color-amber-400); }
.border-black { border-color: var(--color-black); }
.border-blue-500 { border-color: var(--color-blue-500); }
.border-gray-100 { border-color: var(--color-gray-100); }
.border-gray-200 { border-color: var(--color-gray-200); }
.border-gray-300 { border-color: var(--color-gray-300); }
.border-gray-900 { border-color: var(--color-gray-900); }
.border-green-100 { border-color: var(--color-green-100); }
.border-green-500 { border-color: var(--color-green-500); }
.border-neutral-100 { border-color: var(--color-neutral-100); }
.border-neutral-200 { border-color: var(--color-neutral-200); }
.border-neutral-200\/70 {
border-color: color-mix(in srgb, oklch(0.922 0 none) 70%, transparent 30%);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-neutral-200) 70%, transparent);
}
}
.border-neutral-800 { border-color: var(--color-neutral-800); }
.border-orange-100\/50 {
border-color: color-mix(in srgb, oklch(0.954 0.038 75.164), transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-orange-100) 50%, transparent);
}
}
.border-purple-500 { border-color: var(--color-purple-500); }
.border-red-100 { border-color: var(--color-red-100); }
.border-red-200 { border-color: var(--color-red-200); }
.border-red-500 { border-color: var(--color-red-500); }
.border-slate-200 { border-color: var(--color-slate-200); }
.border-transparent { border-color: transparent; }
.border-white { border-color: var(--color-white); }
.border-white\/30 {
border-color: color-mix(in srgb, rgb(255, 255, 255) 30%, transparent 70%);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-white) 30%, transparent);
}
}
.border-t-gray-900 { border-top-color: var(--color-gray-900); }
.border-t-transparent { border-top-color: transparent; }
.bg-\[\#22C55E\] { background-color: rgb(34, 197, 94); }
.bg-\[\#F5F5F5\] { background-color: rgb(245, 245, 245); }
.bg-\[\#b3341f\] { background-color: rgb(179, 52, 31); }
.bg-\[\#f0f5ff\] { background-color: rgb(240, 245, 255); }
.bg-\[\#f5f5f5\] { background-color: rgb(245, 245, 245); }
.bg-\[\#f5f5f7\] { background-color: rgb(245, 245, 247); }
.bg-\[\#f6e8d3\] { background-color: rgb(246, 232, 211); }
.bg-\[\#f9f9f9\] { background-color: rgb(249, 249, 249); }
.bg-\[\#f9fafb\] { background-color: rgb(249, 250, 251); }
.bg-amber-50 { background-color: var(--color-amber-50); }
.bg-amber-50\/60 {
background-color: color-mix(in srgb, oklch(0.987 0.022 95.277) 60%, transparent 40%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-amber-50) 60%, transparent);
}
}
.bg-amber-100 { background-color: var(--color-amber-100); }
.bg-amber-500 { background-color: var(--color-amber-500); }
.bg-black { background-color: var(--color-black); }
.bg-black\/10 {
background-color: color-mix(in srgb, rgb(0, 0, 0) 10%, transparent 90%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 10%, transparent);
}
}
.bg-black\/30 {
background-color: color-mix(in srgb, rgb(0, 0, 0) 30%, transparent 70%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 30%, transparent);
}
}
.bg-black\/40 {
background-color: color-mix(in srgb, rgb(0, 0, 0) 40%, transparent 60%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 40%, transparent);
}
}
.bg-black\/50 {
background-color: color-mix(in srgb, rgb(0, 0, 0), transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
}
}
.bg-black\/55 {
background-color: color-mix(in srgb, rgb(0, 0, 0) 55%, transparent 45%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 55%, transparent);
}
}
.bg-black\/60 {
background-color: color-mix(in srgb, rgb(0, 0, 0) 60%, transparent 40%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 60%, transparent);
}
}
.bg-black\/90 {
background-color: color-mix(in srgb, rgb(0, 0, 0) 90%, transparent 10%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 90%, transparent);
}
}
.bg-blue-50 { background-color: var(--color-blue-50); }
.bg-blue-100 { background-color: var(--color-blue-100); }
.bg-gray-50 { background-color: var(--color-gray-50); }
.bg-gray-50\/50 {
background-color: color-mix(in srgb, oklch(0.985 0.002 247.839), transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-gray-50) 50%, transparent);
}
}
.bg-gray-100 { background-color: var(--color-gray-100); }
.bg-gray-200 { background-color: var(--color-gray-200); }
.bg-gray-300 { background-color: var(--color-gray-300); }
.bg-gray-300\/60 {
background-color: color-mix(in srgb, oklch(0.872 0.01 258.338) 60%, transparent 40%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-gray-300) 60%, transparent);
}
}
.bg-gray-400 { background-color: var(--color-gray-400); }
.bg-gray-500 { background-color: var(--color-gray-500); }
.bg-gray-900 { background-color: var(--color-gray-900); }
.bg-gray-900\/40 {
background-color: color-mix(in srgb, oklch(0.21 0.034 264.665) 40%, transparent 60%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-gray-900) 40%, transparent);
}
}
.bg-gray-900\/60 {
background-color: color-mix(in srgb, oklch(0.21 0.034 264.665) 60%, transparent 40%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-gray-900) 60%, transparent);
}
}
.bg-gray-900\/85 {
background-color: color-mix(in srgb, oklch(0.21 0.034 264.665) 85%, transparent 15%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-gray-900) 85%, transparent);
}
}
.bg-green-50 { background-color: var(--color-green-50); }
.bg-green-100 { background-color: var(--color-green-100); }
.bg-indigo-100 { background-color: var(--color-indigo-100); }
.bg-neutral-50 { background-color: var(--color-neutral-50); }
.bg-neutral-100 { background-color: var(--color-neutral-100); }
.bg-neutral-200 { background-color: var(--color-neutral-200); }
.bg-neutral-800\/80 {
background-color: color-mix(in srgb, oklch(0.269 0 none) 80%, transparent 20%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-neutral-800) 80%, transparent);
}
}
.bg-neutral-900 { background-color: var(--color-neutral-900); }
.bg-neutral-950 { background-color: var(--color-neutral-950); }
.bg-orange-50 { background-color: var(--color-orange-50); }
.bg-orange-50\/80 {
background-color: color-mix(in srgb, oklch(0.98 0.016 73.684) 80%, transparent 20%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-orange-50) 80%, transparent);
}
}
.bg-pink-100 { background-color: var(--color-pink-100); }
.bg-purple-100 { background-color: var(--color-purple-100); }
.bg-red-50 { background-color: var(--color-red-50); }
.bg-red-50\/80 {
background-color: color-mix(in srgb, oklch(0.971 0.013 17.38) 80%, transparent 20%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-red-50) 80%, transparent);
}
}
.bg-red-100 { background-color: var(--color-red-100); }
.bg-red-500 { background-color: var(--color-red-500); }
.bg-transparent { background-color: transparent; }
.bg-white { background-color: var(--color-white); }
.bg-white\/5 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 5%, transparent 95%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 5%, transparent);
}
}
.bg-white\/10 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 10%, transparent 90%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 10%, transparent);
}
}
.bg-white\/15 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 15%, transparent 85%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 15%, transparent);
}
}
.bg-white\/20 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 20%, transparent 80%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 20%, transparent);
}
}
.bg-white\/25 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 25%, transparent 75%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 25%, transparent);
}
}
.bg-white\/30 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 30%, transparent 70%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 30%, transparent);
}
}
.bg-white\/40 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 40%, transparent 60%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 40%, transparent);
}
}
.bg-white\/50 {
background-color: color-mix(in srgb, rgb(255, 255, 255), transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 50%, transparent);
}
}
.bg-white\/60 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 60%, transparent 40%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 60%, transparent);
}
}
.bg-white\/70 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 70%, transparent 30%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 70%, transparent);
}
}
.bg-white\/80 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 80%, transparent 20%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 80%, transparent);
}
}
.bg-white\/85 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 85%, transparent 15%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 85%, transparent);
}
}
.bg-white\/90 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 90%, transparent 10%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 90%, transparent);
}
}
.bg-white\/95 {
background-color: color-mix(in srgb, rgb(255, 255, 255) 95%, transparent 5%);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-white) 95%, transparent);
}
}
.bg-yellow-100 { background-color: var(--color-yellow-100); }
.bg-linear-to-b {
--tw-gradient-position: to bottom;
@supports (background-image: linear-gradient(in lab, red, red)) {
--tw-gradient-position: to bottom in oklab;
}
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-br {
--tw-gradient-position: to bottom right;
@supports (background-image: linear-gradient(in lab, red, red)) {
--tw-gradient-position: to bottom right in oklab;
}
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-linear-to-t {
--tw-gradient-position: to top;
@supports (background-image: linear-gradient(in lab, red, red)) {
--tw-gradient-position: to top in oklab;
}
background-image: linear-gradient(var(--tw-gradient-stops));
}
.bg-gradient-to-r { --tw-gradient-position: to right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); }
.from-\[\#FF8C00\] { --tw-gradient-from: #FF8C00; --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.from-black\/40 {
--tw-gradient-from: color-mix(in srgb, #000 40%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-gradient-from: color-mix(in oklab, var(--color-black) 40%, transparent);
}
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-black\/70 {
--tw-gradient-from: color-mix(in srgb, #000 70%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-gradient-from: color-mix(in oklab, var(--color-black) 70%, transparent);
}
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
.from-blue-500 { --tw-gradient-from: var(--color-blue-500); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.from-emerald-200 { --tw-gradient-from: var(--color-emerald-200); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.from-indigo-900 { --tw-gradient-from: var(--color-indigo-900); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.from-purple-500 { --tw-gradient-from: var(--color-purple-500); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.from-sky-200 { --tw-gradient-from: var(--color-sky-200); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.via-amber-200 { --tw-gradient-via: var(--color-amber-200); --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); }
.via-green-500 { --tw-gradient-via: var(--color-green-500); --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); }
.via-purple-700 { --tw-gradient-via: var(--color-purple-700); --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); }
.via-transparent { --tw-gradient-via: transparent; --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position); --tw-gradient-stops: var(--tw-gradient-via-stops); }
.to-\[\#FFD700\] { --tw-gradient-to: #FFD700; --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.to-blue-500 { --tw-gradient-to: var(--color-blue-500); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.to-emerald-800 { --tw-gradient-to: var(--color-emerald-800); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.to-indigo-600 { --tw-gradient-to: var(--color-indigo-600); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.to-orange-400 { --tw-gradient-to: var(--color-orange-400); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.to-transparent { --tw-gradient-to: transparent; --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); }
.fill-current { fill: currentcolor; }
.object-contain { object-fit: contain; }
.object-cover { object-fit: cover; }
.p-0 { padding: 0px; }
.p-1 { padding: var(--spacing); }
.p-1\.5 { padding: calc(var(--spacing) * 1.5); }
.p-2 { padding: calc(var(--spacing) * 2); }
.p-3 { padding: calc(var(--spacing) * 3); }
.p-4 { padding: calc(var(--spacing) * 4); }
.p-6 { padding: calc(var(--spacing) * 6); }
.p-8 { padding: calc(var(--spacing) * 8); }
.p-10 { padding: calc(var(--spacing) * 10); }
.p-12 { padding: calc(var(--spacing) * 12); }
.px-0 { padding-inline: 0px; }
.px-1 { padding-inline: var(--spacing); }
.px-1\.5 { padding-inline: calc(var(--spacing) * 1.5); }
.px-2 { padding-inline: calc(var(--spacing) * 2); }
.px-2\.5 { padding-inline: calc(var(--spacing) * 2.5); }
.px-3 { padding-inline: calc(var(--spacing) * 3); }
.px-4 { padding-inline: calc(var(--spacing) * 4); }
.px-5 { padding-inline: calc(var(--spacing) * 5); }
.px-6 { padding-inline: calc(var(--spacing) * 6); }
.px-7 { padding-inline: calc(var(--spacing) * 7); }
.px-8 { padding-inline: calc(var(--spacing) * 8); }
.px-\[10\%\] { padding-inline: 10%; }
.py-0\.5 { padding-block: calc(var(--spacing) * 0.5); }
.py-1 { padding-block: var(--spacing); }
.py-1\.5 { padding-block: calc(var(--spacing) * 1.5); }
.py-2 { padding-block: calc(var(--spacing) * 2); }
.py-2\.5 { padding-block: calc(var(--spacing) * 2.5); }
.py-3 { padding-block: calc(var(--spacing) * 3); }
.py-3\.5 { padding-block: calc(var(--spacing) * 3.5); }
.py-4 { padding-block: calc(var(--spacing) * 4); }
.py-5 { padding-block: calc(var(--spacing) * 5); }
.py-6 { padding-block: calc(var(--spacing) * 6); }
.py-8 { padding-block: calc(var(--spacing) * 8); }
.py-10 { padding-block: calc(var(--spacing) * 10); }
.py-12 { padding-block: calc(var(--spacing) * 12); }
.py-16 { padding-block: calc(var(--spacing) * 16); }
.py-20 { padding-block: calc(var(--spacing) * 20); }
.py-24 { padding-block: calc(var(--spacing) * 24); }
.pt-0\.5 { padding-top: calc(var(--spacing) * 0.5); }
.pt-1 { padding-top: var(--spacing); }
.pt-2 { padding-top: calc(var(--spacing) * 2); }
.pt-3 { padding-top: calc(var(--spacing) * 3); }
.pt-4 { padding-top: calc(var(--spacing) * 4); }
.pt-6 { padding-top: calc(var(--spacing) * 6); }
.pt-8 { padding-top: calc(var(--spacing) * 8); }
.pt-14 { padding-top: calc(var(--spacing) * 14); }
.pt-32 { padding-top: calc(var(--spacing) * 32); }
.pt-40 { padding-top: calc(var(--spacing) * 40); }
.pr-1\.5 { padding-right: calc(var(--spacing) * 1.5); }
.pr-3 { padding-right: calc(var(--spacing) * 3); }
.pr-4 { padding-right: calc(var(--spacing) * 4); }
.pr-32 { padding-right: calc(var(--spacing) * 32); }
.pb-0 { padding-bottom: 0px; }
.pb-1 { padding-bottom: var(--spacing); }
.pb-2 { padding-bottom: calc(var(--spacing) * 2); }
.pb-3 { padding-bottom: calc(var(--spacing) * 3); }
.pb-4 { padding-bottom: calc(var(--spacing) * 4); }
.pb-8 { padding-bottom: calc(var(--spacing) * 8); }
.pb-16 { padding-bottom: calc(var(--spacing) * 16); }
.pl-1\.5 { padding-left: calc(var(--spacing) * 1.5); }
.pl-4 { padding-left: calc(var(--spacing) * 4); }
.pl-16 { padding-left: calc(var(--spacing) * 16); }
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.font-display { font-family: var(--font-display); }
.font-mono { font-family: var(--font-mono); }
.text-2xl { font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); }
.text-3xl { font-size: var(--text-3xl); line-height: var(--tw-leading, var(--text-3xl--line-height)); }
.text-4xl { font-size: var(--text-4xl); line-height: var(--tw-leading, var(--text-4xl--line-height)); }
.text-5xl { font-size: var(--text-5xl); line-height: var(--tw-leading, var(--text-5xl--line-height)); }
.text-6xl { font-size: var(--text-6xl); line-height: var(--tw-leading, var(--text-6xl--line-height)); }
.text-9xl { font-size: var(--text-9xl); line-height: var(--tw-leading, var(--text-9xl--line-height)); }
.text-base { font-size: var(--text-base); line-height: var(--tw-leading, var(--text-base--line-height)); }
.text-lg { font-size: var(--text-lg); line-height: var(--tw-leading, var(--text-lg--line-height)); }
.text-sm { font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); }
.text-xl { font-size: var(--text-xl); line-height: var(--tw-leading, var(--text-xl--line-height)); }
.text-xs { font-size: var(--text-xs); line-height: var(--tw-leading, var(--text-xs--line-height)); }
.text-\[8px\] { font-size: 8px; }
.text-\[9px\] { font-size: 9px; }
.text-\[10px\] { font-size: 10px; }
.text-\[11px\] { font-size: 11px; }
.text-\[12px\] { font-size: 12px; }
.text-\[13px\] { font-size: 13px; }
.text-\[14px\] { font-size: 14px; }
.text-\[15px\] { font-size: 15px; }
.text-\[16px\] { font-size: 16px; }
.text-\[17px\] { font-size: 17px; }
.text-\[18px\] { font-size: 18px; }
.text-\[20px\] { font-size: 20px; }
.text-\[22px\] { font-size: 22px; }
.leading-4 { --tw-leading: calc(var(--spacing) * 4); line-height: calc(var(--spacing) * 4); }
.leading-5 { --tw-leading: calc(var(--spacing) * 5); line-height: calc(var(--spacing) * 5); }
.leading-\[1\.6\] { --tw-leading: 1.6; line-height: 1.6; }
.leading-none { --tw-leading: 1; line-height: 1; }
.leading-relaxed { --tw-leading: var(--leading-relaxed); line-height: var(--leading-relaxed); }
.leading-snug { --tw-leading: var(--leading-snug); line-height: var(--leading-snug); }
.font-black { --tw-font-weight: var(--font-weight-black); font-weight: var(--font-weight-black); }
.font-bold { --tw-font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold); }
.font-medium { --tw-font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium); }
.font-normal { --tw-font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal); }
.font-semibold { --tw-font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold); }
.tracking-\[0\.3em\] { --tw-tracking: 0.3em; letter-spacing: 0.3em; }
.tracking-tight { --tw-tracking: var(--tracking-tight); letter-spacing: var(--tracking-tight); }
.tracking-wide { --tw-tracking: var(--tracking-wide); letter-spacing: var(--tracking-wide); }
.tracking-wider { --tw-tracking: var(--tracking-wider); letter-spacing: var(--tracking-wider); }
.tracking-widest { --tw-tracking: var(--tracking-widest); letter-spacing: var(--tracking-widest); }
.break-words { overflow-wrap: break-word; }
.break-all { word-break: break-all; }
.whitespace-nowrap { white-space: nowrap; }
.whitespace-pre-wrap { white-space: pre-wrap; }
.text-\[\#1b1b1b\] { color: rgb(27, 27, 27); }
.text-\[\#22C55E\] { color: rgb(34, 197, 94); }
.text-\[\#333333\] { color: rgb(51, 51, 51); }
.text-\[\#f9f9f9\] { color: rgb(249, 249, 249); }
.text-amber-600 { color: var(--color-amber-600); }
.text-amber-700 { color: var(--color-amber-700); }
.text-amber-800 { color: var(--color-amber-800); }
.text-black { color: var(--color-black); }
.text-blue-500 { color: var(--color-blue-500); }
.text-blue-600 { color: var(--color-blue-600); }
.text-blue-700 { color: var(--color-blue-700); }
.text-emerald-500 { color: var(--color-emerald-500); }
.text-emerald-600 { color: var(--color-emerald-600); }
.text-gray-300 { color: var(--color-gray-300); }
.text-gray-400 { color: var(--color-gray-400); }
.text-gray-500 { color: var(--color-gray-500); }
.text-gray-600 { color: var(--color-gray-600); }
.text-gray-700 { color: var(--color-gray-700); }
.text-gray-800 { color: var(--color-gray-800); }
.text-gray-900 { color: var(--color-gray-900); }
.text-green-500 { color: var(--color-green-500); }
.text-green-600 { color: var(--color-green-600); }
.text-green-700 { color: var(--color-green-700); }
.text-green-800 { color: var(--color-green-800); }
.text-indigo-600 { color: var(--color-indigo-600); }
.text-neutral-300 { color: var(--color-neutral-300); }
.text-neutral-400 { color: var(--color-neutral-400); }
.text-neutral-500 { color: var(--color-neutral-500); }
.text-neutral-600 { color: var(--color-neutral-600); }
.text-neutral-700 { color: var(--color-neutral-700); }
.text-neutral-900 { color: var(--color-neutral-900); }
.text-orange-500 { color: var(--color-orange-500); }
.text-orange-600 { color: var(--color-orange-600); }
.text-orange-700 { color: var(--color-orange-700); }
.text-pink-600 { color: var(--color-pink-600); }
.text-purple-600 { color: var(--color-purple-600); }
.text-red-400 { color: var(--color-red-400); }
.text-red-500 { color: var(--color-red-500); }
.text-red-500\/90 {
color: color-mix(in srgb, oklch(0.637 0.237 25.331) 90%, transparent 10%);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-red-500) 90%, transparent);
}
}
.text-red-600 { color: var(--color-red-600); }
.text-red-700 { color: var(--color-red-700); }
.text-slate-300 { color: var(--color-slate-300); }
.text-slate-400 { color: var(--color-slate-400); }
.text-slate-500 { color: var(--color-slate-500); }
.text-slate-600 { color: var(--color-slate-600); }
.text-slate-900 { color: var(--color-slate-900); }
.text-white { color: var(--color-white); }
.text-white\/60 {
color: color-mix(in srgb, rgb(255, 255, 255) 60%, transparent 40%);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-white) 60%, transparent);
}
}
.text-white\/80 {
color: color-mix(in srgb, rgb(255, 255, 255) 80%, transparent 20%);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-white) 80%, transparent);
}
}
.text-white\/90 {
color: color-mix(in srgb, rgb(255, 255, 255) 90%, transparent 10%);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-white) 90%, transparent);
}
}
.text-yellow-400 { color: var(--color-yellow-400); }
.text-yellow-600 { color: var(--color-yellow-600); }
.capitalize { text-transform: capitalize; }
.uppercase { text-transform: uppercase; }
.ordinal { --tw-ordinal: ordinal; font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,); }
.line-through { text-decoration-line: line-through; }
.underline { text-decoration-line: underline; }
.underline-offset-2 { text-underline-offset: 2px; }
.antialiased { -webkit-font-smoothing: antialiased; }
.placeholder-gray-400::placeholder { color: var(--color-gray-400); }
.accent-amber-500 { accent-color: var(--color-amber-500); }
.accent-gray-900 { accent-color: var(--color-gray-900); }
.opacity-0 { opacity: 0; }
.opacity-10 { opacity: 0.1; }
.opacity-50 { opacity: 0.5; }
.opacity-60 { opacity: 0.6; }
.opacity-100 { opacity: 1; }
.shadow { --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-2xl { --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-\[0_0_0_1\.5px_rgba\(0\,0\,0\,0\.35\)\] { --tw-shadow: 0 0 0 1.5px var(--tw-shadow-color, rgba(0,0,0,0.35)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-\[0_0_20px_rgba\(0\,0\,0\,0\.2\)\] { --tw-shadow: 0 0 20px var(--tw-shadow-color, rgba(0,0,0,0.2)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-\[0_0_20px_rgba\(0\,0\,0\,0\.05\)\] { --tw-shadow: 0 0 20px var(--tw-shadow-color, rgba(0,0,0,0.05)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-\[0_4px_20px_-2px_rgba\(0\,0\,0\,0\.05\)\,0_2px_10px_-4px_rgba\(0\,0\,0\,0\.03\)\] { --tw-shadow: 0 4px 20px -2px var(--tw-shadow-color, rgba(0,0,0,0.05)), 0 2px 10px -4px var(--tw-shadow-color, rgba(0,0,0,0.03)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-\[0_8px_30px_rgb\(0\,0\,0\,0\.04\)\] { --tw-shadow: 0 8px 30px var(--tw-shadow-color, rgb(0,0,0,0.04)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-lg { --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-md { --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-sm { --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-xl { --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.ring-1 { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.ring-2 { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.ring-4 { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.shadow-black\/5 {
--tw-shadow-color: color-mix(in srgb, #000 5%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-black) 5%, transparent) var(--tw-shadow-alpha), transparent);
}
}
.shadow-black\/20 {
--tw-shadow-color: color-mix(in srgb, #000 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-black) 20%, transparent) var(--tw-shadow-alpha), transparent);
}
}
.shadow-gray-500\/10 {
--tw-shadow-color: color-mix(in srgb, oklch(55.1% 0.027 264.364) 10%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-gray-500) 10%, transparent) var(--tw-shadow-alpha), transparent);
}
}
.ring-gray-100 { --tw-ring-color: var(--color-gray-100); }
.ring-transparent { --tw-ring-color: transparent; }
.ring-white { --tw-ring-color: var(--color-white); }
.ring-white\/40 {
--tw-ring-color: color-mix(in srgb, #fff 40%, transparent);
@supports (color: color-mix(in lab, red, red)) {
--tw-ring-color: color-mix(in oklab, var(--color-white) 40%, transparent);
}
}
.blur-3xl { --tw-blur: blur(var(--blur-3xl)); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); }
.drop-shadow-\[0_1px_2px_rgba\(255\,255\,255\,0\.5\)\] { --tw-drop-shadow-size: drop-shadow(0 1px 2px var(--tw-drop-shadow-color, rgba(255,255,255,0.5))); --tw-drop-shadow: var(--tw-drop-shadow-size); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); }
.drop-shadow-\[0_2px_4px_rgba\(0\,0\,0\,0\.3\)\] { --tw-drop-shadow-size: drop-shadow(0 2px 4px var(--tw-drop-shadow-color, rgba(0,0,0,0.3))); --tw-drop-shadow: var(--tw-drop-shadow-size); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); }
.grayscale { --tw-grayscale: grayscale(100%); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); }
.backdrop-blur-\[2px\] { --tw-backdrop-blur: blur(2px); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); }
.backdrop-blur-\[4px\] { --tw-backdrop-blur: blur(4px); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); }
.backdrop-blur-md { --tw-backdrop-blur: blur(var(--blur-md)); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); }
.backdrop-blur-sm { --tw-backdrop-blur: blur(var(--blur-sm)); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); }
.transition { transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); }
.transition-\[width\] { transition-property: width; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); }
.transition-all { transition-property: all; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); }
.transition-colors { transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); }
.transition-opacity { transition-property: opacity; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); }
.transition-shadow { transition-property: box-shadow; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); }
.transition-transform { transition-property: transform, translate, scale, rotate; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); }
.duration-150 { --tw-duration: 150ms; transition-duration: 150ms; }
.duration-200 { --tw-duration: 200ms; transition-duration: 200ms; }
.duration-300 { --tw-duration: 300ms; transition-duration: 300ms; }
.duration-500 { --tw-duration: 500ms; transition-duration: 500ms; }
.ease-in { --tw-ease: var(--ease-in); transition-timing-function: var(--ease-in); }
.ease-in-out { --tw-ease: var(--ease-in-out); transition-timing-function: var(--ease-in-out); }
.ease-out { --tw-ease: var(--ease-out); transition-timing-function: var(--ease-out); }
.outline-none { --tw-outline-style: none; outline-style: none; }
.select-none { user-select: none; }
@media (hover: hover) {
.group-hover\:scale-105:is(:where(.group):hover *) { --tw-scale-x: 105%; --tw-scale-y: 105%; --tw-scale-z: 105%; scale: var(--tw-scale-x) var(--tw-scale-y); }
.group-hover\:scale-110:is(:where(.group):hover *) { --tw-scale-x: 110%; --tw-scale-y: 110%; --tw-scale-z: 110%; scale: var(--tw-scale-x) var(--tw-scale-y); }
.group-hover\:border-gray-300:is(:where(.group):hover *) { border-color: var(--color-gray-300); }
.group-hover\:text-black:is(:where(.group):hover *) { color: var(--color-black); }
.group-hover\:opacity-100:is(:where(.group):hover *) { opacity: 1; }
.group-hover\:shadow-lg:is(:where(.group):hover *) { --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.group-hover\:ring-gray-100:is(:where(.group):hover *) { --tw-ring-color: var(--color-gray-100); }
.group-hover\/qr\:opacity-100:is(:where(.group\/qr):hover *) { opacity: 1; }
}
.peer-checked\:translate-x-4:is(:where(.peer):checked ~ *) { --tw-translate-x: calc(var(--spacing) * 4); translate: var(--tw-translate-x) var(--tw-translate-y); }
.peer-checked\:bg-gray-900:is(:where(.peer):checked ~ *) { background-color: var(--color-gray-900); }
.placeholder\:text-gray-300::placeholder { color: var(--color-gray-300); }
.placeholder\:text-neutral-400::placeholder { color: var(--color-neutral-400); }
@media (hover: hover) {
.hover\:-translate-y-1:hover { --tw-translate-y: calc(var(--spacing) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); }
.hover\:scale-105:hover { --tw-scale-x: 105%; --tw-scale-y: 105%; --tw-scale-z: 105%; scale: var(--tw-scale-x) var(--tw-scale-y); }
.hover\:scale-110:hover { --tw-scale-x: 110%; --tw-scale-y: 110%; --tw-scale-z: 110%; scale: var(--tw-scale-x) var(--tw-scale-y); }
.hover\:border-black:hover { border-color: var(--color-black); }
.hover\:border-gray-300:hover { border-color: var(--color-gray-300); }
.hover\:border-gray-400:hover { border-color: var(--color-gray-400); }
.hover\:border-gray-900:hover { border-color: var(--color-gray-900); }
.hover\:border-red-300:hover { border-color: var(--color-red-300); }
.hover\:bg-\[\#F0F0F0\]:hover { background-color: rgb(240, 240, 240); }
.hover\:bg-\[\#f5f5f5\]:hover { background-color: rgb(245, 245, 245); }
.hover\:bg-\[\#f9f9f9\]:hover { background-color: rgb(249, 249, 249); }
.hover\:bg-amber-50:hover { background-color: var(--color-amber-50); }
.hover\:bg-amber-100:hover { background-color: var(--color-amber-100); }
.hover\:bg-amber-600:hover { background-color: var(--color-amber-600); }
.hover\:bg-black:hover { background-color: var(--color-black); }
.hover\:bg-black\/5:hover { background-color: color-mix(in srgb, rgb(0, 0, 0) 5%, transparent 95%); }
@supports (color: color-mix(in lab, red, red)) {
.hover\:bg-black\/5:hover { background-color: color-mix(in oklab, var(--color-black) 5%, transparent); }
}
.hover\:bg-gray-50:hover { background-color: var(--color-gray-50); }
.hover\:bg-gray-100:hover { background-color: var(--color-gray-100); }
.hover\:bg-gray-200:hover { background-color: var(--color-gray-200); }
.hover\:bg-gray-600:hover { background-color: var(--color-gray-600); }
.hover\:bg-gray-800:hover { background-color: var(--color-gray-800); }
.hover\:bg-neutral-100:hover { background-color: var(--color-neutral-100); }
.hover\:bg-neutral-200:hover { background-color: var(--color-neutral-200); }
.hover\:bg-neutral-700:hover { background-color: var(--color-neutral-700); }
.hover\:bg-neutral-800:hover { background-color: var(--color-neutral-800); }
.hover\:bg-red-50:hover { background-color: var(--color-red-50); }
.hover\:bg-red-600:hover { background-color: var(--color-red-600); }
.hover\:bg-white\/20:hover { background-color: color-mix(in srgb, rgb(255, 255, 255) 20%, transparent 80%); }
@supports (color: color-mix(in lab, red, red)) {
.hover\:bg-white\/20:hover { background-color: color-mix(in oklab, var(--color-white) 20%, transparent); }
}
.hover\:text-amber-900:hover { color: var(--color-amber-900); }
.hover\:text-black:hover { color: var(--color-black); }
.hover\:text-gray-600:hover { color: var(--color-gray-600); }
.hover\:text-gray-700:hover { color: var(--color-gray-700); }
.hover\:text-gray-800:hover { color: var(--color-gray-800); }
.hover\:text-gray-900:hover { color: var(--color-gray-900); }
.hover\:text-neutral-600:hover { color: var(--color-neutral-600); }
.hover\:text-neutral-900:hover { color: var(--color-neutral-900); }
.hover\:text-red-600:hover { color: var(--color-red-600); }
.hover\:text-slate-900:hover { color: var(--color-slate-900); }
.hover\:text-white:hover { color: var(--color-white); }
.hover\:underline:hover { text-decoration-line: underline; }
.hover\:opacity-80:hover { opacity: 0.8; }
.hover\:opacity-90:hover { opacity: 0.9; }
.hover\:shadow-\[0_0_30px_rgba\(0\,0\,0\,0\.3\)\]:hover { --tw-shadow: 0 0 30px var(--tw-shadow-color, rgba(0,0,0,0.3)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.hover\:shadow-lg:hover { --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.hover\:shadow-md:hover { --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
}
.focus\:border-amber-400:focus { border-color: var(--color-amber-400); }
.focus\:border-black:focus { border-color: var(--color-black); }
.focus\:border-gray-900:focus { border-color: var(--color-gray-900); }
.focus\:bg-white:focus { background-color: var(--color-white); }
.focus\:ring-0:focus { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.focus\:ring-1:focus { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
.focus\:ring-gray-900:focus { --tw-ring-color: var(--color-gray-900); }
.focus\:outline-none:focus { --tw-outline-style: none; outline-style: none; }
.active\:scale-90:active { --tw-scale-x: 90%; --tw-scale-y: 90%; --tw-scale-z: 90%; scale: var(--tw-scale-x) var(--tw-scale-y); }
.active\:scale-95:active { --tw-scale-x: 95%; --tw-scale-y: 95%; --tw-scale-z: 95%; scale: var(--tw-scale-x) var(--tw-scale-y); }
.active\:scale-\[0\.98\]:active { scale: 0.98; }
.active\:bg-gray-200:active { background-color: var(--color-gray-200); }
.active\:text-gray-700:active { color: var(--color-gray-700); }
.disabled\:cursor-not-allowed:disabled { cursor: not-allowed; }
.disabled\:bg-gray-300:disabled { background-color: var(--color-gray-300); }
.disabled\:opacity-40:disabled { opacity: 0.4; }
.disabled\:opacity-50:disabled { opacity: 0.5; }
.disabled\:opacity-60:disabled { opacity: 0.6; }
.disabled\:shadow-none:disabled { --tw-shadow: 0 0 #0000; box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
@media (hover: hover) {
.disabled\:hover\:bg-gray-300:disabled:hover { background-color: var(--color-gray-300); }
}
@media (width < 420px) {
.max-\[420px\]\:grid-cols-1 { grid-template-columns: repeat(1, minmax(0px, 1fr)); }
}
@media (width >= 40rem) {
.sm\:col-span-2 { grid-column: span 2 / span 2; }
.sm\:my-8 { margin-block: calc(var(--spacing) * 8); }
.sm\:flex { display: flex; }
.sm\:hidden { display: none; }
.sm\:inline { display: inline; }
.sm\:inline-flex { display: inline-flex; }
.sm\:table-cell { display: table-cell; }
.sm\:h-8 { height: calc(var(--spacing) * 8); }
.sm\:h-10 { height: calc(var(--spacing) * 10); }
.sm\:h-\[88vh\] { height: 88vh; }
.sm\:w-8 { width: calc(var(--spacing) * 8); }
.sm\:w-10 { width: calc(var(--spacing) * 10); }
.sm\:w-\[min\(1080px\,96vw\)\] { width: min(1080px, 96vw); }
.sm\:columns-2 { columns: 2; }
.sm\:columns-3 { columns: 3; }
.sm\:columns-4 { columns: 4; }
.sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0px, 1fr)); }
.sm\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0px, 1fr)); }
.sm\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0px, 1fr)); }
.sm\:flex-row { flex-direction: row; }
.sm\:items-center { align-items: center; }
.sm\:gap-3 { gap: calc(var(--spacing) * 3); }
.sm\:gap-8 { gap: calc(var(--spacing) * 8); }
.sm\:rounded-2xl { border-radius: var(--radius-2xl); }
.sm\:p-4 { padding: calc(var(--spacing) * 4); }
.sm\:p-8 { padding: calc(var(--spacing) * 8); }
.sm\:px-5 { padding-inline: calc(var(--spacing) * 5); }
.sm\:px-6 { padding-inline: calc(var(--spacing) * 6); }
.sm\:px-8 { padding-inline: calc(var(--spacing) * 8); }
.sm\:px-10 { padding-inline: calc(var(--spacing) * 10); }
.sm\:py-8 { padding-block: calc(var(--spacing) * 8); }
.sm\:py-12 { padding-block: calc(var(--spacing) * 12); }
.sm\:pt-0 { padding-top: 0px; }
.sm\:pb-10 { padding-bottom: calc(var(--spacing) * 10); }
.sm\:pb-20 { padding-bottom: calc(var(--spacing) * 20); }
.sm\:text-left { text-align: left; }
.sm\:text-2xl { font-size: var(--text-2xl); line-height: var(--tw-leading, var(--text-2xl--line-height)); }
.sm\:text-4xl { font-size: var(--text-4xl); line-height: var(--tw-leading, var(--text-4xl--line-height)); }
.sm\:text-base { font-size: var(--text-base); line-height: var(--tw-leading, var(--text-base--line-height)); }
.sm\:text-lg { font-size: var(--text-lg); line-height: var(--tw-leading, var(--text-lg--line-height)); }
.sm\:text-sm { font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); }
.sm\:text-xs { font-size: var(--text-xs); line-height: var(--tw-leading, var(--text-xs--line-height)); }
.sm\:text-\[11px\] { font-size: 11px; }
.sm\:shadow-2xl { --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); }
}
@media (width >= 48rem) {
.md\:block { display: block; }
.md\:flex { display: flex; }
.md\:hidden { display: none; }
.md\:columns-3 { columns: 3; }
.md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0px, 1fr)); }
.md\:flex-row { flex-direction: row; }
.md\:items-center { align-items: center; }
.md\:p-12 { padding: calc(var(--spacing) * 12); }
.md\:pt-0 { padding-top: 0px; }
.md\:text-3xl { font-size: var(--text-3xl); line-height: var(--tw-leading, var(--text-3xl--line-height)); }
}
@media (width >= 64rem) {
.lg\:min-h-0 { min-height: 0px; }
.lg\:w-\[340px\] { width: 340px; }
.lg\:columns-3 { columns: 3; }
.lg\:columns-4 { columns: 4; }
.lg\:columns-5 { columns: 5; }
.lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0px, 1fr)); }
.lg\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0px, 1fr)); }
.lg\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0px, 1fr)); }
.lg\:flex-row { flex-direction: row; }
.lg\:border-t-0 { border-top-style: var(--tw-border-style); border-top-width: 0px; }
.lg\:border-l { border-left-style: var(--tw-border-style); border-left-width: 1px; }
.lg\:p-12 { padding: calc(var(--spacing) * 12); }
.lg\:px-8 { padding-inline: calc(var(--spacing) * 8); }
}
@media (width >= 80rem) {
.xl\:columns-4 { columns: 4; }
.xl\:columns-5 { columns: 5; }
.xl\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0px, 1fr)); }
}
@media (width >= 96rem) {
.\32 xl\:columns-6 { columns: 6; }
.\32 xl\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0px, 1fr)); }
}
}
@layer base {
html { -webkit-font-smoothing: antialiased; text-rendering: optimizelegibility; }
body { font-family: var(--font-display); }
}
@property --tw-translate-x { syntax: "*"; inherits: false; initial-value: 0; }
@property --tw-translate-y { syntax: "*"; inherits: false; initial-value: 0; }
@property --tw-translate-z { syntax: "*"; inherits: false; initial-value: 0; }
@property --tw-rotate-x { syntax: "*"; inherits: false; }
@property --tw-rotate-y { syntax: "*"; inherits: false; }
@property --tw-rotate-z { syntax: "*"; inherits: false; }
@property --tw-skew-x { syntax: "*"; inherits: false; }
@property --tw-skew-y { syntax: "*"; inherits: false; }
@property --tw-space-y-reverse { syntax: "*"; inherits: false; initial-value: 0; }
@property --tw-space-x-reverse { syntax: "*"; inherits: false; initial-value: 0; }
@property --tw-divide-y-reverse { syntax: "*"; inherits: false; initial-value: 0; }
@property --tw-border-style { syntax: "*"; inherits: false; initial-value: solid; }
@property --tw-gradient-position { syntax: "*"; inherits: false; }
@property --tw-gradient-from { syntax: "<color>"; inherits: false; initial-value: #0000; }
@property --tw-gradient-via { syntax: "<color>"; inherits: false; initial-value: #0000; }
@property --tw-gradient-to { syntax: "<color>"; inherits: false; initial-value: #0000; }
@property --tw-gradient-stops { syntax: "*"; inherits: false; }
@property --tw-gradient-via-stops { syntax: "*"; inherits: false; }
@property --tw-gradient-from-position { syntax: "<length-percentage>"; inherits: false; initial-value: 0%; }
@property --tw-gradient-via-position { syntax: "<length-percentage>"; inherits: false; initial-value: 50%; }
@property --tw-gradient-to-position { syntax: "<length-percentage>"; inherits: false; initial-value: 100%; }
@property --tw-leading { syntax: "*"; inherits: false; }
@property --tw-font-weight { syntax: "*"; inherits: false; }
@property --tw-tracking { syntax: "*"; inherits: false; }
@property --tw-ordinal { syntax: "*"; inherits: false; }
@property --tw-slashed-zero { syntax: "*"; inherits: false; }
@property --tw-numeric-figure { syntax: "*"; inherits: false; }
@property --tw-numeric-spacing { syntax: "*"; inherits: false; }
@property --tw-numeric-fraction { syntax: "*"; inherits: false; }
@property --tw-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; }
@property --tw-shadow-color { syntax: "*"; inherits: false; }
@property --tw-shadow-alpha { syntax: "<percentage>"; inherits: false; initial-value: 100%; }
@property --tw-inset-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; }
@property --tw-inset-shadow-color { syntax: "*"; inherits: false; }
@property --tw-inset-shadow-alpha { syntax: "<percentage>"; inherits: false; initial-value: 100%; }
@property --tw-ring-color { syntax: "*"; inherits: false; }
@property --tw-ring-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; }
@property --tw-inset-ring-color { syntax: "*"; inherits: false; }
@property --tw-inset-ring-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; }
@property --tw-ring-inset { syntax: "*"; inherits: false; }
@property --tw-ring-offset-width { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --tw-ring-offset-color { syntax: "*"; inherits: false; initial-value: #fff; }
@property --tw-ring-offset-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; }
@property --tw-blur { syntax: "*"; inherits: false; }
@property --tw-brightness { syntax: "*"; inherits: false; }
@property --tw-contrast { syntax: "*"; inherits: false; }
@property --tw-grayscale { syntax: "*"; inherits: false; }
@property --tw-hue-rotate { syntax: "*"; inherits: false; }
@property --tw-invert { syntax: "*"; inherits: false; }
@property --tw-opacity { syntax: "*"; inherits: false; }
@property --tw-saturate { syntax: "*"; inherits: false; }
@property --tw-sepia { syntax: "*"; inherits: false; }
@property --tw-drop-shadow { syntax: "*"; inherits: false; }
@property --tw-drop-shadow-color { syntax: "*"; inherits: false; }
@property --tw-drop-shadow-alpha { syntax: "<percentage>"; inherits: false; initial-value: 100%; }
@property --tw-drop-shadow-size { syntax: "*"; inherits: false; }
@property --tw-backdrop-blur { syntax: "*"; inherits: false; }
@property --tw-backdrop-brightness { syntax: "*"; inherits: false; }
@property --tw-backdrop-contrast { syntax: "*"; inherits: false; }
@property --tw-backdrop-grayscale { syntax: "*"; inherits: false; }
@property --tw-backdrop-hue-rotate { syntax: "*"; inherits: false; }
@property --tw-backdrop-invert { syntax: "*"; inherits: false; }
@property --tw-backdrop-opacity { syntax: "*"; inherits: false; }
@property --tw-backdrop-saturate { syntax: "*"; inherits: false; }
@property --tw-backdrop-sepia { syntax: "*"; inherits: false; }
@property --tw-duration { syntax: "*"; inherits: false; }
@property --tw-ease { syntax: "*"; inherits: false; }
@property --tw-scale-x { syntax: "*"; inherits: false; initial-value: 1; }
@property --tw-scale-y { syntax: "*"; inherits: false; initial-value: 1; }
@property --tw-scale-z { syntax: "*"; inherits: false; initial-value: 1; }
@keyframes spin {
100% { transform: rotate(360deg); }
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop { --tw-translate-x: 0; --tw-translate-y: 0; --tw-translate-z: 0; --tw-rotate-x: initial; --tw-rotate-y: initial; --tw-rotate-z: initial; --tw-skew-x: initial; --tw-skew-y: initial; --tw-space-y-reverse: 0; --tw-space-x-reverse: 0; --tw-divide-y-reverse: 0; --tw-border-style: solid; --tw-gradient-position: initial; --tw-gradient-from: #0000; --tw-gradient-via: #0000; --tw-gradient-to: #0000; --tw-gradient-stops: initial; --tw-gradient-via-stops: initial; --tw-gradient-from-position: 0%; --tw-gradient-via-position: 50%; --tw-gradient-to-position: 100%; --tw-leading: initial; --tw-font-weight: initial; --tw-tracking: initial; --tw-ordinal: initial; --tw-slashed-zero: initial; --tw-numeric-figure: initial; --tw-numeric-spacing: initial; --tw-numeric-fraction: initial; --tw-shadow: 0 0 #0000; --tw-shadow-color: initial; --tw-shadow-alpha: 100%; --tw-inset-shadow: 0 0 #0000; --tw-inset-shadow-color: initial; --tw-inset-shadow-alpha: 100%; --tw-ring-color: initial; --tw-ring-shadow: 0 0 #0000; --tw-inset-ring-color: initial; --tw-inset-ring-shadow: 0 0 #0000; --tw-ring-inset: initial; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; --tw-blur: initial; --tw-brightness: initial; --tw-contrast: initial; --tw-grayscale: initial; --tw-hue-rotate: initial; --tw-invert: initial; --tw-opacity: initial; --tw-saturate: initial; --tw-sepia: initial; --tw-drop-shadow: initial; --tw-drop-shadow-color: initial; --tw-drop-shadow-alpha: 100%; --tw-drop-shadow-size: initial; --tw-backdrop-blur: initial; --tw-backdrop-brightness: initial; --tw-backdrop-contrast: initial; --tw-backdrop-grayscale: initial; --tw-backdrop-hue-rotate: initial; --tw-backdrop-invert: initial; --tw-backdrop-opacity: initial; --tw-backdrop-saturate: initial; --tw-backdrop-sepia: initial; --tw-duration: initial; --tw-ease: initial; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scale-z: 1; }
}
}
.mobile-sidebar-enter-active[data-v-56bfec04] { transition: 0.3s; }
.mobile-sidebar-leave-active[data-v-56bfec04] { transition: 0.3s; }
.mobile-sidebar-enter-from[data-v-56bfec04], .mobile-sidebar-leave-to[data-v-56bfec04] { opacity: 0; }
.mobile-sidebar-enter-from aside[data-v-56bfec04], .mobile-sidebar-leave-to aside[data-v-56bfec04] { transform: translateX(-100%); }
.mobile-sidebar-enter-to aside[data-v-56bfec04], .mobile-sidebar-leave-from aside[data-v-56bfec04] { transform: translateX(0px); }
.generation-md > :first-child { margin-top: 0px; }
.generation-md h1, .generation-md h2, .generation-md h3, .generation-md h4 { margin: 0.7em 0px 0.35em; color: rgb(17, 24, 39); line-height: 1.3; }
.generation-md h1 { font-size: 1.15em; font-weight: 700; }
.generation-md h2 { font-size: 1.05em; font-weight: 700; }
.generation-md h3, .generation-md h4 { font-size: 1em; font-weight: 700; }
.generation-md p { margin: 0.35em 0px; }
.generation-md ul, .generation-md ol { margin: 0.35em 0px; padding-left: 1.4em; }
.generation-md ul { list-style: disc; }
.generation-md ol { list-style: decimal; }
.generation-md li { margin: 0.15em 0px; }
.generation-md li > p { margin: 0.15em 0px; }
.generation-md strong { font-weight: 700; color: rgb(17, 24, 39); }
.generation-md em { font-style: italic; }
.generation-md del { text-decoration: line-through; color: rgb(156, 163, 175); }
.generation-md a { color: rgb(37, 99, 235); text-decoration: underline; }
.generation-md code { background: rgb(243, 244, 246); padding: 0.1em 0.35em; border-radius: 4px; font-size: 0.88em; }
.generation-md pre { background: rgb(246, 248, 246); border: 1px solid rgb(229, 231, 235); border-radius: 8px; padding: 0.8em 1em; overflow-x: auto; margin: 0.5em 0px; }
.generation-md pre code { background: none; padding: 0px; }
.generation-md blockquote { margin: 0.5em 0px; padding: 0.3em 0.9em; border-left: 3px solid rgb(209, 213, 219); color: rgb(107, 114, 128); }
.generation-md table { border-collapse: collapse; margin: 0.5em 0px; }
.generation-md th, .generation-md td { border: 1px solid rgb(229, 231, 235); padding: 0.35em 0.7em; text-align: left; }
.generation-md th { background: rgb(243, 244, 246); font-weight: 700; }
.generation-md hr { border-width: 1px medium medium; border-style: solid none none; border-color: rgb(229, 231, 235) currentcolor currentcolor; border-image: none; margin: 0.8em 0px; }
.generation-md img { max-width: 100%; border-radius: 8px; }
.lightbox-enter-active[data-v-188a4043], .lightbox-leave-active[data-v-188a4043] { transition: opacity 0.3s; }
.lightbox-enter-from[data-v-188a4043], .lightbox-leave-to[data-v-188a4043] { opacity: 0; }
.qr-tool-enter-active[data-v-10c9a45d], .qr-tool-leave-active[data-v-10c9a45d] { transition: opacity 0.2s; }
.qr-tool-enter-from[data-v-10c9a45d], .qr-tool-leave-to[data-v-10c9a45d] { opacity: 0; }
.bind-mobile-enter-active[data-v-aceadc84], .bind-mobile-leave-active[data-v-aceadc84] { transition: opacity 0.2s; }
.bind-mobile-enter-from[data-v-aceadc84], .bind-mobile-leave-to[data-v-aceadc84] { opacity: 0; }
.price-transition[data-v-9c530f66] { transition: 0.3s; }
.table-th[data-v-a82c8120] { padding-top: 1rem; padding-bottom: 1rem; font-size: 0.875rem; font-weight: 600; color: rgb(156, 163, 175); text-transform: uppercase; letter-spacing: 0.05em; text-align: left; border-bottom: 1px solid rgb(243, 244, 246); background-color: rgb(255, 255, 255); }
@media (min-width: 640px) {
.table-th[data-v-a82c8120] { padding-top: 1.5rem; padding-bottom: 1.5rem; }
}
.table-td[data-v-a82c8120] { padding-top: 1rem; padding-bottom: 1rem; font-size: 1rem; color: rgb(75, 85, 99); border-bottom: 1px solid rgb(249, 250, 251); }
@media (min-width: 640px) {
.table-td[data-v-a82c8120] { padding-top: 1.5rem; padding-bottom: 1.5rem; }
}
.msg-enter-active[data-v-f3be6059] { transition: 0.3s ease-out; }
.msg-enter-from[data-v-f3be6059] { opacity: 0; transform: translateY(12px); }
main.is-empty #composerShell { position: static; }
#composerShell { flex: none; }
/* 生成结果 markdown 排版(对齐 tripcn.ai-web:结构化内容用白色卡片包裹) */
.generation-md { display: flex; flex-direction: column; gap: 12px; }
.generation-md > * { margin: 0 !important; }
.generation-md > p {
font-size: 14px;
line-height: 1.7;
color: #4b5563;
}
.generation-md > h1,
.generation-md > h2 {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 12px 16px;
font-size: 18px;
font-weight: 700;
line-height: 1.35;
color: #111827;
}
.generation-md > h3,
.generation-md > h4,
.generation-md > h5,
.generation-md > h6 {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 12px 16px;
font-size: 14px;
font-weight: 600;
line-height: 1.45;
color: #1f2937;
}
.generation-md > ul,
.generation-md > ol {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 12px 16px 12px 32px;
font-size: 14px;
line-height: 1.7;
color: #4b5563;
}
.generation-md > ul { list-style: disc; }
.generation-md > ol { list-style: decimal; }
.generation-md li { margin: 2px 0; }
.generation-md li > p {
margin: 2px 0 !important;
background: none;
border: 0;
padding: 0;
font-size: inherit;
color: inherit;
}
.generation-md > table {
width: 100%;
border: 1px solid #e5e7eb;
border-radius: 12px;
border-collapse: separate;
border-spacing: 0;
overflow: hidden;
background: #fff;
font-size: 13px;
}
.generation-md > table th,
.generation-md > table td {
border: 0;
border-bottom: 1px solid #f3f4f6;
padding: 10px 16px;
text-align: left;
vertical-align: top;
}
.generation-md > table tr:last-child td { border-bottom: 0; }
.generation-md > table th { background: #f9fafb; font-weight: 600; color: #111827; }
.generation-md > table td { color: #4b5563; }
.generation-md > blockquote {
border-left: 4px solid #fcd34d;
background: #fffbeb;
border-radius: 0 10px 10px 0;
padding: 10px 16px;
font-size: 14px;
line-height: 1.65;
color: #374151;
}
.generation-md > pre {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 12px 16px;
overflow-x: auto;
font-size: 12px;
}
.generation-md > pre code { background: none; padding: 0; }
.generation-md > hr { border: 0; border-top: 1px solid #e5e7eb; }
.generation-md strong { font-weight: 700; color: #111827; }
.generation-md em { font-style: italic; }
.generation-md del { text-decoration: line-through; color: #9ca3af; }
.generation-md a { color: #2563eb; text-decoration: underline; }
.generation-md code { background: #f3f4f6; padding: 0.1em 0.35em; border-radius: 4px; font-size: 0.88em; }
.generation-md img {
display: block;
width: 100%;
max-width: 520px;
margin: 2px 0;
border: 1px solid #e5e7eb;
border-radius: 12px;
background: #fff;
cursor: zoom-in;
transition: opacity .15s ease;
}
.generation-md img:hover { opacity: .92; }
.generation-md > p:has(> img:only-child) {
background: none;
border: 0;
padding: 0;
}
/* Industry & skill selector */
.skill-wrap { position: relative; display: flex; align-items: center; }
.skill-btn { display: inline-flex; align-items: center; gap: 5px; max-width: 240px; height: 32px; padding: 0 10px 0 8px; border: 1px solid transparent; border-radius: 9px; color: #4b5563; font-size: 12px; font-weight: 500; background: transparent; white-space: nowrap; cursor: pointer; transition: background-color .15s, color .15s, border-color .15s; }
.skill-btn:hover { background: #f5f5f7; color: #111827; }
.skill-btn[aria-expanded="true"] { background: #f3f6ef; border-color: #e3e9db; color: #08482d; }
.skill-btn .material-symbols-outlined { flex: none; font-size: 16px; color: #6b7280; }
.skill-btn[aria-expanded="true"] .material-symbols-outlined { color: #08482d; }
.skill-btn > span:nth-child(2) { overflow: hidden; text-overflow: ellipsis; }
.skill-btn > span:last-child { transition: transform .18s ease; }
.skill-btn[aria-expanded="true"] > span:last-child { transform: rotate(180deg); }
.skill-menu {
position: fixed;
left: 16px;
bottom: 220px;
z-index: 60;
display: flex;
flex-direction: column;
width: min(460px, calc(100vw - 24px));
max-height: min(540px, 74vh);
padding: 0;
overflow: hidden;
border: 1px solid rgba(8, 72, 45, 0.09);
border-radius: 20px;
background: #fff;
box-shadow: 0 28px 64px -28px rgba(16, 30, 20, 0.34), 0 10px 24px -16px rgba(16, 30, 20, 0.16);
transform-origin: bottom left;
animation: skillMenuPop 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}
.skill-menu[hidden] { display: none; }
@keyframes skillMenuPop {
from { opacity: 0; transform: translateY(6px) scale(0.985); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.skill-menu { animation: none; }
.skill-btn > span:last-child { transition: none; }
}
.skill-menu__bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px 4px; }
.skill-menu__title { color: #111827; font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }
.skill-menu__close { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: 0; border-radius: 999px; background: transparent; color: #9ca3af; cursor: pointer; transition: background-color .15s, color .15s; }
.skill-menu__close:hover { background: #f3f4f1; color: #111827; }
.skill-menu__close .material-symbols-outlined { font-size: 17px; }
.skill-menu__section { padding: 8px 16px 0; }
.skill-menu__section--list { display: flex; flex-direction: column; min-height: 0; padding-bottom: 8px; }
.skill-menu__head { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; color: #9aa294; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; }
.industry-tabs { display: flex; gap: 8px; padding: 1px 1px 6px; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.industry-tabs::-webkit-scrollbar { display: none; }
.industry-chip { flex: none; height: 30px; padding: 0 13px; border: 1px solid #e7e9e3; border-radius: 999px; background: #fff; color: #5c635a; font-size: 12.5px; line-height: 1.2; white-space: nowrap; cursor: pointer; transition: background-color .15s, border-color .15s, color .15s, box-shadow .15s; }
.industry-chip:hover { border-color: #cfd6c7; background: #fafbf7; color: #111827; }
.industry-chip.is-active { border-color: #08482D; background: #08482D; color: #fff; box-shadow: 0 8px 18px -10px rgba(8, 72, 45, 0.65); }
.skill-list-wrap { position: relative; display: flex; flex: 1; min-height: 0; }
.skill-list-wrap::after { content: ""; position: absolute; right: 0; bottom: 0; left: 0; height: 26px; background: linear-gradient(to top, #fff 15%, rgba(255, 255, 255, 0)); opacity: 0; pointer-events: none; transition: opacity .15s ease; }
.skill-list-wrap.is-scrollable::after { opacity: 1; }
#skillList { display: flex; flex: 1; flex-direction: column; gap: 2px; min-height: 0; margin: 0 -6px; padding: 0 6px 2px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: #d9ded2 transparent; }
#skillList::-webkit-scrollbar { width: 6px; }
#skillList::-webkit-scrollbar-thumb { border-radius: 999px; background: #d9ded2; }
#skillList::-webkit-scrollbar-track { background: transparent; }
.skill-option { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; border: 0; border-radius: 12px; background: transparent; color: #111827; text-align: left; cursor: pointer; transition: background-color .15s, box-shadow .15s; }
.skill-option:hover { background: #f6f8f4; }
.skill-option.is-active { background: #f1f6ec; box-shadow: inset 0 0 0 1px #dbe7cf; }
.skill-option__icon { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 28px; height: 28px; border-radius: 9px; background: #f3f6ef; color: #08482d; }
.skill-option__icon .material-symbols-outlined { font-size: 16px; }
.skill-option.is-active .skill-option__icon { background: #08482d; color: #fff; }
.skill-option__body { display: flex; flex: 1; flex-direction: column; gap: 2px; min-width: 0; }
.skill-option__name { overflow: hidden; color: #111827; font-size: 13px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.skill-option__desc { overflow: hidden; color: #98a094; font-size: 11.5px; line-height: 1.45; text-overflow: ellipsis; white-space: nowrap; }
.skill-option .skill-option__check { flex: none; font-size: 17px !important; color: #08482D; opacity: 0; transform: scale(0.8); transition: opacity .15s, transform .15s; }
.skill-option.is-active .skill-option__check { opacity: 1; transform: scale(1); }
.skill-menu__empty, .skill-menu__error { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 22px 12px; border-radius: 12px; background: #fafbf7; color: #9aa294; font-size: 12px; text-align: center; }
.skill-menu__empty .material-symbols-outlined { font-size: 22px; color: #c3c9bd; }
.skill-menu__error { background: #fef2f2; color: #b91c1c; }
.skill-menu__retry { margin-top: 2px; padding: 3px 10px; border: 1px solid #fecaca; border-radius: 999px; background: #fff; color: #b91c1c; font-size: 11.5px; cursor: pointer; }
.skill-menu__retry:hover { background: #fff1f2; }
.skill-menu__foot { flex: none; padding: 9px 16px; border-top: 1px solid #f1f2ee; background: #fcfdfa; color: #a3a89e; font-size: 11px; }
@media (max-width: 639px) {
.skill-btn { max-width: 180px; }
.skill-menu { max-height: min(420px, 62vh); }
#skillList { max-height: 240px; }
}
/* Tripcn-style upload modal */
#uploadModal[hidden], #uploadModal [hidden] { display: none !important; }
.upload-panel { display: none; }
.upload-panel.is-active { display: flex; }
.upload-tab { color: #64748b; }
.upload-tab:hover { color: #0f172a; }
.upload-tab.is-active { color: #000; }
.upload-tab-line { display: none; }
.upload-tab.is-active .upload-tab-line { display: block; }
#uploadDropZone.is-dragging { border-color: #000; background: #f3f4f6; }
#uploadConfirm { display: none; }
#uploadConfirm.is-visible { display: inline-flex; }
.upload-folder-tile { aspect-ratio: 4 / 3; }
</style>
</head>
<body class="material-symbols-outloaded">
<div data-v-f3be6059="" class="flex h-screen overflow-hidden bg-white text-[#1b1b1b] font-display antialiased"><!-- Desktop Sidebar --><aside data-v-56bfec04="" id="sidebar" class="hidden sm:flex bg-[#f9f9f9] border-r border-[#eeeeee] flex-col shrink-0 transition-all duration-300 ease-in-out w-64 overflow-hidden"><div data-v-56bfec04="" class="p-4 flex items-center justify-between mb-4"><div data-v-56bfec04="" id="logoHome" class="flex items-center gap-2.5 cursor-pointer hover:scale-105 transform duration-200 ml-2"><svg data-v-56bfec04="" class="w-6 h-6 shrink-0" viewBox="0 0 32 32" fill="none" aria-hidden="true"><path data-v-56bfec04="" d="M19 2 5 19h8l-2 11L27 12h-9l1-10Z" fill="#08482D"></path><path data-v-56bfec04="" d="M19 2 11 12" fill="none" stroke="#DAE7C7" stroke-width="1.4" stroke-linecap="round"></path></svg><span data-v-56bfec04="" class="font-bold text-lg tracking-tight text-gray-900">来秒AI</span></div><button data-v-56bfec04="" id="collapse" class="flex items-center justify-center mt-1 group w-7 h-7 rounded-lg hover:bg-[#F0F0F0] transition-colors duration-200 cursor-pointer shrink-0"><svg data-v-56bfec04="" xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" class="text-[#333333] w-5 h-5"><rect data-v-56bfec04="" width="15.167" height="11.833" x="2.417" y="4.083" stroke="currentColor" stroke-width="1.5" rx="0.917"></rect><path data-v-56bfec04="" stroke="currentColor" stroke-width="1.5" d="M6.667 4.167v11.666"></path></svg></button></div><nav data-v-56bfec04="" class="flex-1 px-4 overflow-y-auto space-y-8"><div data-v-56bfec04=""><h3 data-v-56bfec04="" class="px-4 text-[10px] font-bold uppercase tracking-widest mb-3 whitespace-nowrap text-gray-400"> 开启创作 </h3><div data-v-56bfec04="" class="space-y-1"><button data-v-56bfec04="" id="newChat" type="button" class="flex items-center gap-2.5 px-4 py-2.5 rounded-lg text-sm font-medium transition-all cursor-pointer hover:scale-105 transform duration-200 w-full bg-[#f5f5f5] text-black font-semibold"><span data-v-56bfec04="" class="material-symbols-outlined text-[20px] shrink-0">edit_square</span><span data-v-56bfec04="" id="newChatLabel">新对话</span></button></div></div><div data-v-56bfec04=""><h3 data-v-56bfec04="" class="px-4 text-[10px] font-bold uppercase tracking-widest mb-3 whitespace-nowrap text-gray-400"> 最近 </h3><!-- 收起态:只显示一个图标,点击展开侧边栏 --><!-- 展开态:按日期分组 --><div data-v-56bfec04="" id="recentGroups" class="space-y-2"></div><button data-v-56bfec04="" id="recentCollapsed" type="button" class="group hidden items-center justify-center w-full py-2.5 rounded-lg transition-all cursor-pointer hover:bg-[#f5f5f5] hover:scale-105 transform duration-200"><span data-v-56bfec04="" class="material-symbols-outlined text-[20px] shrink-0 text-gray-500 group-hover:text-black transition-colors">sms</span></button></div></nav><div data-v-56bfec04="" class="p-4 border-t border-[#eeeeee]"><div data-v-56bfec04="" class="flex items-center"><div data-v-56bfec04="" id="userRow" class="flex items-center gap-3 px-2 py-2 cursor-pointer rounded-lg transition-all flex-1 min-w-0 hover:bg-gray-50"><div data-v-56bfec04="" id="userAvatar" class="w-8 h-8 rounded-full bg-gray-100 overflow-hidden ring-1 ring-gray-100 flex items-center justify-center shrink-0"><span data-v-56bfec04="" class="material-symbols-outlined text-gray-400">person</span></div><div data-v-56bfec04="" id="userMeta" class="flex-1 min-w-0 flex items-center gap-2"><p data-v-56bfec04="" id="userNickname" class="text-xs font-semibold text-gray-900 truncate min-w-0 flex-1">来秒AI 创作者</p></div></div></div></div></aside><!-- Mobile Top Nav --><nav data-v-56bfec04="" class="sm:hidden fixed top-0 left-0 right-0 z-40 h-14 bg-white border-b border-[#eeeeee] flex items-center justify-between px-4"><div data-v-56bfec04="" class="flex items-center gap-3"><button data-v-56bfec04="" id="mobileMenu" type="button" class="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-gray-100 transition-colors cursor-pointer"><span data-v-56bfec04="" class="material-symbols-outlined text-gray-700 text-[22px]">menu</span></button><div data-v-56bfec04="" id="mobileLogo" class="flex items-center gap-2 cursor-pointer"><svg data-v-56bfec04="" class="w-6 h-6 shrink-0" viewBox="0 0 32 32" fill="none" aria-hidden="true"><path data-v-56bfec04="" d="M19 2 5 19h8l-2 11L27 12h-9l1-10Z" fill="#08482D"></path><path data-v-56bfec04="" d="M19 2 11 12" fill="none" stroke="#DAE7C7" stroke-width="1.4" stroke-linecap="round"></path></svg><span data-v-56bfec04="" class="font-bold text-base text-gray-900">来秒AI</span></div></div></nav><!-- Mobile Sidebar -->
<div id="mobileDrawer" class="hidden fixed inset-0 z-50 sm:hidden">
<div id="mobileBackdrop" class="absolute inset-0 bg-black/40 backdrop-blur-sm"></div>
<aside class="absolute left-0 top-0 h-full bg-[#f9f9f9] border-r border-[#eeeeee] flex flex-col shrink-0 w-64 shadow-2xl">
<div class="p-6 flex items-center justify-between mb-4 gap-2.5">
<div class="flex items-center gap-2.5">
<svg class="w-6 h-6 shrink-0" viewBox="0 0 32 32" fill="none" aria-hidden="true"><path d="M19 2 5 19h8l-2 11L27 12h-9l1-10Z" fill="#08482D"/><path d="M19 2 11 12" fill="none" stroke="#DAE7C7" stroke-width="1.4" stroke-linecap="round"/></svg>
<span class="font-bold text-lg tracking-tight text-gray-900">来秒AI</span>
</div>
<button id="mobileClose" type="button" class="text-gray-500 hover:text-gray-900 cursor-pointer hover:scale-110 transform duration-200"><span class="material-symbols-outlined text-xl">close</span></button>
</div>
<nav class="flex-1 px-4 space-y-8 overflow-y-auto">
<div>
<h3 class="px-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-3">开启创作</h3>
<div class="space-y-1">
<button id="mNewChat" type="button" class="flex items-center gap-2.5 px-4 py-2.5 rounded-lg text-sm font-medium transition-all cursor-pointer hover:scale-105 transform duration-200 w-full bg-[#f5f5f5] text-black font-semibold"><span class="material-symbols-outlined text-[20px] shrink-0">edit_square</span>新对话</button>
</div>
</div>
<div>
<h3 class="px-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-3">最近</h3>
<div class="space-y-2"></div>
</div>
</nav>
<div class="p-4 border-t border-[#eeeeee]">
<div id="mUserRow" class="flex items-center gap-3 px-2 py-2 cursor-pointer rounded-lg transition-all hover:bg-gray-50">
<div id="mUserAvatar" class="w-8 h-8 rounded-full bg-gray-100 overflow-hidden ring-1 ring-gray-100 flex items-center justify-center shrink-0"><span class="material-symbols-outlined text-gray-400">person</span></div>
<div class="flex-1 min-w-0 flex items-center gap-2">
<p id="mUserNickname" class="text-xs font-semibold text-gray-900 truncate min-w-0 flex-1">来秒AI 创作者</p>
</div>
</div>
</div>
</aside>
</div>
<!-- Main Content --><main data-v-f3be6059="" id="main" class="is-empty grow flex flex-col h-full bg-[#f5f5f7] relative pt-14 sm:pt-0 min-h-0 overflow-y-auto"><!-- Top Right Capsules --><div data-v-f3be6059="" class="fixed top-5 right-5 flex items-center space-x-1.5 z-10 hidden md:flex"><div data-v-f3be6059="" class="flex items-center"><button data-v-f3be6059="" id="scoreCapsule" type="button" title="积分明细" class="flex items-center gap-1.5 rounded-l-lg px-3 py-1.5 text-sm font-medium text-gray-500 hover:text-gray-900 hover:bg-black/5 transition-colors cursor-pointer"><span data-v-f3be6059="" class="material-symbols-outlined text-[18px]">bolt</span><span data-v-f3be6059="" id="headerScore">0</span></button><span data-v-f3be6059="" class="w-px h-4 bg-gray-300/60"></span><button data-v-f3be6059="" id="rechargeBtn" type="button" class="flex items-center rounded-r-lg px-3 py-1.5 text-sm font-medium text-gray-500 hover:text-gray-900 hover:bg-black/5 transition-colors cursor-pointer"><span data-v-f3be6059="">充值</span></button></div><a data-v-f3be6059="" href="/library" class="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium text-gray-500 hover:text-gray-900 hover:bg-black/5 transition-colors cursor-pointer"><span data-v-f3be6059="" class="material-symbols-outlined text-[18px]">folder</span><span data-v-f3be6059="">资产库</span></a></div><!-- Content --><div data-v-f3be6059="" id="chatScroll" class="grow min-h-0 py-6 flex flex-col overflow-y-auto"><div data-v-f3be6059="" id="content" class="w-full max-w-4xl mx-auto px-4 sm:px-8 flex flex-col grow"><section data-v-f3be6059="" id="empty" class="grow flex flex-col items-center justify-center"><p data-v-f3be6059="" class="text-3xl font-bold text-gray-800"> 就现在,把行业灵感变成作品 </p><p data-v-f3be6059="" class="mt-2 text-base text-gray-400"> 选择行业与技能,输入需求或上传产品图,来秒AI 一键成片 </p></section><section data-v-f3be6059="" id="thread" class="hidden mb-16 w-full"></section></div></div><!-- Floating Input Card --><div data-v-f3be6059="" id="composerShell" class="composer-shell w-full z-40 pb-4"><div data-v-f3be6059="" class="max-w-4xl mx-auto px-4 sm:px-8"><!-- 积分不足提示:在输入框外部上方弹出 --><!--v-if--><div data-v-f3be6059="" class="bg-white border border-gray-100 rounded-2xl p-4 shadow-[0_4px_20px_-2px_rgba(0,0,0,0.05),0_2px_10px_-4px_rgba(0,0,0,0.03)]"><div data-v-f3be6059="" id="composerBody" class="flex flex-col space-y-3"><!-- Attachment Preview Thumbnails --><!--v-if--><!-- Text Input:固定前缀绝对定位,可编辑区仅首行缩进;已生成海报后不再展示前缀 --><div data-v-f3be6059="" id="textRow" class="relative pt-1 text-sm text-gray-800 leading-relaxed"><span data-v-f3be6059="" id="prefix" hidden class="absolute top-1 left-0 py-0.5 text-sm text-gray-800 leading-relaxed select-none">帮我生成一组</span><span data-v-f3be6059="" id="placeholder" hidden class="absolute top-1 py-0.5 text-sm text-gray-300 leading-relaxed pointer-events-none whitespace-nowrap" style="left: 0;">[上传产品图或描述行业需求]</span><div data-v-f3be6059="" id="input" contenteditable="true" class="w-full text-gray-800 outline-none min-h-[1.75em] max-h-[8em] overflow-y-auto whitespace-pre-wrap py-0.5"></div></div><!-- Bottom Toolbar --><footer data-v-f3be6059="" class="flex items-center justify-between mt-4 pt-1"><!-- Left Controls --><div data-v-f3be6059="" class="flex items-center space-x-4 text-gray-600 font-medium"><!-- Upload Button (圆形 add) --><button data-v-f3be6059="" id="plus" type="button" aria-label="上传参考" class="w-8 h-8 rounded-full flex items-center justify-center bg-white hover:bg-gray-100 transition-colors cursor-pointer text-gray-500"><span data-v-f3be6059="" class="material-symbols-outlined text-lg">add</span></button>
<div class="skill-wrap" data-v-f3be6059="">
<button id="skillBtn" type="button" class="skill-btn" title="选择行业与技能" aria-haspopup="listbox" aria-expanded="false">
<span class="material-symbols-outlined">auto_awesome</span>
<span id="skillLabel">加载行业…</span>
<span class="material-symbols-outlined">expand_more</span>
</button>
<div id="skillMenu" class="skill-menu" hidden>
<div class="skill-menu__bar">
<span class="skill-menu__title">选择行业与技能</span>
<button id="skillMenuClose" class="skill-menu__close" type="button" aria-label="关闭">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<div class="skill-menu__section">
<div class="skill-menu__head">行业</div>
<div id="industryTabs" class="industry-tabs" role="tablist" aria-label="行业列表"></div>
</div>
<div class="skill-menu__section skill-menu__section--list">
<div class="skill-menu__head">技能</div>
<div id="skillListWrap" class="skill-list-wrap">
<div id="skillList" role="listbox" aria-label="技能列表"></div>
</div>
</div>
<div class="skill-menu__foot">选定技能后会自动填充示例提示词,可继续修改</div>
</div>
</div></div><!-- Right Controls --><div data-v-f3be6059="" class="flex items-center space-x-3"><!-- Generate Button --><button data-v-f3be6059="" id="send" type="button" class="w-8 h-8 text-white rounded-full flex items-center justify-center shadow-sm transition-all duration-200 bg-gray-400 cursor-not-allowed" disabled=""><span data-v-f3be6059="" class="material-symbols-outlined text-sm">arrow_upward</span></button></div></footer></div></div></div><div data-v-f3be6059="" class="flex justify-center items-start gap-1.5 select-none pt-4 pb-2" style="visibility:hidden" aria-hidden="true"><span data-v-f3be6059="" class="text-xs text-gray-400 leading-snug">允许平台将本会话生成的作品用于宣传展示与图片搜索收录</span></div></div><input id="file" type="file" accept="image/*,.pdf,.doc,.docx,.txt,.md" multiple hidden></main><!-- Upload Media Modal -->
<div id="uploadModal" class="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm" hidden>
<div class="bg-white rounded-2xl shadow-2xl w-[95vw] max-w-4xl max-h-[85vh] flex flex-col overflow-hidden">
<div class="px-8 pt-6 pb-0 shrink-0 flex items-center justify-between">
<div class="flex gap-8 border-b border-slate-200">
<button type="button" data-upload-tab="library" class="upload-tab pb-3 text-sm font-semibold transition-all cursor-pointer relative">素材库上传<div class="upload-tab-line absolute bottom-0 left-0 right-0 h-0.5 bg-black"></div></button>
<button type="button" data-upload-tab="works" class="upload-tab pb-3 text-sm font-semibold transition-all cursor-pointer relative">作品上传<div class="upload-tab-line absolute bottom-0 left-0 right-0 h-0.5 bg-black"></div></button>
<button type="button" data-upload-tab="local" class="upload-tab pb-3 text-sm font-semibold transition-all cursor-pointer relative">本地上传<div class="upload-tab-line absolute bottom-0 left-0 right-0 h-0.5 bg-black"></div></button>
</div>
<button id="uploadClose" type="button" class="p-2 text-slate-400 hover:text-slate-900 transition-colors cursor-pointer" aria-label="关闭上传"><span class="material-symbols-outlined">close</span></button>
</div>
<div class="flex-1 overflow-y-auto p-6">
<div id="uploadLibraryPanel" class="upload-panel is-active h-full flex-col">
<nav id="uploadLibraryBreadcrumb" class="hidden items-center gap-2 text-sm text-slate-400 font-medium mb-4">
<button id="uploadLibraryRoot" type="button" class="hover:text-slate-900 transition-colors cursor-pointer">素材库</button>
<span class="material-symbols-outlined text-sm">chevron_right</span>
<span id="uploadFolderName" class="text-slate-900"></span>
</nav>
<div id="uploadLibraryEmpty" class="flex flex-col items-center justify-center h-48 text-center">
<span class="material-symbols-outlined text-slate-300 text-5xl mb-3">folder_open</span>
<p id="uploadLibraryEmptyText" class="text-slate-500 text-sm">素材库为空</p>
</div>
<div id="uploadLibraryGrid" class="hidden grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-5 gap-4"></div>
</div>
<div id="uploadWorksPanel" class="upload-panel min-h-full flex-col">
<div id="uploadWorksEmpty" class="flex flex-col items-center justify-center h-48 text-center">
<span class="material-symbols-outlined text-slate-300 text-5xl mb-3">image_not_supported</span>
<p id="uploadWorksEmptyText" class="text-slate-500 text-sm">暂无作品</p>
</div>
<div id="uploadWorksGrid" class="hidden columns-3 sm:columns-4 lg:columns-5 gap-4 space-y-4"></div>
</div>
<div id="uploadLocalPanel" class="upload-panel h-full flex-col">
<div id="uploadDropZone" class="border-2 border-dashed border-gray-200 rounded-xl bg-gray-50/50 p-8 flex flex-col items-center justify-center gap-3 hover:border-gray-300 hover:bg-gray-50 transition-all cursor-pointer">
<div class="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center"><span class="material-symbols-outlined text-gray-400">upload</span></div>
<div class="text-center">
<p class="text-sm font-medium text-gray-700">点击或拖拽文件到此处上传</p>
<p class="text-xs text-gray-400 mt-1">支持图片、Word、PDF、TXT、MD 文件,可多选</p>
</div>
</div>
<div id="uploadSelected" class="mt-6 hidden">
<div id="uploadFileList" class="space-y-2 max-h-56 overflow-y-auto"></div>
<div id="uploadProgressWrap" class="mt-3 hidden">
<div class="h-1.5 bg-gray-200 rounded-full overflow-hidden"><div id="uploadProgressBar" class="h-full bg-black rounded-full transition-[width] duration-200" style="width:0%"></div></div>
<p id="uploadProgressText" class="text-xs text-gray-500 mt-1">正在上传 0%</p>
</div>
<div class="mt-3 flex justify-end">
<button id="uploadClear" type="button" class="px-3 py-1.5 text-xs font-medium text-gray-500 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer">清空</button>
</div>
</div>
<div id="uploadError" class="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg text-xs text-red-600" hidden></div>
</div>
</div>
<div class="px-6 py-4 border-t border-slate-200 flex items-center justify-end gap-3 shrink-0">
<button id="uploadCancel" type="button" class="px-5 py-2.5 text-sm font-semibold text-gray-600 bg-gray-100 rounded-xl hover:bg-gray-200 transition-colors cursor-pointer">取消</button>
<button id="uploadConfirm" type="button" class="hidden px-5 py-2.5 text-sm font-semibold text-white bg-black rounded-xl hover:opacity-90 transition-opacity items-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer">确认上传</button>
</div>
</div>
</div>
<!--v-if--><!-- 行程内容放大编辑弹窗:与确认框共享 confirmTexts,完成时写回草稿 --><!--v-if--><!-- Score Detail Dialog --><!--v-if--><!-- Recharge Dialog --><!--v-if--><!-- Lightbox --><!--teleport start--><!--teleport end--><!-- 二维码替换工具 --><!--teleport start--><!--teleport end--><!-- 绑定手机号(微信登录后未绑定则提示) --><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div><div id="attachmentHoverPreview" class="fixed z-50 pointer-events-none hidden"><div class="bg-white rounded-xl shadow-2xl border border-gray-200 overflow-hidden p-1.5"><img id="attachmentHoverImage" class="rounded-lg object-contain" style="width:200px;height:auto" alt="附件预览" /></div></div><div id="toast" class="fixed bottom-8 left-1/2 -translate-x-1/2 z-[100] rounded-lg bg-black/85 px-4 py-2 text-xs text-white opacity-0 pointer-events-none transition-opacity duration-200"></div>
<script>
(function () {
"use strict";
const SKILL_META = (document.querySelector('meta[name="skill-agent-api"]') || {}).content || "";
const SKILL_API = (SKILL_META && SKILL_META.charAt(0) !== "%" ? SKILL_META.replace(/\/$/, "") : "/skill-agent");
const $ = function (id) { return document.getElementById(id); };
const sidebar = $("sidebar");
const logoHome = $("logoHome");
const collapseBtn = $("collapse");
const newChat = $("newChat");
const newChatLabel = $("newChatLabel");
const recentGroups = $("recentGroups");
const recentCollapsed = $("recentCollapsed");
const userRow = $("userRow");
const userMeta = $("userMeta");
const userAvatar = $("userAvatar");
const userNickname = $("userNickname");
const main = $("main");
const chatScroll = $("chatScroll");
const empty = $("empty");
const thread = $("thread");
const prefix = $("prefix");
const placeholder = $("placeholder");
const input = $("input");
const plus = $("plus");
const send = $("send");
const fileInput = $("file");
const composerBody = $("composerBody");
const composerShell = $("composerShell");
const textRow = $("textRow");
const attachmentHoverPreview = $("attachmentHoverPreview");
const attachmentHoverImage = $("attachmentHoverImage");
if (attachmentHoverImage) attachmentHoverImage.referrerPolicy = "no-referrer";
const toast = $("toast");
const mobileMenu = $("mobileMenu");
const mobileLogo = $("mobileLogo");
const mobileDrawer = $("mobileDrawer");
const mobileBackdrop = $("mobileBackdrop");
const mobileClose = $("mobileClose");
const mNewChat = $("mNewChat");
const mUserRow = $("mUserRow");
const mUserAvatar = $("mUserAvatar");
const mUserNickname = $("mUserNickname");
const skillBtn = $("skillBtn");
const skillLabel = $("skillLabel");
const skillMenu = $("skillMenu");
const industryTabs = $("industryTabs");
const skillList = $("skillList");
const skillListWrap = $("skillListWrap");
const skillMenuClose = $("skillMenuClose");
const skillWrap = skillBtn ? skillBtn.closest(".skill-wrap") : null;
const uploadModal = $("uploadModal");
const uploadTabButtons = Array.prototype.slice.call(document.querySelectorAll("[data-upload-tab]"));
const uploadLibraryPanel = $("uploadLibraryPanel");
const uploadWorksPanel = $("uploadWorksPanel");
const uploadLocalPanel = $("uploadLocalPanel");
const uploadDropZone = $("uploadDropZone");
const uploadSelected = $("uploadSelected");
const uploadFileList = $("uploadFileList");
const uploadProgressWrap = $("uploadProgressWrap");
const uploadProgressBar = $("uploadProgressBar");
const uploadProgressText = $("uploadProgressText");
const uploadError = $("uploadError");
const uploadClear = $("uploadClear");
const uploadConfirm = $("uploadConfirm");
const uploadCancel = $("uploadCancel");
const uploadClose = $("uploadClose");
const uploadLibraryBreadcrumb = $("uploadLibraryBreadcrumb");
const uploadLibraryRoot = $("uploadLibraryRoot");
const uploadFolderName = $("uploadFolderName");
const uploadLibraryEmpty = $("uploadLibraryEmpty");
const uploadLibraryEmptyText = $("uploadLibraryEmptyText");
const uploadLibraryGrid = $("uploadLibraryGrid");
const uploadWorksEmpty = $("uploadWorksEmpty");
const uploadWorksEmptyText = $("uploadWorksEmptyText");
const uploadWorksGrid = $("uploadWorksGrid");
const labels = Array.prototype.slice.call(sidebar.querySelectorAll("h3"));
let sessions = [];
let seq = 0;
let activeMenu = "new-chat";
let currentSessionId = null;
let remotePollTimer = 0;
function persistCurrentSession(id) {
if (id) localStorage.setItem("current_session_id", String(id));
else localStorage.removeItem("current_session_id");
}
function stopRemotePolling() {
if (remotePollTimer) {
window.clearInterval(remotePollTimer);
remotePollTimer = 0;
}
}
let collapsedGroups = Object.create(null);
let busy = false;
let toastTimer = 0;
let attachments = [];
let previewRow = null;
let uploadTab = "library";
let selectedLocalFiles = [];
let uploadProgress = null;
let uploadProgressIndex = 0;
let uploadProgressTotal = 0;
let uploadAbortController = null;
let uploadFolders = [];
let uploadAssets = [];
let uploadFolderId = "0";
let uploadFolderLabel = "";
let uploadSelectedAsset = null;
let uploadWorks = [];
let uploadSelectedWork = null;
let uploadLibraryLoaded = false;
let uploadWorksLoaded = false;
let skills = [];
let industries = [];
let selectedIndustry = "";
let selectedSkillKey = "";
let lastDefaultPrompt = "";
let skillLoading = false;
let skillError = "";
let renderTimer = 0;
function showToast(message) {
if (!toast) return;
toast.textContent = message;
toast.classList.add("is-visible");
window.clearTimeout(toastTimer);
toastTimer = window.setTimeout(function () {
toast.classList.remove("is-visible");
}, 2200);
}
function goMe(tab) {
window.location.href = tab ? "/me?tab=" + encodeURIComponent(tab) : "/me";
}
function syncNewChatButton(el, active) {
if (!el) return;
el.classList.toggle("bg-[#f5f5f5]", active);
el.classList.toggle("text-black", active);
el.classList.toggle("font-semibold", active);
el.classList.toggle("text-gray-500", !active);
el.classList.toggle("hover:bg-gray-50", !active);
el.classList.toggle("hover:text-gray-900", !active);
}
function setActive(menu) {
activeMenu = menu;
syncNewChatButton(newChat, menu === "new-chat");
syncNewChatButton(mNewChat, menu === "new-chat");
renderRecent();
}
const SB_LABELS = [];
let collapseTimer = 0;
function collapseLabelTargets() {
if (!SB_LABELS.length) {
const logoText = logoHome ? logoHome.querySelector("span") : null;
SB_LABELS.push(logoText, newChatLabel, userMeta);
labels.forEach(function (label) { SB_LABELS.push(label); });
SB_LABELS.forEach(function (el) {
if (!el) return;
el.style.transition = "opacity .16s ease";
el.style.whiteSpace = "nowrap";
el.style.overflow = "hidden";
el.style.minWidth = "0";
});
}
return SB_LABELS;
}
function fadeLabels(show) {
collapseLabelTargets().forEach(function (el) {
if (!el) return;
el.style.opacity = show ? "1" : "0";
});
}
function setCollapsed(collapsed) {
window.clearTimeout(collapseTimer);
const animate = function (fn, delay) {
collapseTimer = window.setTimeout(fn, delay);
};
if (collapsed) {
fadeLabels(false);
sidebar.classList.remove("w-64");
sidebar.classList.add("w-20");
animate(function () {
logoHome.classList.add("hidden");
newChatLabel.classList.add("hidden");
userMeta.classList.add("hidden");
for (let i = 0; i < labels.length; i++) {
labels[i].classList.add("text-[#f9f9f9]");
labels[i].classList.remove("text-gray-400");
}
collapseBtn.classList.add("mx-auto");
newChat.classList.add("justify-center", "px-0");
recentGroups.classList.add("hidden");
recentCollapsed.classList.remove("hidden");
recentCollapsed.classList.add("flex");
userRow.classList.add("justify-center");
}, 280);
return;
}
logoHome.classList.remove("hidden");
newChatLabel.classList.remove("hidden");
userMeta.classList.remove("hidden");
for (let i = 0; i < labels.length; i++) {
labels[i].classList.remove("text-[#f9f9f9]");
labels[i].classList.add("text-gray-400");
}
collapseBtn.classList.remove("mx-auto");
newChat.classList.remove("justify-center", "px-0");
recentGroups.classList.remove("hidden");
recentCollapsed.classList.add("hidden");
recentCollapsed.classList.remove("flex");
userRow.classList.remove("justify-center");
collapsedGroups = Object.create(null);
renderRecent();
fadeLabels(false);
sidebar.classList.remove("w-20");
sidebar.classList.add("w-64");
animate(function () { fadeLabels(true); }, 60);
}
function toggleCollapse() {
setCollapsed(!sidebar.classList.contains("w-20"));
}
const RECENT_BUCKETS = [
{ max: 0, label: "今天" },
{ max: 7, label: "7天内" },
{ max: 30, label: "30天内" },
{ max: Infinity, label: "更早" },
];
function sessionDayLabel(ts) {
const day = new Date(ts || Date.now());
day.setHours(0, 0, 0, 0);
const startToday = new Date();
startToday.setHours(0, 0, 0, 0);
const diffDays = Math.floor((startToday.getTime() - day.getTime()) / 86400000);
for (let i = 0; i < RECENT_BUCKETS.length; i += 1) {
if (diffDays <= RECENT_BUCKETS[i].max) return RECENT_BUCKETS[i].label;
}
return "更早";
}
function renderRecent() {
recentGroups.innerHTML = "";
if (!sessions.length) return;
const index = Object.create(null);
sessions.forEach(function (session) {
const label = sessionDayLabel(session.createdAt);
if (!index[label]) index[label] = { label: label, items: [] };
index[label].items.push(session);
});
const groups = [];
RECENT_BUCKETS.forEach(function (bucket) {
if (index[bucket.label]) groups.push(index[bucket.label]);
});
groups.forEach(function (group) {
const box = document.createElement("div");
box.setAttribute("data-v-56bfec04", "");
const hasActive = group.items.some(function (item) {
return activeMenu === "recent-" + item.id;
});
const collapsed = collapsedGroups[group.label] !== undefined
? !!collapsedGroups[group.label]
: !hasActive;
const head = document.createElement("button");
head.type = "button";
head.className =
"flex items-center justify-between w-full px-4 py-1.5 rounded-lg text-[11px] font-semibold text-gray-400 hover:text-gray-600 hover:bg-gray-50 transition-colors cursor-pointer";
head.setAttribute("data-v-56bfec04", "");
head.innerHTML =
'<span></span><span data-v-56bfec04="" class="material-symbols-outlined text-sm transition-transform duration-200 ' +
(collapsed ? "" : "rotate-180") + '">expand_more</span>';
head.firstChild.textContent = group.label;
head.addEventListener("click", function () {
if (sidebar.classList.contains("w-20")) {
setCollapsed(false);
return;
}
collapsedGroups[group.label] = !collapsed;
renderRecent();
});
const items = document.createElement("div");
items.className = "space-y-1 mt-1" + (collapsed ? " hidden" : "");
items.setAttribute("data-v-56bfec04", "");
group.items.forEach(function (session) {
const active = activeMenu === "recent-" + session.id;
const item = document.createElement("button");
item.type = "button";
item.setAttribute("data-v-56bfec04", "");
item.className =
"flex items-center gap-2.5 px-4 py-2.5 rounded-lg text-[13px] font-medium transition-all cursor-pointer hover:scale-105 transform duration-200 w-full " +
(active ? "bg-[#f5f5f5] text-black font-semibold" : "text-gray-500 hover:bg-gray-50 hover:text-gray-900");
item.innerHTML =
'<span data-v-56bfec04="" class="material-symbols-outlined text-[14px] shrink-0 ' +
(active ? "text-black" : "text-gray-400") + '">sms</span><span class="truncate"></span>';
item.querySelector(".truncate").textContent = session.title;
item.addEventListener("click", function () {
selectSession(session);
});
items.appendChild(item);
});
box.appendChild(head);
box.appendChild(items);
recentGroups.appendChild(box);
});
}
function makeLoader() {
const loader = document.createElement("div");
loader.setAttribute("data-v-f3be6059", "");
loader.className = "flex items-center gap-1.5 text-xs text-gray-400";
loader.innerHTML =
'<span class="material-symbols-outlined text-[15px] animate-spin">progress_activity</span><span>正在生成…</span>';
return loader;
}
function attachmentTypeOf(item) {
const type = item && item.type;
if (type === "image" || type === "video" || type === "audio" || type === "other") return type;
const url = String((item && item.url) || "").split("?")[0].toLowerCase();
if (/\.(png|jpe?g|gif|webp|bmp|svg)$/.test(url)) return "image";
if (/\.(mp4|mov|webm|avi|mkv)$/.test(url)) return "video";
if (/\.(mp3|wav|ogg|aac|flac|m4a)$/.test(url)) return "audio";
return "other";
}
function makeUserAttachments(items) {
const row = document.createElement("div");
row.setAttribute("data-v-f3be6059", "");
row.className = "flex justify-end gap-1.5 mb-1.5 overflow-x-auto max-w-full";
const imageUrls = [];
(items || []).forEach(function (item) {
const url = resolveUploadUrl(item && item.url);
if (!url) return;
const type = attachmentTypeOf(item);
if (type === "video") {
const video = document.createElement("video");
video.src = url;
video.muted = true;
video.playsInline = true;
video.preload = "metadata";
video.className = "w-12 h-12 rounded-lg object-cover border border-gray-200 shrink-0 bg-black";
row.appendChild(video);
return;
}
if (type === "audio") {
const audio = document.createElement("div");
audio.className = "w-12 h-12 rounded-lg border border-gray-200 shrink-0 bg-gray-100 flex items-center justify-center";
const icon = document.createElement("span");
icon.className = "material-symbols-outlined text-gray-500 text-xl";
icon.textContent = "music_note";
audio.appendChild(icon);
row.appendChild(audio);
return;
}
if (type === "other") {
const link = document.createElement("a");
link.href = url;
link.target = "_blank";
link.rel = "noopener";
link.className = "flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg border border-gray-200 bg-white shrink-0 hover:bg-gray-50 transition-colors";
const icon = document.createElement("span");
icon.className = "material-symbols-outlined text-base text-gray-400";
icon.textContent = "description";
const name = document.createElement("span");
name.className = "max-w-[120px] truncate text-xs text-gray-600";
name.textContent = (item && item.name) || "文件";
link.appendChild(icon);
link.appendChild(name);
row.appendChild(link);
return;
}
const img = document.createElement("img");
img.src = url;
img.alt = (item && item.name) || "参考图";
img.loading = "lazy";
img.referrerPolicy = "no-referrer";
img.className =
"w-12 h-12 rounded-lg border border-gray-200 object-cover shrink-0 cursor-zoom-in hover:opacity-90 transition-opacity";
const imageIndex = imageUrls.push(url) - 1;
img.addEventListener("click", function () { openImageLightbox(imageUrls, imageIndex); });
row.appendChild(img);
});
return row;
}
const UPLOADED_DOCS_RE = /【用户上传了[\s\S]*?】\s*/g;
// 历史会话里存的是发给 Agent 的完整消息(含文档指令块),展示时应只显示用户原文
function cleanUserPromptText(text) {
const raw = String(text || "");
const cleaned = raw.replace(UPLOADED_DOCS_RE, "").trim();
return cleaned || raw;
}
function cleanSessionTitle(title) {
const raw = String(title || "");
let text = raw.replace(UPLOADED_DOCS_RE, "");
const mark = text.indexOf("【用户上传了");
if (mark !== -1) text = text.slice(0, mark);
text = text.replace(/\s+/g, " ").trim();
return text ? text.slice(0, 40) : "新对话";
}
function makeUserBubble(session) {
const wrap = document.createElement("div");
wrap.setAttribute("data-v-f3be6059", "");
wrap.className = "flex flex-col gap-2";
const items = Array.isArray(session.attachments) && session.attachments.length
? session.attachments
: (Array.isArray(session.referenceImages) && session.referenceImages.length
? session.referenceImages.map(function (url) { return { url: url, type: "image", name: "参考图" }; })
: (session.imageUrl ? [{ url: session.imageUrl, type: "image", name: "参考图" }] : []));
if (items.length) wrap.appendChild(makeUserAttachments(items));
const row = document.createElement("div");
row.className = "flex justify-end";
const inner = document.createElement("div");
inner.className = "flex flex-col items-end max-w-[85%]";
const prompt = cleanUserPromptText(session.prompt) || "根据这张产品图生成行业海报";
const bubble = document.createElement("div");
bubble.className =
"bg-white text-gray-800 rounded-xl px-4 py-2.5 text-[15px] leading-[1.6] whitespace-pre-wrap";
bubble.textContent = prompt;
inner.appendChild(bubble);
const copyBtn = document.createElement("button");
copyBtn.type = "button";
copyBtn.title = "复制提示词";
copyBtn.setAttribute("aria-label", "复制提示词");
copyBtn.className =
"mt-0.5 -mr-1 w-7 h-7 flex items-center justify-center rounded-full text-gray-400 bg-transparent hover:text-gray-600 hover:bg-gray-100 active:bg-gray-200 active:text-gray-700 active:scale-90 transition-all duration-150 cursor-pointer";
copyBtn.innerHTML = '<span class="material-symbols-outlined" style="font-size:16px;line-height:1">content_copy</span>';
copyBtn.addEventListener("click", function () {
copyToClipboard(prompt, copyBtn);
});
inner.appendChild(copyBtn);
row.appendChild(inner);
wrap.appendChild(row);
return wrap;
}
function copyToClipboard(text, button) {
const done = function () {
if (!button) return;
const original = button.innerHTML;
button.innerHTML = '<span class="material-symbols-outlined text-green-500" style="font-size:16px;line-height:1">check</span>';
window.setTimeout(function () { button.innerHTML = original; }, 1200);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done, done);
return;
}
const area = document.createElement("textarea");
area.value = text;
document.body.appendChild(area);
area.select();
try { document.execCommand("copy"); } catch (error) { console.warn("复制失败", error); }
document.body.removeChild(area);
done();
}
// 仅「旅游商品详情长图」技能需要把多张详情图拼成一张长图
function isLongImageSession(session) {
const key = String((session && session.skillKey) || "");
if (/longtrip|long-image|longimage|detail-page|travel-detail/i.test(key)) return true;
const skill = key ? skills.find(function (item) { return item.key === key; }) : null;
const name = String((skill && (skill.display_name || skill.name)) || (session && session.skillName) || "");
return /详情长图|旅游详情/.test(name);
}
function loadStitchImage(url, useCors) {
return new Promise(function (resolve, reject) {
const img = new Image();
if (useCors) img.crossOrigin = "anonymous";
img.onload = function () { resolve(img); };
img.onerror = function () { reject(new Error("图片加载失败")); };
img.src = url;
});
}
function loadStitchImageWithFallback(url) {
return loadStitchImage(url, true).catch(function () { return loadStitchImage(url, false); });
}
async function buildTravelLongImage(urls) {
if (!Array.isArray(urls) || urls.length < 2) throw new Error("至少需要 2 张图片才能拼接长图");
const images = await Promise.all(urls.map(loadStitchImageWithFallback));
const valid = images.filter(function (img) { return img.naturalWidth > 0 && img.naturalHeight > 0; });
if (valid.length < 2) throw new Error("可拼接的图片不足 2 张");
const gap = 16;
const maxWidth = 1080;
const maxHeight = 16000;
const widest = Math.max.apply(null, valid.map(function (img) { return img.naturalWidth; }));
const baseWidth = Math.min(maxWidth, widest);
const baseHeights = valid.map(function (img) { return Math.round(img.naturalHeight * (baseWidth / img.naturalWidth)); });
const rawHeight = baseHeights.reduce(function (sum, h) { return sum + h; }, 0) + gap * (valid.length - 1);
const scale = rawHeight > maxHeight ? maxHeight / rawHeight : 1;
const width = Math.max(1, Math.round(baseWidth * scale));
const heights = baseHeights.map(function (h) { return Math.max(1, Math.round(h * scale)); });
const totalHeight = heights.reduce(function (sum, h) { return sum + h; }, 0) + gap * (valid.length - 1);
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = totalHeight;
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("当前浏览器不支持长图拼接");
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, width, totalHeight);
let y = 0;
valid.forEach(function (img, index) {
ctx.drawImage(img, 0, y, width, heights[index]);
y += heights[index] + gap;
});
const blob = await new Promise(function (resolve, reject) {
try {
canvas.toBlob(function (result) {
if (result) resolve(result);
else reject(new Error("长图导出失败"));
}, "image/jpeg", 0.95);
} catch (error) {
reject(new Error("长图导出失败:图片未开启跨域(CORS)"));
}
});
return { blob: blob, width: width, height: totalHeight };
}
function closeLongImage() {
const overlay = document.getElementById("longImageOverlay");
if (!overlay) return;
overlay.style.display = "none";
document.documentElement.style.overflow = "";
const img = overlay.querySelector(".long-image-preview");
if (img && img.dataset.objectUrl) {
URL.revokeObjectURL(img.dataset.objectUrl);
delete img.dataset.objectUrl;
}
}
function showLongImage(result) {
const blobUrl = URL.createObjectURL(result.blob);
let overlay = document.getElementById("longImageOverlay");
if (!overlay) {
overlay = document.createElement("div");
overlay.id = "longImageOverlay";
overlay.style.cssText = "position:fixed;inset:0;z-index:140;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.82);padding:24px;";
overlay.innerHTML =
'<div style="position:relative;max-width:min(92vw,760px);max-height:92vh;display:flex;flex-direction:column;gap:12px">'
+ '<div style="display:flex;justify-content:space-between;align-items:center;gap:12px;color:#fff">'
+ '<span class="long-image-title" style="font-size:13px;opacity:.85"></span>'
+ '<div style="display:flex;gap:8px;align-items:center">'
+ '<a class="long-image-download" style="display:inline-flex;align-items:center;gap:6px;background:#fff;color:#111;border-radius:999px;padding:6px 14px;font-size:13px;font-weight:600;text-decoration:none">'
+ '<span class="material-symbols-outlined" style="font-size:16px;line-height:1">download</span><span>下载</span>长图</a>'
+ '<button type="button" class="long-image-close" style="width:32px;height:32px;border-radius:999px;background:rgba(255,255,255,.16);color:#fff;border:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center">'
+ '<span class="material-symbols-outlined" style="font-size:18px">close</span></button>'
+ "</div></div>"
+ '<img class="long-image-preview" alt="旅游详情长图" style="max-height:80vh;max-width:100%;object-fit:contain;border-radius:12px;background:#fff" />'
+ "</div>";
overlay.addEventListener("click", function (event) {
if (event.target === overlay || (event.target.closest && event.target.closest(".long-image-close"))) closeLongImage();
});
document.body.appendChild(overlay);
}
const img = overlay.querySelector(".long-image-preview");
const link = overlay.querySelector(".long-image-download");
const title = overlay.querySelector(".long-image-title");
if (img.dataset.objectUrl) URL.revokeObjectURL(img.dataset.objectUrl);
img.dataset.objectUrl = blobUrl;
img.src = blobUrl;
link.href = blobUrl;
link.download = "旅游详情长图-" + Date.now() + ".jpg";
title.textContent = "旅游详情长图 · " + result.width + " × " + result.height;
overlay.style.display = "flex";
document.documentElement.style.overflow = "hidden";
}
function appendTravelLongImageButton(wrap, media) {
const bar = document.createElement("div");
bar.className = "flex justify-end";
const button = document.createElement("button");
button.type = "button";
button.className =
"inline-flex items-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-600 transition-colors cursor-pointer hover:border-black hover:text-black disabled:opacity-60 disabled:cursor-not-allowed";
button.innerHTML = '<span class="material-symbols-outlined text-[16px]">view_stream</span><span>拼接长图</span>';
button.addEventListener("click", async function () {
if (button.disabled) return;
const label = button.querySelector("span:last-child");
button.disabled = true;
if (label) label.textContent = "拼接中…";
try {
const result = await buildTravelLongImage(media);
showLongImage(result);
} catch (error) {
showToast(error instanceof Error ? error.message : "长图拼接失败");
} finally {
button.disabled = false;
if (label) label.textContent = "拼接长图";
}
});
bar.appendChild(button);
wrap.appendChild(bar);
}
window.addEventListener("keydown", function (event) {
if (event.key === "Escape") closeLongImage();
});
// 最终回答里若已按「图N + 图片」内联展示,就不再在顶部重复放网格
function prepareAnswerWithInlineMedia(answer, media) {
const text = String(answer || "");
const keys = Object.create(null);
(media || []).forEach(function (url) { keys[normalizeMediaUrl(url)] = true; });
const inline = [];
const guarded = text.replace(/!\[([^\]]*)\]\(([^)\s]+)[^)]*\)/g, function (full, alt, url) {
if (!keys[normalizeMediaUrl(url)]) return full;
const token = "\u0000INLINE_MEDIA_" + inline.length + "\u0000";
inline.push({ token: token, alt: alt || "生成结果", url: url });
return token;
});
let output = stripMediaFromAnswer(guarded, media);
inline.forEach(function (item) {
output = output.split(item.token).join("\n\n![" + item.alt + "](" + item.url + ")\n\n");
});
const inlineKeys = Object.create(null);
inline.forEach(function (item) { inlineKeys[normalizeMediaUrl(item.url)] = true; });
return {
text: output,
inlineCount: inline.length,
remainingMedia: (media || []).filter(function (url) { return !inlineKeys[normalizeMediaUrl(url)]; }),
};
}
function makeResult(session) {
const wrap = document.createElement("div");
wrap.setAttribute("data-v-f3be6059", "");
wrap.className = "flex flex-col gap-2 scroll-mt-20";
const media = Array.isArray(session.media) ? session.media : [];
const streaming = session.status === "loading";
const prepared = prepareAnswerWithInlineMedia(session.answer, media);
// 生成过程中不提前在顶部铺图片网格,避免图片先出现在上方、随后又被图1/图2内联替换
const gridMedia = streaming ? [] : (prepared.inlineCount ? prepared.remainingMedia : media);
if (gridMedia.length) {
const grid = document.createElement("div");
grid.className = "grid grid-cols-2 md:grid-cols-4 gap-2";
for (let i = 0; i < gridMedia.length; i++) {
const image = document.createElement("img");
image.src = gridMedia[i];
image.alt = "生成结果 " + (i + 1);
image.loading = "lazy";
image.referrerPolicy = "no-referrer";
image.className =
"w-full rounded-lg border border-gray-200 object-cover cursor-zoom-in hover:opacity-90 transition-opacity";
image.addEventListener("click", function () { openImageLightbox(gridMedia, i); });
grid.appendChild(image);
}
wrap.appendChild(grid);
}
if (!streaming && media.length > 1 && isLongImageSession(session)) appendTravelLongImageButton(wrap, media);
if (session.answer) {
const text = document.createElement("div");
// 只有最终生成完成的内容才用白色卡片包裹;流式输出阶段显示纯文本
if (streaming) {
text.className = "text-[15px] text-gray-800 leading-[1.6] whitespace-pre-wrap break-words";
text.textContent = stripMediaFromAnswer(session.answer, media);
} else {
text.className = "text-[15px] text-gray-800 leading-[1.6] break-words generation-md";
text.innerHTML = renderAnswer(prepared.text);
bindAnswerMediaPreview(text);
}
wrap.appendChild(text);
}
if (!media.length && !session.answer) {
const text = document.createElement("div");
text.className = "text-sm text-gray-500";
text.textContent = "生成完成";
wrap.appendChild(text);
}
return wrap;
}
function renderAnswer(text) {
const renderer = window.__renderMarkdown;
if (typeof renderer === "function") {
try {
return renderer(String(text || ""));
} catch (error) {
console.warn("markdown 渲染失败,回退到纯文本", error);
}
}
return formatAnswerHtml(text);
}
function formatAnswerHtml(text) {
const escaped = String(text)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return escaped
.replace(/\*\*([^*\n]+)\*\*/g, '<strong class="font-semibold text-gray-900">$1</strong>')
.replace(/^#{1,6}\s+(.+)$/gm, '<span class="font-semibold text-gray-900">$1</span>')
.replace(/\n/g, "<br>");
}
function bindAnswerMediaPreview(container) {
const images = Array.prototype.slice.call(container.querySelectorAll("img"));
const urls = images.map(function (img) { return img.currentSrc || img.src; });
images.forEach(function (img, index) {
img.classList.add("cursor-zoom-in");
img.addEventListener("click", function () { openImageLightbox(urls, index); });
});
Array.prototype.forEach.call(container.querySelectorAll("a[href]"), function (link) {
const href = link.getAttribute("href") || "";
if (!/\.(png|jpe?g|gif|webp|bmp|svg)(\?|#|$)/i.test(href)) return;
link.addEventListener("click", function (event) {
event.preventDefault();
openImageLightbox([href], 0);
});
});
}
let lightboxList = [];
let lightboxIndex = 0;
function ensureLightbox() {
let box = document.getElementById("genLightbox");
if (box) return box;
box = document.createElement("div");
box.id = "genLightbox";
box.className = "fixed inset-0 z-[120] hidden items-center justify-center bg-black/80 p-6";
box.innerHTML =
'<button type="button" data-lightbox-close class="absolute top-5 right-5 w-10 h-10 rounded-full bg-white/15 text-white hover:bg-white/25 transition-colors cursor-pointer" aria-label="关闭">' +
'<span class="material-symbols-outlined">close</span></button>' +
'<button type="button" class="lightbox-nav lightbox-nav--prev" aria-label="上一张" style="position:absolute;left:16px;top:50%;transform:translateY(-50%);width:42px;height:42px;border:0;border-radius:999px;background:rgba(255,255,255,.16);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer">' +
'<span class="material-symbols-outlined">chevron_left</span></button>' +
'<button type="button" class="lightbox-nav lightbox-nav--next" aria-label="下一张" style="position:absolute;right:16px;top:50%;transform:translateY(-50%);width:42px;height:42px;border:0;border-radius:999px;background:rgba(255,255,255,.16);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer">' +
'<span class="material-symbols-outlined">chevron_right</span></button>' +
'<div style="display:flex;flex-direction:column;align-items:center;gap:12px;max-height:92vh">' +
'<img class="lightbox-img max-h-[82vh] max-w-[80vw] rounded-xl shadow-2xl object-contain bg-white" alt="生成结果预览" />' +
'<div style="display:flex;align-items:center;gap:14px;color:#fff">' +
'<span class="lightbox-counter" style="font-size:12px;opacity:.8"></span>' +
'<button type="button" class="lightbox-download" style="display:inline-flex;align-items:center;gap:6px;background:#fff;color:#111;border:0;border-radius:999px;padding:6px 16px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap">' +
'<span class="material-symbols-outlined" style="font-size:16px">download</span>下载</button>' +
"</div></div>";
box.addEventListener("click", function (event) {
if (event.target === box || (event.target.closest && event.target.closest("[data-lightbox-close]"))) {
closeImageLightbox();
}
});
box.querySelector(".lightbox-nav--prev").addEventListener("click", function (event) {
event.stopPropagation();
stepLightbox(-1);
});
box.querySelector(".lightbox-nav--next").addEventListener("click", function (event) {
event.stopPropagation();
stepLightbox(1);
});
box.querySelector(".lightbox-download").addEventListener("click", function (event) {
event.stopPropagation();
void downloadLightboxImage();
});
document.body.appendChild(box);
return box;
}
function renderLightbox() {
const box = ensureLightbox();
const img = box.querySelector(".lightbox-img");
const counter = box.querySelector(".lightbox-counter");
const prev = box.querySelector(".lightbox-nav--prev");
const next = box.querySelector(".lightbox-nav--next");
const url = lightboxList[lightboxIndex] || "";
img.src = url;
const total = lightboxList.length;
counter.textContent = total > 1 ? lightboxIndex + 1 + " / " + total : "";
const many = total > 1;
prev.style.display = many ? "flex" : "none";
next.style.display = many ? "flex" : "none";
}
function openImageLightbox(list, index) {
lightboxList = Array.isArray(list) ? list.filter(Boolean) : [];
if (!lightboxList.length) return;
lightboxIndex = Math.min(Math.max(0, Number(index) || 0), lightboxList.length - 1);
const box = ensureLightbox();
renderLightbox();
box.classList.remove("hidden");
box.classList.add("flex");
document.documentElement.style.overflow = "hidden";
}
function stepLightbox(delta) {
if (lightboxList.length < 2) return;
lightboxIndex = (lightboxIndex + delta + lightboxList.length) % lightboxList.length;
renderLightbox();
}
function guessImageExt(url) {
const match = String(url || "").split("?")[0].match(/\.(png|jpe?g|webp|gif|bmp)$/i);
return match ? "." + match[1].toLowerCase() : ".png";
}
async function downloadLightboxImage() {
const url = lightboxList[lightboxIndex];
if (!url) return;
const filename = "来秒AI图片-" + (lightboxIndex + 1) + guessImageExt(url);
const button = ensureLightbox().querySelector(".lightbox-download");
const label = button ? button.lastChild : null;
if (button) button.disabled = true;
try {
const response = await fetch(url, { mode: "cors" });
if (!response.ok) throw new Error("HTTP " + response.status);
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = objectUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
window.setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 5000);
} catch (error) {
window.open(url, "_blank", "noopener");
} finally {
if (button) button.disabled = false;
if (label) label.textContent = label.textContent || "下载";
}
}
function closeImageLightbox() {
const box = document.getElementById("genLightbox");
if (!box) return;
box.classList.add("hidden");
box.classList.remove("flex");
lightboxList = [];
lightboxIndex = 0;
document.documentElement.style.overflow = "";
}
document.addEventListener("keydown", function (event) {
const box = document.getElementById("genLightbox");
const open = box && !box.classList.contains("hidden");
if (!open) return;
if (event.key === "Escape") closeImageLightbox();
else if (event.key === "ArrowLeft") stepLightbox(-1);
else if (event.key === "ArrowRight") stepLightbox(1);
});
function makeError(message) {
const error = document.createElement("div");
error.setAttribute("data-v-f3be6059", "");
error.className = "rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-600";
error.textContent = message || "生成失败,请稍后重试";
return error;
}
function contentActionButton(label, primary, disabled, onClick) {
const button = document.createElement("button");
button.type = "button";
button.className = primary
? "px-3 py-1.5 rounded-lg text-xs font-bold transition-colors " + (disabled ? "bg-gray-300 cursor-not-allowed text-white" : "bg-amber-500 hover:bg-amber-600 text-white cursor-pointer")
: "px-3 py-1.5 rounded-lg text-xs font-medium text-gray-600 bg-white border border-gray-200 transition-colors " + (disabled ? "opacity-60 cursor-not-allowed" : "hover:bg-gray-50 cursor-pointer");
button.textContent = label;
button.disabled = !!disabled;
if (!disabled) button.addEventListener("click", onClick);
return button;
}
function cleanConfirmDraft(value) {
if (typeof value !== "string") return value;
return value
.replace(/^(\s*(?:[-*]\s*)?)【请补充[::]\s*([^】]+)】\s*$/gm, "$1$2:")
.replace(/【请补充[::][^】]*】/g, "")
.replace(/【待填写[::]?[^】]*】/g, "");
}
function normalizeConfirmOptions(value) {
if (!Array.isArray(value)) return [];
const out = [];
value.forEach(function (item) {
if (typeof item === "string") {
const id = item.trim();
if (id) out.push({ id: id, label: id, desc: "" });
return;
}
if (!item || typeof item !== "object") return;
const id = item.id ? String(item.id) : "";
if (!id) return;
out.push({
id: id,
label: item.label ? String(item.label) : id,
desc: item.desc ? String(item.desc) : "",
});
});
return out;
}
function makeConfirmOptions(session, pending, options) {
const list = document.createElement("div");
list.className = "space-y-2";
const selectedId = typeof pending.selectedId === "string" ? pending.selectedId : "";
options.forEach(function (option) {
const active = option.id === selectedId;
const label = document.createElement("label");
label.className = "flex items-start gap-2 rounded-lg border px-2.5 py-2 transition-colors "
+ (active ? "border-amber-400 bg-amber-100" : "border-amber-200 bg-white hover:bg-amber-50")
+ (pending.submitting ? " opacity-60" : " cursor-pointer");
const input = document.createElement("input");
input.type = "radio";
input.name = "confirm-option-" + pending.toolCallId;
input.checked = active;
input.disabled = !!pending.submitting;
input.className = "mt-0.5 accent-amber-500";
input.addEventListener("change", function () {
pending.selectedId = option.id;
renderThread(session);
});
const body = document.createElement("span");
body.className = "text-[12px] leading-snug";
body.innerHTML = '<span class="font-bold text-gray-800"></span><span class="text-gray-500 ml-1.5"></span>';
body.querySelector("span:first-child").textContent = option.label || option.id;
body.querySelector("span:last-child").textContent = option.desc || "";
label.appendChild(input);
label.appendChild(body);
list.appendChild(label);
});
return list;
}
function makeContentConfirmCard(session) {
const pending = session.contentConfirm;
const payload = pending.payload || {};
const isStyle = pending.confirmType === "travel_style_confirm";
const isUser = pending.confirmType === "user_confirm";
const userOptions = isUser ? normalizeConfirmOptions(payload.options) : [];
// 有选项时优先按选择处理,避免模型漏传 mode 时把选项渲染成输入框
const genericMode = isUser ? (userOptions.length ? "select" : (payload.mode || "confirm")) : "";
const card = document.createElement("div");
card.setAttribute("data-v-f3be6059", "");
card.className = "rounded-xl border border-amber-200 bg-amber-50/80 px-4 py-3 space-y-2";
const head = document.createElement("div");
head.className = "flex items-center gap-1.5 text-xs font-medium text-amber-800";
const icon = isStyle ? "palette" : isUser ? (genericMode === "select" ? "list" : genericMode === "edit" ? "edit" : "help") : "help";
const title = isStyle ? "请选择海报风格" : isUser ? (payload.title || "请确认") : "请确认行程内容";
head.innerHTML = '<span class="material-symbols-outlined text-[16px]">' + icon + '</span><span></span>';
head.querySelector("span:last-child").textContent = title;
const canExpand = !isStyle && (!isUser || genericMode === "edit");
if (canExpand) {
const expand = document.createElement("button");
expand.type = "button";
expand.title = "放大编辑";
expand.setAttribute("aria-label", "放大编辑");
expand.className = "ml-auto p-1 rounded-md text-amber-700 hover:bg-amber-100 transition-colors cursor-pointer";
expand.innerHTML = '<span class="material-symbols-outlined text-[16px]">open_in_full</span>';
expand.addEventListener("click", function () {
const editorTitle = pending.confirmType === "travel_content_confirm" ? "编辑线路内容" : (title || "编辑内容");
openDocumentEditor(pending, editorTitle);
});
head.appendChild(expand);
}
card.appendChild(head);
let confirmDisabled = !!pending.submitting;
if (isStyle) {
const options = normalizeConfirmOptions(payload.styles);
if (!options.length) {
const empty = document.createElement("div");
empty.className = "text-xs text-gray-500";
empty.textContent = "暂无可用风格";
card.appendChild(empty);
confirmDisabled = true;
} else {
card.appendChild(makeConfirmOptions(session, pending, options));
if (!pending.selectedId) confirmDisabled = true;
}
} else if (isUser) {
const message = typeof payload.message === "string" ? payload.message : "";
const defaultValue = typeof payload.default_value === "string" ? payload.default_value.trim() : "";
// 编辑模式下若说明与可编辑内容完全一致,只保留可编辑框,避免同一段内容展示两遍
if (message && (!defaultValue || message.trim() !== defaultValue)) {
const messageEl = document.createElement("div");
messageEl.className = "max-h-60 overflow-y-auto rounded-lg bg-white/70 px-3 py-2 text-xs leading-relaxed text-gray-700 whitespace-pre-wrap";
messageEl.textContent = message;
card.appendChild(messageEl);
}
if (genericMode === "edit") {
const textarea = document.createElement("textarea");
textarea.className = "w-full rounded-lg border border-amber-200 bg-white px-3 py-2 text-xs leading-relaxed text-gray-800 outline-none focus:border-amber-400";
textarea.rows = 8;
textarea.value = typeof pending.draft === "string"
? pending.draft
: (typeof payload.default_value === "string" ? payload.default_value : "");
textarea.disabled = !!pending.submitting;
textarea.addEventListener("input", function () { pending.draft = textarea.value; });
card.appendChild(textarea);
pending.__textarea = textarea;
}
if (genericMode === "select") {
const options = userOptions;
if (!options.length) {
const empty = document.createElement("div");
empty.className = "text-xs text-gray-500";
empty.textContent = "暂无选项";
card.appendChild(empty);
confirmDisabled = true;
} else {
card.appendChild(makeConfirmOptions(session, pending, options));
if (!pending.selectedId) confirmDisabled = true;
}
}
} else {
const summary = typeof payload.summary === "string" ? payload.summary : "";
const textarea = document.createElement("textarea");
textarea.className = "w-full rounded-lg border border-amber-200 bg-white px-3 py-2 text-xs leading-relaxed text-gray-800 outline-none focus:border-amber-400";
textarea.rows = 8;
textarea.value = typeof pending.draft === "string" ? pending.draft : summary;
textarea.disabled = !!pending.submitting;
textarea.addEventListener("input", function () { pending.draft = textarea.value; });
card.appendChild(textarea);
pending.__textarea = textarea;
}
if (pending.error) {
const error = document.createElement("div");
error.className = "text-[11px] text-red-500 whitespace-pre-wrap";
error.textContent = pending.error;
card.appendChild(error);
}
const confirmLabel = isStyle
? "确认风格"
: isUser
? (genericMode === "select" ? "确认选择" : genericMode === "edit" ? "提交" : "确认")
: "确认内容";
const actions = document.createElement("div");
actions.className = "flex gap-2 pt-1";
actions.appendChild(contentActionButton(confirmLabel, true, confirmDisabled, function () {
let content = "";
if (isStyle || (isUser && genericMode === "select")) {
content = pending.selectedId || "";
} else if (isUser && genericMode === "edit") {
content = pending.__textarea ? pending.__textarea.value : (pending.draft || "");
} else if (!isUser) {
content = pending.__textarea ? pending.__textarea.value : "";
}
submitContentConfirm(session, true, content);
}));
actions.appendChild(contentActionButton("取消", false, !!pending.submitting, function () {
submitContentConfirm(session, false, "");
}));
card.appendChild(actions);
return card;
}
function openDocumentEditor(pending, title) {
const overlay = document.createElement("div");
overlay.id = "docEditor";
Object.assign(overlay.style, {
position: "fixed", inset: "0", zIndex: "130", display: "flex", alignItems: "center",
justifyContent: "center", padding: "16px", background: "rgba(0,0,0,0.5)",
backdropFilter: "blur(4px)", WebkitBackdropFilter: "blur(4px)",
});
const panel = document.createElement("div");
Object.assign(panel.style, {
width: "min(92vw, 1024px)", height: "85vh", display: "flex", flexDirection: "column",
background: "#fff", borderRadius: "16px", overflow: "hidden",
boxShadow: "0 24px 60px -20px rgba(0,0,0,0.35)",
});
const head = document.createElement("div");
Object.assign(head.style, {
display: "flex", alignItems: "center", justifyContent: "space-between",
padding: "16px 24px", borderBottom: "1px solid #e2e8f0", flex: "none",
});
const headLeft = document.createElement("div");
Object.assign(headLeft.style, { display: "flex", alignItems: "center", gap: "6px", fontSize: "14px", fontWeight: "600", color: "#0f172a" });
headLeft.innerHTML = '<span class="material-symbols-outlined" style="font-size:18px;line-height:1">edit_note</span><span></span>';
headLeft.lastChild.textContent = title || "编辑内容";
const closeBtn = document.createElement("button");
closeBtn.type = "button";
closeBtn.setAttribute("aria-label", "关闭");
Object.assign(closeBtn.style, { padding: "8px", color: "#94a3b8", background: "transparent", border: "0", cursor: "pointer" });
closeBtn.innerHTML = '<span class="material-symbols-outlined">close</span>';
head.appendChild(headLeft);
head.appendChild(closeBtn);
const body = document.createElement("div");
Object.assign(body.style, { flex: "1", minHeight: "0", padding: "24px" });
const textarea = document.createElement("textarea");
Object.assign(textarea.style, {
width: "100%", height: "100%", boxSizing: "border-box", padding: "12px 16px",
fontSize: "14px", lineHeight: "1.7", color: "#1f2937", background: "#f8fafc",
border: "1px solid #e2e8f0", borderRadius: "12px", resize: "none", outline: "none",
});
const current = pending.__textarea ? pending.__textarea.value : (pending.draft || "");
textarea.value = current;
body.appendChild(textarea);
const foot = document.createElement("div");
Object.assign(foot.style, {
display: "flex", alignItems: "center", justifyContent: "flex-end", gap: "12px",
padding: "16px 24px", borderTop: "1px solid #e2e8f0", flex: "none",
});
const cancel = document.createElement("button");
cancel.type = "button";
cancel.textContent = "取消";
Object.assign(cancel.style, {
padding: "10px 20px", fontSize: "14px", fontWeight: "600", color: "#475569",
background: "#f1f5f9", border: "0", borderRadius: "12px", cursor: "pointer",
});
const done = document.createElement("button");
done.type = "button";
done.textContent = "完成";
Object.assign(done.style, {
padding: "10px 20px", fontSize: "14px", fontWeight: "600", color: "#fff",
background: "#0f172a", border: "0", borderRadius: "12px", cursor: "pointer",
});
const close = function () {
document.removeEventListener("keydown", onKey);
overlay.remove();
};
const onKey = function (event) {
if (event.key === "Escape") close();
};
cancel.addEventListener("click", close);
closeBtn.addEventListener("click", close);
overlay.addEventListener("click", function (event) {
if (event.target === overlay) close();
});
done.addEventListener("click", function () {
const value = textarea.value;
pending.draft = value;
if (pending.__textarea) pending.__textarea.value = value;
close();
});
document.addEventListener("keydown", onKey);
foot.appendChild(cancel);
foot.appendChild(done);
panel.appendChild(head);
panel.appendChild(body);
panel.appendChild(foot);
overlay.appendChild(panel);
document.body.appendChild(overlay);
textarea.focus();
}
async function submitContentConfirm(session, approved, content) {
const pending = session.contentConfirm;
if (!pending || pending.submitting) return;
pending.submitting = true;
pending.error = "";
if (currentSessionId === session.id) renderThread(session);
const body = { approved: approved, content: content || "" };
const uid = localUid();
const teamId = currentTeamId();
if (uid) body.uid = uid;
if (teamId) body.team_id = teamId;
try {
const response = await fetch(
SKILL_API + "/sessions/" + encodeURIComponent(pending.sessionId) + "/tool-confirmations/" + encodeURIComponent(pending.toolCallId),
{ method: "POST", headers: authHeaders(true), body: JSON.stringify(body) },
);
const data = await response.json().catch(function () { return {}; });
if (!response.ok) throw new Error(data.detail || data.message || "HTTP " + response.status);
if (data.ret !== undefined && data.ret !== 0) throw new Error(data.msg || "确认失败");
window.setTimeout(function () {
if (session.contentConfirm && session.contentConfirm.toolCallId === pending.toolCallId) {
session.contentConfirm = null;
if (currentSessionId === session.id) renderThread(session);
}
}, 2000);
} catch (error) {
pending.submitting = false;
pending.error = "确认失败:" + (error instanceof Error ? error.message : String(error));
if (currentSessionId === session.id) renderThread(session);
}
}
const TURN_FIELDS = [
"prompt", "attachments", "referenceImages", "imageUrl", "skillKey", "skillName",
"status", "answer", "thinking", "toolSteps", "pendingConfirm", "contentConfirm",
"media", "error",
];
function defineTurnView(session) {
TURN_FIELDS.forEach(function (field) {
Object.defineProperty(session, field, {
configurable: true,
get: function () {
const turn = session.turns[session.turns.length - 1];
return turn ? turn[field] : undefined;
},
set: function (value) {
const turn = session.turns[session.turns.length - 1];
if (turn) turn[field] = value;
},
});
});
}
function createTurn(fields) {
return Object.assign({
prompt: "",
attachments: [],
referenceImages: [],
imageUrl: "",
skillKey: "",
skillName: "",
status: "loading",
answer: "",
thinking: "",
toolSteps: [],
pendingConfirm: null,
contentConfirm: null,
media: [],
error: "",
createdAt: Date.now(),
}, fields || {});
}
function renderThread(session) {
empty.classList.add("hidden");
main.classList.remove("is-empty");
thread.classList.remove("hidden");
thread.classList.add("pt-14");
thread.innerHTML = "";
const list = document.createElement("div");
list.setAttribute("data-v-f3be6059", "");
list.className = "space-y-4";
const turns = Array.isArray(session.turns) ? session.turns : [];
turns.forEach(function (turn, index) {
const isLast = index === turns.length - 1;
list.appendChild(makeUserBubble(turn));
const time = document.createElement("div");
time.className = "text-left text-[11px] text-gray-400";
time.textContent = formatClock(turn.createdAt);
list.appendChild(time);
const thinking = makeThinkingPanel(turn);
if (thinking) list.appendChild(thinking);
const steps = makeToolSteps(turn);
if (steps) list.appendChild(steps);
const hasOutput = !!(turn.answer || (Array.isArray(turn.media) && turn.media.length));
if (hasOutput) list.appendChild(makeResult(turn));
if (!isLast) {
if (turn.status === "error") list.appendChild(makeError(turn.error));
return;
}
if (turn.status === "error") {
list.appendChild(makeError(turn.error));
} else if (turn.status === "done") {
if (!hasOutput) list.appendChild(makeResult(turn));
} else if (turn.pendingConfirm) {
list.appendChild(makeToolConfirmCard(session));
} else if (turn.contentConfirm) {
list.appendChild(makeContentConfirmCard(session));
} else {
list.appendChild(makeLoader());
}
});
thread.appendChild(list);
if (chatScroll) chatScroll.scrollTop = chatScroll.scrollHeight;
syncComposerVisibility();
syncSend();
}
function syncComposerVisibility() {
if (!composerShell) return;
const session = sessions.find(function (item) { return item.id === currentSessionId; });
const turns = session && Array.isArray(session.turns) ? session.turns : [];
const last = turns[turns.length - 1];
const waiting = !!(last && last.status === "loading" && (last.contentConfirm || last.pendingConfirm));
// 等待用户确认时只保留确认卡(选项或输入框),不显示底部聊天输入框
composerShell.style.display = waiting ? "none" : "";
}
function capTail(text, max) {
const value = String(text || "");
return value.length <= max ? value : value.slice(-max);
}
function makeThinkingPanel(session) {
const text = String(session.thinking || "");
if (!text) return null;
const loading = session.status === "loading";
const card = document.createElement("div");
card.className = "rounded-lg border border-gray-200 bg-white overflow-hidden";
const head = document.createElement("div");
head.className = "w-full flex items-center gap-1.5 px-3 py-1.5 text-xs text-gray-400 font-medium";
const label = document.createElement("span");
label.className = "flex items-center gap-1.5";
label.innerHTML =
'<span class="material-symbols-outlined text-gray-400" style="font-size:20px;line-height:1">lightbulb</span>' +
'<span>思考过程</span>';
head.appendChild(label);
if (loading) {
const spinner = document.createElement("span");
spinner.className = "material-symbols-outlined text-[13px] animate-spin text-gray-400";
spinner.textContent = "progress_activity";
head.appendChild(spinner);
}
card.appendChild(head);
const body = document.createElement("div");
body.className =
"px-3 py-2 text-xs text-gray-400 leading-relaxed whitespace-pre-wrap border-t border-gray-100 max-h-56 overflow-y-auto";
const shown = text.length > 8000 ? "…(仅显示最近 8000 字)\n" + text.slice(-8000) : text;
body.textContent = shown;
card.appendChild(body);
requestAnimationFrame(function () {
body.scrollTop = body.scrollHeight;
});
return card;
}
function formatClock(ts) {
if (!ts) return "";
const date = new Date(ts);
const pad = function (value) { return String(value).padStart(2, "0"); };
return pad(date.getHours()) + ":" + pad(date.getMinutes());
}
function toolLabel(name) {
const map = {
quote_image: "预估费用",
generate_image: "生成图片",
generate_images_batch: "批量生成套图",
read: "读取文件",
ls: "浏览目录",
find: "查找文件",
list_skills: "列出技能",
read_skill: "读取技能",
read_document: "读取行程文档",
read_document_text: "读取文档内容",
confirm_user: "请求用户确认",
confirm_travel_content: "确认行程内容",
confirm_travel_style: "选择海报风格",
};
return map[name] || name || "工具";
}
function makeToolSteps(session) {
const steps = Array.isArray(session.toolSteps) ? session.toolSteps : [];
if (!steps.length) return null;
const wrap = document.createElement("div");
wrap.className = "flex flex-col gap-1.5 py-1";
steps.forEach(function (step) {
const row = document.createElement("div");
row.className = "flex items-center gap-1.5 text-xs";
if (step.status === "running") {
row.innerHTML =
'<span class="material-symbols-outlined text-[15px] text-gray-500 animate-spin">progress_activity</span>' +
'<span class="text-gray-500"></span>';
row.lastChild.textContent = "正在调用 " + toolLabel(step.name) + "…";
} else if (step.status === "error") {
row.innerHTML =
'<span class="material-symbols-outlined text-[15px] text-red-500">error</span>' +
'<span class="text-red-500"></span>';
row.lastChild.textContent = toolLabel(step.name) + " 调用失败";
} else {
row.innerHTML =
'<span class="material-symbols-outlined text-[15px] text-green-500">check_circle</span>' +
'<span class="text-gray-400"></span>';
row.lastChild.textContent = "已调用 " + toolLabel(step.name);
}
wrap.appendChild(row);
});
return wrap;
}
function makeToolConfirmCard(session) {
const pending = session.pendingConfirm;
const card = document.createElement("div");
card.className = "rounded-xl border border-amber-300 bg-amber-50 p-3 scroll-mt-20";
const head = document.createElement("div");
head.className = "flex items-center gap-1.5 text-xs font-medium text-amber-800 mb-1";
head.innerHTML = '<span class="material-symbols-outlined text-[16px]">help</span><span></span>';
head.lastChild.textContent = "智能体准备调用「" + toolLabel(pending.toolName) + "」,是否确认执行?";
card.appendChild(head);
const args = pending.args && typeof pending.args === "object" ? pending.args : null;
const quote = pending.quote && typeof pending.quote === "object" ? pending.quote : null;
const detail = document.createElement("div");
detail.className = "space-y-1 rounded-lg bg-white/70 px-2.5 py-2 mb-2 text-[11px] leading-relaxed text-amber-800";
const addLine = function (label, value) {
if (!value) return;
const line = document.createElement("div");
const name = document.createElement("span");
name.className = "text-amber-700/80";
name.textContent = label + ":";
const val = document.createElement("span");
val.className = "font-medium";
val.textContent = value;
line.appendChild(name);
line.appendChild(val);
detail.appendChild(line);
};
if (quote) {
addLine("技能", quote.skill_name || (args && args.skill_id) || "");
const size = quote.size || (args && args.size) || "";
const quantity = quote.quantity || (args && args.n) || 1;
addLine("规格", (size ? size + " · " : "") + quantity + " 张");
// 积分与计费不再在前端展示,由后端按技能库单价自动扣减
} else if (args) {
addLine("技能", args.skill_id || "");
addLine("规格", [args.size, args.n ? args.n + " 张" : ""].filter(Boolean).join(" · "));
if (args.message) addLine("说明", String(args.message).slice(0, 200));
}
if (detail.childNodes.length) card.appendChild(detail);
if (args && args.prompt) {
const promptBox = document.createElement("details");
promptBox.className = "mb-2 rounded-lg bg-white/70 px-2.5 py-1.5 text-[11px] text-amber-800";
const summaryEl = document.createElement("summary");
summaryEl.className = "cursor-pointer select-none text-amber-700";
summaryEl.textContent = "查看生图提示词";
const promptText = document.createElement("div");
promptText.className = "mt-1 max-h-24 overflow-y-auto whitespace-pre-wrap leading-relaxed text-amber-700/90";
promptText.textContent = String(args.prompt);
promptBox.appendChild(summaryEl);
promptBox.appendChild(promptText);
card.appendChild(promptBox);
}
if (pending.error) {
const error = document.createElement("div");
error.className = "text-[11px] text-red-500 whitespace-pre-wrap mb-2";
error.textContent = pending.error;
card.appendChild(error);
}
const actions = document.createElement("div");
actions.className = "flex gap-2";
const ok = document.createElement("button");
ok.type = "button";
ok.className = "px-3 py-1.5 rounded-lg text-xs font-bold text-white bg-amber-500 hover:bg-amber-600 transition-colors cursor-pointer disabled:bg-gray-300 disabled:cursor-not-allowed";
ok.textContent = pending.submitting ? "处理中…" : "确认执行";
ok.disabled = !!pending.submitting;
ok.addEventListener("click", function () { sendToolConfirmation(session, true); });
const cancel = document.createElement("button");
cancel.type = "button";
cancel.className = "px-3 py-1.5 rounded-lg text-xs font-medium text-gray-600 bg-white border border-gray-200 hover:bg-gray-50 transition-colors cursor-pointer disabled:opacity-60";
cancel.textContent = "取消";
cancel.disabled = !!pending.submitting;
cancel.addEventListener("click", function () { sendToolConfirmation(session, false); });
actions.appendChild(ok);
actions.appendChild(cancel);
card.appendChild(actions);
return card;
}
function scheduleThreadRender(session) {
if (currentSessionId !== session.id || renderTimer) return;
renderTimer = window.setTimeout(function () {
renderTimer = 0;
renderThread(session);
}, 100);
}
async function selectSession(session) {
if (sidebar.classList.contains("w-20")) {
setCollapsed(false);
return;
}
if (session.remote && !session.loaded) {
const ok = await loadRemoteMessages(session);
if (!ok) return;
}
currentSessionId = session.id;
persistCurrentSession(session.agentId || (session.remote ? session.id : ""));
stopRemotePolling();
setActive("recent-" + session.id);
input.textContent = "";
placeholder.classList.remove("hidden");
clearAttachments();
syncSend();
renderThread(session);
startRemotePolling(session);
}
function startRemotePolling(session) {
stopRemotePolling();
if (!session || !session.remote || !session.loaded) return;
const lastTurn = function () {
return session.turns && session.turns.length ? session.turns[session.turns.length - 1] : null;
};
const turn = lastTurn();
if (!turn || turn.status !== "loading") return;
remotePollTimer = window.setInterval(async function () {
if (currentSessionId !== session.id) {
stopRemotePolling();
return;
}
const ok = await loadRemoteMessages(session);
if (ok && currentSessionId === session.id) {
renderThread(session);
syncSend();
}
const current = lastTurn();
if (!current || current.status !== "loading") stopRemotePolling();
}, 2000);
}
async function loadRemoteMessages(session) {
try {
const response = await fetch(SKILL_API + "/sessions/" + encodeURIComponent(session.id) + "/messages", {
headers: authHeaders(false),
});
if (!response.ok) throw new Error("HTTP " + response.status);
const data = await response.json();
const messages = Array.isArray(data.messages) ? data.messages : [];
applyRemoteMessages(session, messages);
return true;
} catch (error) {
console.warn("会话消息加载失败", error);
return false;
}
}
function applyRemoteMessages(session, messages) {
const turns = [];
let active = null;
messages.forEach(function (message) {
const ts = message.add_time ? message.add_time * 1000 : Date.now();
if (message.role === "user") {
const attachments = (Array.isArray(message.media) ? message.media : [])
.map(function (item) {
if (typeof item === "string") return { url: item, type: "image", name: "参考图" };
if (!item || !item.url) return null;
return { url: item.url, type: item.type || "image", name: item.name || "参考图" };
})
.filter(Boolean);
active = createTurn({
prompt: message.content || "",
createdAt: ts,
status: "done",
attachments: attachments,
});
turns.push(active);
return;
}
if (!active) {
active = createTurn({ prompt: session.title, createdAt: ts, status: "done" });
turns.push(active);
}
if (message.content) {
active.answer = active.answer ? active.answer + "\n\n" + message.content : message.content;
}
if (message.thinking) {
active.thinking = active.thinking ? active.thinking + "\n\n" + message.thinking : message.thinking;
}
(message.media || []).forEach(function (item) {
const url = typeof item === "string" ? item : item && item.url;
if (url) active.media.push(url);
});
if (Array.isArray(message.tool_steps)) {
message.tool_steps.forEach(function (step) {
if (step && step.name) active.toolSteps.push({ name: step.name, status: step.status || "done" });
});
}
if (message.status === "loading") {
active.status = "loading";
} else if (message.status === "error" && message.error) {
active.status = "error";
active.error = message.error;
}
active.createdAt = active.createdAt || ts;
});
if (!turns.length) {
turns.push(createTurn({ prompt: session.title, status: "done", createdAt: session.createdAt }));
}
session.turns = turns;
session.createdAt = turns[0].createdAt || session.createdAt;
session.loaded = true;
defineTurnView(session);
}
async function loadRemoteSessions() {
const token = localStorage.getItem("access_token");
if (!token) return;
const params = new URLSearchParams({ limit: "50" });
const uid = localUid();
const teamId = currentTeamId();
if (uid) params.set("uid", uid);
if (teamId) params.set("team_id", teamId);
try {
const response = await fetch(SKILL_API + "/sessions?" + params.toString(), { headers: authHeaders(false) });
if (!response.ok) return;
const data = await response.json();
const list = Array.isArray(data.sessions) ? data.sessions : [];
list.forEach(function (meta) {
const id = meta.session_id;
if (!id) return;
if (sessions.some(function (item) { return item.id === id; })) return;
const remoteSession = {
id: id,
agentId: id,
remote: true,
loaded: false,
title: cleanSessionTitle(meta.title),
createdAt: (meta.update_time || meta.add_time || 0) * 1000,
turns: [],
};
defineTurnView(remoteSession);
sessions.push(remoteSession);
});
sessions.sort(function (a, b) { return (b.createdAt || 0) - (a.createdAt || 0); });
renderRecent();
} catch (error) {
console.warn("会话列表加载失败", error);
}
}
function clearConversation() {
if (busy) return;
stopRemotePolling();
persistCurrentSession("");
currentSessionId = null;
setActive("new-chat");
thread.classList.add("hidden");
thread.classList.remove("pt-14");
thread.innerHTML = "";
empty.classList.remove("hidden");
main.classList.add("is-empty");
input.textContent = "";
clearAttachments();
applySkillPrompt();
if (chatScroll) chatScroll.scrollTop = 0;
input.focus();
}
function handleNewChat() {
if (sidebar.classList.contains("w-20")) {
setCollapsed(false);
return;
}
clearConversation();
}
function currentText() {
return (input.innerText || "").replace(/\u00a0/g, " ").trim();
}
function syncSend() {
const activeSession = sessions.find(function (item) { return item.id === currentSessionId; });
const activeTurns = activeSession && Array.isArray(activeSession.turns) ? activeSession.turns : [];
const lastActiveTurn = activeTurns[activeTurns.length - 1];
const remoteLoading = !!(lastActiveTurn && lastActiveTurn.status === "loading");
const ready = (currentText().length > 0 || attachments.length > 0 || !!defaultPromptFor(currentSkill())) && !busy && !remoteLoading;
send.disabled = !ready;
send.classList.toggle("bg-gray-400", !ready);
send.classList.toggle("cursor-not-allowed", !ready);
send.classList.toggle("bg-gray-900", ready);
send.classList.toggle("hover:bg-black", ready);
send.classList.toggle("hover:shadow-md", ready);
send.classList.toggle("active:scale-95", ready);
send.classList.toggle("cursor-pointer", ready);
placeholder.hidden = currentText().length > 0;
}
function revokeAttachment(item) {
if (item && item.url && item.url.indexOf("blob:") === 0) URL.revokeObjectURL(item.url);
}
function hideAttachmentHover() {
if (!attachmentHoverPreview) return;
attachmentHoverPreview.classList.add("hidden");
if (attachmentHoverImage) attachmentHoverImage.removeAttribute("src");
}
function showAttachmentHover(url, target) {
if (!attachmentHoverPreview || !attachmentHoverImage || !url) return;
const rect = target.getBoundingClientRect();
attachmentHoverImage.src = url;
attachmentHoverPreview.style.left = (rect.left + rect.width / 2) + "px";
attachmentHoverPreview.style.bottom = (window.innerHeight - rect.top + 8) + "px";
attachmentHoverPreview.style.transform = "translateX(-50%)";
attachmentHoverPreview.classList.remove("hidden");
}
function renderAttachments() {
hideAttachmentHover();
if (previewRow) {
previewRow.remove();
previewRow = null;
}
if (!attachments.length) {
fileInput.value = "";
return;
}
const row = document.createElement("div");
row.setAttribute("data-v-f3be6059", "");
row.className = "flex gap-2 overflow-x-auto pb-1";
attachments.forEach(function (item, index) {
const box = document.createElement("div");
box.setAttribute("data-v-f3be6059", "");
box.className = item.type === "other"
? "relative shrink-0 rounded-xl overflow-hidden border border-gray-200 w-auto max-w-[160px] h-14 bg-white px-2.5"
: "relative shrink-0 rounded-xl overflow-hidden border border-gray-200 w-14 h-14";
if (item.type === "image") {
const img = document.createElement("img");
img.src = item.url;
img.alt = item.name || "附件";
img.referrerPolicy = "no-referrer";
img.className = "w-full h-full object-cover";
box.appendChild(img);
box.addEventListener("mouseenter", function () { showAttachmentHover(item.url, box); });
box.addEventListener("mouseleave", hideAttachmentHover);
} else if (item.type === "video") {
const video = document.createElement("video");
video.src = item.url;
video.className = "w-full h-full object-cover";
video.muted = true;
video.disablePictureInPicture = true;
box.appendChild(video);
} else if (item.type === "audio") {
const audio = document.createElement("div");
audio.className = "w-full h-full flex items-center justify-center bg-gray-100 text-gray-500";
audio.innerHTML = '<span class="material-symbols-outlined text-base">music_note</span>';
box.appendChild(audio);
} else {
const file = document.createElement("div");
file.className = "h-full flex items-center gap-1.5";
file.innerHTML = '<span class="material-symbols-outlined text-base text-gray-400">description</span><span class="max-w-[120px] truncate text-xs text-gray-600"></span>';
file.querySelector("span:last-child").textContent = item.name || "文件";
box.appendChild(file);
}
const remove = document.createElement("button");
remove.type = "button";
remove.setAttribute("aria-label", "移除附件");
remove.className = "absolute top-0.5 right-0.5 w-4 h-4 flex items-center justify-center bg-black/50 text-white rounded-full cursor-pointer";
remove.innerHTML = '<span class="text-[10px] leading-none">×</span>';
remove.addEventListener("click", function () { removeAttachment(index); });
box.appendChild(remove);
row.appendChild(box);
});
composerBody.insertBefore(row, textRow);
previewRow = row;
}
function addAttachment(item) {
if (!item || !item.url) return;
attachments.push({
name: item.name || "附件",
url: item.url,
type: item.type || "image",
file: item.file || null,
});
renderAttachments();
syncSend();
}
function removeAttachment(index) {
const item = attachments[index];
if (!item) return;
hideAttachmentHover();
revokeAttachment(item);
attachments.splice(index, 1);
renderAttachments();
syncSend();
}
function clearAttachments(revoke) {
hideAttachmentHover();
attachments.forEach(function (item) {
if (revoke !== false) revokeAttachment(item);
});
attachments = [];
renderAttachments();
syncSend();
}
function normalizeMediaUrl(url) {
return String(url || "").trim().replace(/[),.;]+$/, "").split("#")[0].split("?")[0];
}
function mergeMedia(list, urls) {
const result = [];
const seen = Object.create(null);
(Array.isArray(list) ? list : []).forEach(function (item) {
const url = typeof item === "string" ? item : item && item.url;
const key = normalizeMediaUrl(url);
if (!key || seen[key]) return;
seen[key] = true;
result.push(url);
});
(urls || []).forEach(function (item) {
const url = typeof item === "string" ? item : item && item.url;
const key = normalizeMediaUrl(url);
if (!key || seen[key]) return;
seen[key] = true;
result.push(url);
});
return result;
}
function isImageUrl(url) {
return /\.(png|jpe?g|gif|webp|bmp|svg)(\?|#|$)/i.test(url) || /\/model\/image\//i.test(url);
}
function stripMediaFromAnswer(answer, media) {
let text = String(answer || "");
if (!text) return text;
const keys = (media || []).map(function (url) { return normalizeMediaUrl(url); });
const shouldStrip = function (url) {
return keys.indexOf(normalizeMediaUrl(url)) !== -1 || isImageUrl(url);
};
text = text.replace(/!\[[^\]]*\]\(([^)\s]+)[^)]*\)/g, function (full, url) {
return shouldStrip(url) ? "" : full;
});
text = text.replace(/\[[^\]]*\]\(([^)\s]+)[^)]*\)/g, function (full, url) {
return shouldStrip(url) ? "" : full;
});
text = text.replace(/https?:\/\/[^\s)<>"']+/g, function (url) {
return shouldStrip(url) ? "" : url;
});
text = text.replace(/^[ \t]*(?:👉|➡️|→|▶)[ \t]*$/gm, "");
return text.replace(/\n{3,}/g, "\n\n").replace(/^[ \t]+$/gm, "").trim();
}
async function sendToolConfirmation(session, approved) {
const pending = session.pendingConfirm;
if (!pending || pending.submitting) return;
pending.submitting = true;
pending.error = "";
if (currentSessionId === session.id) renderThread(session);
const body = { approved: approved };
const uid = localUid();
const teamId = currentTeamId();
if (uid) body.uid = uid;
if (teamId) body.team_id = teamId;
try {
const response = await fetch(
SKILL_API + "/sessions/" + encodeURIComponent(pending.sessionId) + "/tool-confirmations/" + encodeURIComponent(pending.toolCallId),
{ method: "POST", headers: authHeaders(true), body: JSON.stringify(body) },
);
const data = await response.json().catch(function () { return {}; });
if (!response.ok) throw new Error(data.detail || data.message || "HTTP " + response.status);
if (!approved) {
session.pendingConfirm = null;
session.status = "done";
}
} catch (error) {
pending.submitting = false;
pending.error = "确认失败:" + (error instanceof Error ? error.message : String(error));
}
if (currentSessionId === session.id) renderThread(session);
}
async function handleStreamEvent(event, session) {
if (!event || !event.type) return;
if (event.type === "session") {
if (event.session_id) {
session.agentId = event.session_id;
persistCurrentSession(event.session_id);
}
return;
}
if (event.type === "thinking_delta") {
session.thinking = capTail((session.thinking || "") + (event.text || ""), 20000);
scheduleThreadRender(session);
return;
}
if (event.type === "text_delta") {
session.answer = (session.answer || "") + (event.text || "");
scheduleThreadRender(session);
return;
}
if (event.type === "images") {
session.media = mergeMedia(session.media, event.images);
scheduleThreadRender(session);
refreshHeaderScoreAfterGeneration(event.quote ? event.quote.total_credits : null);
return;
}
if (event.type === "tool_start") {
if (!Array.isArray(session.toolSteps)) session.toolSteps = [];
session.toolSteps.push({
name: event.tool_name,
callId: event.tool_call_id,
status: "running",
});
scheduleThreadRender(session);
return;
}
if (event.type === "tool_end") {
if (!Array.isArray(session.toolSteps)) session.toolSteps = [];
const step = session.toolSteps.filter(function (item) { return item.callId === event.tool_call_id; }).pop();
if (step) {
step.status = event.ok ? "done" : "error";
} else {
session.toolSteps.push({ name: event.tool_name, callId: event.tool_call_id, status: event.ok ? "done" : "error" });
}
scheduleThreadRender(session);
return;
}
if (event.type === "tool_confirmation_required") {
session.pendingConfirm = {
sessionId: event.session_id,
toolCallId: event.tool_call_id || event.confirmation_id,
toolName: event.tool_name,
args: event.args || null,
quote: event.quote || null,
submitting: false,
error: "",
};
if (currentSessionId === session.id) {
if (renderTimer) { window.clearTimeout(renderTimer); renderTimer = 0; }
renderThread(session);
}
return;
}
if (event.type === "tool_confirmation_resolved") {
if (session.pendingConfirm && session.pendingConfirm.toolCallId === event.tool_call_id) {
session.pendingConfirm = null;
}
scheduleThreadRender(session);
return;
}
if (event.type === "content_confirmation_required") {
const payload = event.payload || {};
session.contentConfirm = {
sessionId: event.session_id,
toolCallId: event.tool_call_id || event.confirmation_id,
confirmType: event.confirm_type,
payload: payload,
submitting: false,
error: "",
draft: typeof payload.default_value === "string" ? cleanConfirmDraft(payload.default_value) : undefined,
selectedId: "",
};
if (currentSessionId === session.id) {
if (renderTimer) { window.clearTimeout(renderTimer); renderTimer = 0; }
renderThread(session);
}
return;
}
if (event.type === "content_confirmation_resolved") {
const pending = session.contentConfirm;
if (pending && pending.toolCallId === event.tool_call_id) {
if (event.confirm_type === "travel_style_confirm" && event.content) {
const styles = pending.payload && Array.isArray(pending.payload.styles) ? pending.payload.styles : [];
const style = styles.find(function (item) { return item && item.id === event.content; });
if (style) session.skillName = "旅游 · " + (style.label || style.id);
}
session.contentConfirm = null;
}
if (currentSessionId === session.id) {
if (renderTimer) { window.clearTimeout(renderTimer); renderTimer = 0; }
renderThread(session);
}
return;
}
if (event.type === "done") {
if (event.session_id && !session.agentId) session.agentId = event.session_id;
session.contentConfirm = null;
session.pendingConfirm = null;
if (event.answer) session.answer = event.answer;
if (Array.isArray(event.media)) session.media = mergeMedia([], event.media);
session.status = "done";
if (currentSessionId === session.id) {
if (renderTimer) { window.clearTimeout(renderTimer); renderTimer = 0; }
renderThread(session);
}
return;
}
if (event.type === "error") {
session.status = "error";
session.contentConfirm = null;
session.error = event.message || "生成失败,请稍后重试";
if (currentSessionId === session.id) {
if (renderTimer) { window.clearTimeout(renderTimer); renderTimer = 0; }
renderThread(session);
}
}
}
async function consumeStream(response, session) {
if (!response.body) throw new Error("浏览器不支持流式响应");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
try {
while (true) {
const chunk = await reader.read();
if (chunk.done) break;
buffer += decoder.decode(chunk.value, { stream: true }).replace(/\r\n/g, "\n");
let split = buffer.indexOf("\n\n");
while (split !== -1) {
const raw = buffer.slice(0, split);
buffer = buffer.slice(split + 2);
const lines = raw.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.indexOf("data:") !== 0) continue;
const payload = line.slice(5).trim();
if (!payload) continue;
let event;
try { event = JSON.parse(payload); } catch (error) { continue; }
await handleStreamEvent(event, session);
}
split = buffer.indexOf("\n\n");
}
}
} finally {
if (reader.releaseLock) reader.releaseLock();
}
}
async function submitPrompt() {
if (busy) return;
const typed = currentText();
const text = typed || defaultPromptFor(currentSkill());
if (!text && !attachments.length) return;
const skill = selectedSkillKey
? skills.find(function (item) { return item.key === selectedSkillKey; })
: null;
const industry = skill ? industryOf(skill) : (selectedIndustry || "");
const referenceImages = attachments
.filter(function (item) { return item.type === "image" && /^https?:\/\//.test(item.url); })
.map(function (item) { return item.url; })
.slice(0, 6);
const sessionAttachments = attachments.map(function (item) {
return { name: item.name || "附件", url: item.url, type: item.type || "image" };
});
const documentAttachments = attachments
.filter(function (item) { return item.type === "other" && /^https?:\/\//.test(item.url); })
.map(function (item) { return (item.name ? item.name + ":" : "") + item.url; });
busy = true;
syncSend();
const existing = sessions.find(function (item) { return item.id === currentSessionId; });
const continuing = !!(existing && existing.agentId);
const session = continuing ? existing : {
id: ++seq,
title: (text || "图片生成").slice(0, 18),
agentId: "",
remote: false,
loaded: true,
createdAt: Date.now(),
turns: [],
};
const turn = createTurn({
prompt: text || "根据这张产品图生成行业海报",
attachments: sessionAttachments,
referenceImages: referenceImages,
imageUrl: referenceImages[0] || "",
skillKey: selectedSkillKey,
skillName: skill ? (industry ? industry + " · " : "") + skillLabelFor(skill) : "直接生成",
createdAt: Date.now(),
status: "loading",
});
session.turns.push(turn);
if (!continuing) {
defineTurnView(session);
sessions.unshift(session);
}
currentSessionId = session.id;
setActive("recent-" + session.id);
renderThread(session);
input.textContent = "";
placeholder.classList.remove("hidden");
clearAttachments(false);
syncSend();
let message = text || defaultPromptFor(currentSkill()) || "根据这张产品图生成行业海报";
if (documentAttachments.length) {
message += "\n\n【用户上传了以下文档(请先用 read_document_text 逐个读取原文;如果是旅游行程文档,可再用 read_document 提取线路摘要):\n"
+ documentAttachments.join("\n") + "】";
}
const body = { message: message, stream: true, title: String(session.title || "新对话").slice(0, 40) };
if (session.skillKey) body.skill_id = session.skillKey;
else body.direct = true;
if (industry) body.industry = industry;
if (referenceImages.length) body.reference_images = referenceImages;
const uid = localUid();
const teamId = currentTeamId();
if (uid) body.uid = uid;
if (teamId) body.team_id = teamId;
if (!session.agentId) {
try {
const createResponse = await fetch(SKILL_API + "/sessions", {
method: "POST",
headers: authHeaders(true),
body: JSON.stringify({
title: String(session.title || "新对话").slice(0, 40),
uid: uid || undefined,
team_id: teamId || undefined,
}),
});
if (createResponse.ok) {
const created = await createResponse.json().catch(function () { return {}; });
if (created && created.session_id) {
session.agentId = created.session_id;
persistCurrentSession(created.session_id);
}
}
} catch (error) {
console.warn("创建会话失败,回退到 agent/run", error);
}
}
if (session.agentId) persistCurrentSession(session.agentId);
const endpoint = session.agentId
? SKILL_API + "/sessions/" + encodeURIComponent(session.agentId) + "/messages"
: SKILL_API + "/agent/run";
try {
const response = await fetch(endpoint, {
method: "POST",
headers: Object.assign({ Accept: "text/event-stream" }, authHeaders(true)),
body: JSON.stringify(body),
});
if (!response.ok) {
const data = await response.json().catch(function () { return {}; });
throw new Error(data.detail || data.message || "HTTP " + response.status);
}
await consumeStream(response, session);
if (turn.status === "loading") turn.status = "done";
} catch (error) {
turn.status = "error";
turn.error = error instanceof Error ? error.message : String(error);
}
busy = false;
syncSend();
if (renderTimer) { window.clearTimeout(renderTimer); renderTimer = 0; }
if (currentSessionId === session.id) renderThread(session);
}
function tokenUid() {
const token = localStorage.getItem("access_token") || "";
const part = token.split(".")[1];
if (!part) return "";
try {
const json = atob(part.replace(/-/g, "+").replace(/_/g, "/"));
const match = json.match(/"uid"\s*:\s*"?([0-9]+)"?/);
return match ? match[1] : "";
} catch (error) {
return "";
}
}
function localUid() {
const fromToken = tokenUid();
if (fromToken) return fromToken;
try {
const raw = localStorage.getItem("user_info");
if (!raw) return "";
const info = JSON.parse(raw);
return info && info.uid ? String(info.uid) : "";
} catch (error) {
return "";
}
}
function readStoredUserInfo() {
try {
return JSON.parse(localStorage.getItem("user_info") || "{}") || {};
} catch (error) {
return {};
}
}
function setUserAvatar(el, url) {
if (!el) return;
el.textContent = "";
if (url) {
const img = document.createElement("img");
img.src = url;
img.alt = "";
img.className = "w-full h-full object-cover";
img.addEventListener("error", function () { setUserAvatar(el, ""); });
el.appendChild(img);
return;
}
const icon = document.createElement("span");
icon.className = "material-symbols-outlined text-gray-400";
icon.textContent = "person";
el.appendChild(icon);
}
function renderUserInfo(info) {
const user = info || readStoredUserInfo();
const nickname = String(user.nickname || "").trim() || "来秒AI 创作者";
const avatar = String(user.avatar || "").trim();
setUserAvatar(userAvatar, avatar);
setUserAvatar(mUserAvatar, avatar);
if (userNickname) userNickname.textContent = nickname;
if (mUserNickname) mUserNickname.textContent = nickname;
}
async function loadUserInfo() {
renderUserInfo();
const token = localStorage.getItem("access_token");
if (!token) return;
try {
const response = await fetch(apiBase() + "/api/user/me", { headers: { Authorization: token } });
if (!response.ok) return;
const data = await response.json();
if (!data || (data.ret !== undefined && data.ret !== 0)) return;
delete data.access_token;
delete data.open_token;
const merged = Object.assign({}, readStoredUserInfo(), data);
localStorage.setItem("user_info", JSON.stringify(merged));
renderUserInfo(merged);
} catch (error) {
console.warn("用户信息加载失败", error);
}
}
function authHeaders(json) {
const headers = {};
if (json) headers["Content-Type"] = "application/json";
const token = localStorage.getItem("access_token");
if (token) headers.Authorization = "Bearer " + token;
const uid = localUid();
if (uid) headers["x-uid"] = uid;
const teamId = currentTeamId();
if (teamId) headers["x-team-id"] = teamId;
return headers;
}
function skillLabelFor(skill) {
return (skill && (skill.display_name || skill.name || skill.key)) || "";
}
function industryOf(skill) {
return skill && skill.industry ? skill.industry : "通用";
}
function skillsInIndustry() {
return skills.filter(function (skill) { return industryOf(skill) === selectedIndustry; });
}
function currentSkill() {
return skills.find(function (skill) { return skill.key === selectedSkillKey; }) || null;
}
function defaultPromptFor(skill) {
return skill && skill.default_input ? skill.default_input : "";
}
function syncPrefix() {
prefix.hidden = true;
input.style.textIndent = "0px";
placeholder.style.left = "0px";
}
function applySkillPrompt() {
const skill = currentSkill();
const text = currentText();
if (!skill) {
if (lastDefaultPrompt && text === lastDefaultPrompt) input.textContent = "";
lastDefaultPrompt = "";
prefix.hidden = true;
placeholder.textContent = "[上传产品图或描述行业需求]";
input.style.textIndent = "0px";
placeholder.style.left = "0px";
syncSend();
return;
}
const prompt = defaultPromptFor(skill);
if (!text || text === lastDefaultPrompt) input.textContent = prompt || "";
lastDefaultPrompt = prompt || "";
placeholder.textContent = "描述你的需求,或补充应用场景";
syncPrefix();
syncSend();
}
function updateSkillButton() {
if (!skillBtn || !skillLabel) return;
let label = "选择行业与技能";
if (skillLoading) label = "加载行业…";
else if (selectedSkillKey) {
const skill = currentSkill();
label = (selectedIndustry ? selectedIndustry + " · " : "") + (skill ? skillLabelFor(skill) : selectedSkillKey);
} else if (selectedIndustry) {
label = selectedIndustry + " · 选择技能";
} else if (skillError) {
label = "技能加载失败";
}
skillLabel.textContent = label;
skillBtn.title = selectedSkillKey ? label + " · " + selectedSkillKey : "选择行业与技能";
}
function emptyState(icon, text) {
const box = document.createElement("div");
box.className = "skill-menu__empty";
box.innerHTML = '<span class="material-symbols-outlined"></span><span></span>';
box.firstChild.textContent = icon;
box.lastChild.textContent = text;
return box;
}
function renderSkills() {
if (!skillList || !industryTabs) return;
skillList.innerHTML = "";
industryTabs.innerHTML = "";
if (skillLoading) {
skillList.appendChild(emptyState("progress_activity", "正在加载行业与技能…"));
syncSkillListScroll();
return;
}
if (skillError) {
const error = document.createElement("div");
error.className = "skill-menu__error";
error.textContent = skillError;
const retry = document.createElement("button");
retry.type = "button";
retry.className = "skill-menu__retry";
retry.textContent = "重试";
retry.addEventListener("click", function () { loadSkills(); });
error.appendChild(retry);
skillList.appendChild(error);
return;
}
if (!skills.length) {
skillList.appendChild(emptyState("extension_off", "暂无可用技能"));
return;
}
industries.forEach(function (industry) {
const chip = document.createElement("button");
chip.type = "button";
chip.className = "industry-chip" + (industry === selectedIndustry ? " is-active" : "");
chip.setAttribute("role", "tab");
chip.setAttribute("aria-selected", industry === selectedIndustry ? "true" : "false");
chip.textContent = industry;
chip.addEventListener("click", function () { selectIndustry(industry); });
industryTabs.appendChild(chip);
});
if (!selectedIndustry) {
skillList.appendChild(emptyState("ads_click", "先选择一个行业,再挑选技能"));
syncSkillListScroll();
return;
}
const group = skillsInIndustry();
if (!group.length) {
skillList.appendChild(emptyState("extension_off", "该行业暂无可用技能"));
return;
}
group.forEach(function (skill) {
const selected = skill.key === selectedSkillKey;
const option = document.createElement("button");
option.type = "button";
option.className = "skill-option" + (selected ? " is-active" : "");
option.setAttribute("role", "option");
option.setAttribute("aria-selected", selected ? "true" : "false");
option.dataset.key = skill.key;
option.title = skill.description || skill.key;
option.innerHTML =
'<span class="skill-option__icon"><span class="material-symbols-outlined">auto_awesome</span></span>' +
'<span class="skill-option__body"><span class="skill-option__name"></span><span class="skill-option__desc"></span></span>' +
'<span class="material-symbols-outlined skill-option__check">check</span>';
option.querySelector(".skill-option__name").textContent = skillLabelFor(skill);
option.querySelector(".skill-option__desc").textContent =
skill.description || [industryOf(skill), skill.key].filter(Boolean).join(" · ");
option.addEventListener("click", function () { selectSkill(skill); });
skillList.appendChild(option);
});
syncSkillListScroll();
}
function syncSkillListScroll() {
if (!skillListWrap || !skillList) return;
requestAnimationFrame(function () {
const scrollable = skillList.scrollHeight - skillList.clientHeight > 4;
const atBottom = skillList.scrollTop + skillList.clientHeight >= skillList.scrollHeight - 4;
skillListWrap.classList.toggle("is-scrollable", scrollable && !atBottom);
});
}
function selectIndustry(industry) {
selectedIndustry = industry;
const group = skills.filter(function (skill) { return industryOf(skill) === industry; });
if (!group.some(function (skill) { return skill.key === selectedSkillKey; })) {
const text = currentText();
if (text && text === lastDefaultPrompt) input.textContent = "";
selectedSkillKey = "";
lastDefaultPrompt = "";
}
renderSkills();
updateSkillButton();
applySkillPrompt();
}
function selectSkill(skill) {
selectedSkillKey = skill.key;
selectedIndustry = industryOf(skill);
renderSkills();
updateSkillButton();
applySkillPrompt();
closeSkillMenu();
}
function positionSkillMenu() {
if (!skillMenu || !skillBtn) return;
const card = skillBtn.closest(".rounded-2xl");
const cardRect = (card || skillBtn).getBoundingClientRect();
const btnRect = skillBtn.getBoundingClientRect();
const margin = 16;
const width = Math.min(460, window.innerWidth - margin * 2);
const maxLeft = Math.max(margin, window.innerWidth - width - margin);
const left = Math.min(Math.max(btnRect.left, margin), maxLeft);
const bottom = Math.max(margin, window.innerHeight - cardRect.top + 10);
skillMenu.style.width = width + "px";
skillMenu.style.left = left + "px";
skillMenu.style.bottom = bottom + "px";
}
function openSkillMenu() {
if (!skillMenu) return;
renderSkills();
positionSkillMenu();
skillMenu.hidden = false;
skillBtn.setAttribute("aria-expanded", "true");
main.classList.add("skill-menu-open");
}
function closeSkillMenu() {
if (!skillMenu) return;
skillMenu.hidden = true;
skillBtn.setAttribute("aria-expanded", "false");
main.classList.remove("skill-menu-open");
}
async function loadSkills() {
if (!skillBtn) return;
skillLoading = true;
skillError = "";
renderSkills();
updateSkillButton();
syncSend();
try {
const response = await fetch(apiBase() + "/api/skill/list");
const data = await response.json().catch(function () { return {}; });
if (!response.ok) {
throw new Error(data.msg || data.detail || data.message || "HTTP " + response.status);
}
if (data.ret !== undefined && data.ret !== 0) {
throw new Error(data.msg || "HTTP " + response.status);
}
skills = [];
industries = [];
const groups = Array.isArray(data.industries) ? data.industries : [];
groups.forEach(function (group) {
if (!group) return;
const industry = (group.industry || "").trim();
if (industry && industries.indexOf(industry) === -1) industries.push(industry);
(Array.isArray(group.skills) ? group.skills : []).forEach(function (item) {
if (item && item.key) skills.push(item);
});
});
if (!industries.length) {
skills.forEach(function (skill) {
const industry = industryOf(skill);
if (industries.indexOf(industry) === -1) industries.push(industry);
});
}
if (!skills.length) {
selectedIndustry = "";
selectedSkillKey = "";
skillError = "暂无可用技能";
} else {
if (selectedIndustry && industries.indexOf(selectedIndustry) === -1) {
selectedIndustry = "";
selectedSkillKey = "";
}
if (selectedSkillKey && !currentSkill()) selectedSkillKey = "";
if (selectedSkillKey) selectedIndustry = industryOf(currentSkill());
}
} catch (error) {
skills = [];
industries = [];
selectedIndustry = "";
selectedSkillKey = "";
skillError = "技能加载失败:" + (error instanceof Error ? error.message : String(error));
}
skillLoading = false;
renderSkills();
updateSkillButton();
applySkillDeepLink();
applySkillPrompt();
}
function applySkillDeepLink() {
const params = new URLSearchParams(window.location.search);
const skillId = params.get("skill");
const industryName = params.get("industry");
if (skillId) {
const skill = skills.find(function (item) { return item.key === skillId; });
if (skill) {
selectedIndustry = industryOf(skill);
selectedSkillKey = skill.key;
renderSkills();
updateSkillButton();
return;
}
}
if (industryName && industries.indexOf(industryName) !== -1) {
selectedIndustry = industryName;
selectedSkillKey = "";
renderSkills();
updateSkillButton();
}
}
function formatFileSize(bytes) {
if (!bytes) return "0 B";
const units = ["B", "KB", "MB", "GB"];
const index = Math.floor(Math.log(bytes) / Math.log(1024));
return parseFloat((bytes / Math.pow(1024, index)).toFixed(2)) + " " + units[index];
}
function detectLocalType(file) {
const ext = (file.name.split(".").pop() || "").toLowerCase();
if (file.type.indexOf("image/") === 0 || ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].indexOf(ext) !== -1) return "image";
if (file.type.indexOf("video/") === 0 || ["mp4", "mov", "webm", "avi", "mkv"].indexOf(ext) !== -1) return "video";
if (file.type.indexOf("audio/") === 0 || ["mp3", "wav", "ogg", "aac", "flac", "m4a"].indexOf(ext) !== -1) return "audio";
return "other";
}
function setUploadProgress(value, index, total) {
uploadProgress = value;
uploadProgressIndex = value === null ? 0 : (index || 1);
uploadProgressTotal = value === null ? 0 : (total || 1);
if (uploadProgressWrap) {
uploadProgressWrap.classList.toggle("hidden", value === null);
}
if (uploadProgressBar) uploadProgressBar.style.width = (value === null ? 0 : value) + "%";
if (uploadProgressText) {
uploadProgressText.textContent = value === null
? "正在上传 0%"
: "正在上传 " + (uploadProgressTotal > 1 ? uploadProgressIndex + "/" + uploadProgressTotal + " " : "") + value + "%";
}
syncUploadConfirm();
}
function syncUploadConfirm() {
if (!uploadConfirm) return;
const hasSelection = (uploadTab === "local" && selectedLocalFiles.length > 0)
|| (uploadTab === "library" && !!uploadSelectedAsset)
|| (uploadTab === "works" && !!uploadSelectedWork);
const uploading = uploadProgress !== null;
uploadConfirm.classList.toggle("is-visible", hasSelection);
uploadConfirm.disabled = uploading;
uploadConfirm.textContent = uploading
? "上传中 " + (uploadProgressTotal > 1 ? uploadProgressIndex + "/" + uploadProgressTotal + " " : "") + uploadProgress + "%"
: (uploadTab === "local" ? "确认上传" : "确认选择");
}
function syncUploadUI() {
const count = selectedLocalFiles.length;
if (uploadSelected) uploadSelected.classList.toggle("hidden", count === 0);
renderUploadFileList();
const uploading = uploadProgress !== null;
if (uploadProgressWrap) uploadProgressWrap.classList.toggle("hidden", !uploading);
syncUploadConfirm();
}
function setUploadTab(tab) {
uploadTab = tab;
uploadTabButtons.forEach(function (button) {
button.classList.toggle("is-active", button.dataset.uploadTab === tab);
});
[
["library", uploadLibraryPanel],
["works", uploadWorksPanel],
["local", uploadLocalPanel],
].forEach(function (pair) {
if (pair[1]) pair[1].classList.toggle("is-active", pair[0] === tab);
});
if (uploadError) uploadError.hidden = true;
syncUploadUI();
}
function resetLocalFile() {
selectedLocalFiles = [];
uploadProgress = null;
uploadProgressIndex = 0;
uploadProgressTotal = 0;
if (uploadError) uploadError.hidden = true;
if (uploadProgressWrap) uploadProgressWrap.classList.add("hidden");
if (uploadProgressBar) uploadProgressBar.style.width = "0%";
if (uploadProgressText) uploadProgressText.textContent = "正在上传 0%";
syncUploadUI();
}
function openUploadModal() {
if (!uploadModal) return;
hideAttachmentHover();
uploadSelectedAsset = null;
uploadSelectedWork = null;
uploadFolderId = "0";
uploadFolderLabel = "";
uploadAssets = [];
uploadModal.hidden = false;
resetLocalFile();
setUploadTab("library");
loadUploadLibrary();
loadUploadWorks();
}
function closeUploadModal() {
if (!uploadModal) return;
if (uploadAbortController) {
uploadAbortController.abort();
uploadAbortController = null;
}
uploadModal.hidden = true;
resetLocalFile();
}
function isSupportedLocalFile(file) {
if (!file) return false;
const ext = (file.name.split(".").pop() || "").toLowerCase();
if (file.type.indexOf("image/") === 0) return true;
if (["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].indexOf(ext) !== -1) return true;
return ["pdf", "doc", "docx", "txt", "md"].indexOf(ext) !== -1;
}
function renderUploadFileList() {
if (!uploadFileList) return;
uploadFileList.innerHTML = "";
selectedLocalFiles.forEach(function (file, index) {
const row = document.createElement("div");
row.className = "flex items-center gap-3 p-3 bg-gray-50 rounded-xl";
const icon = detectLocalType(file) === "image" ? "image" : "description";
row.innerHTML = '<div class="w-10 h-10 rounded-lg bg-white flex items-center justify-center text-gray-400"><span class="material-symbols-outlined">' + icon + '</span></div><div class="flex-1 min-w-0"><p class="text-sm font-medium text-gray-700 truncate"></p><p class="text-xs text-gray-400"></p></div><button type="button" class="p-2 text-gray-400 hover:text-gray-600 transition-colors cursor-pointer" aria-label="移除文件"><span class="material-symbols-outlined">close</span></button>';
const paragraphs = row.querySelectorAll("p");
paragraphs[0].textContent = file.name;
paragraphs[1].textContent = formatFileSize(file.size);
const remove = row.querySelector("button");
if (uploadProgress !== null) remove.disabled = true;
remove.addEventListener("click", function () { removeLocalFile(index); });
uploadFileList.appendChild(row);
});
}
function removeLocalFile(index) {
if (uploadProgress !== null) return;
selectedLocalFiles.splice(index, 1);
syncUploadUI();
}
function handleLocalFiles(files) {
if (uploadProgress !== null) return;
const incoming = Array.prototype.slice.call(files || []);
if (!incoming.length) return;
const rejected = [];
incoming.forEach(function (file) {
if (!isSupportedLocalFile(file)) {
rejected.push(file.name);
return;
}
const exists = selectedLocalFiles.some(function (item) {
return item.name === file.name && item.size === file.size && item.lastModified === file.lastModified;
});
if (!exists) selectedLocalFiles.push(file);
});
if (uploadError) {
if (rejected.length) {
uploadError.textContent = "仅支持图片和文档(PDF/Word/TXT/MD),已忽略:" + rejected.join("、");
uploadError.hidden = false;
} else {
uploadError.hidden = true;
}
}
syncUploadUI();
}
function openLocalFilePicker() {
const picker = document.createElement("input");
picker.type = "file";
picker.multiple = true;
picker.accept = "image/*,.pdf,.doc,.docx,.txt,.md";
picker.onchange = function (event) {
handleLocalFiles(event.target.files);
};
picker.click();
}
function getUploadCredential(file, category, signal) {
const url = new URL(apiBase() + "/api/base/upload_url", window.location.origin);
url.searchParams.set("filename", file.name || "Untitled.png");
url.searchParams.set("category", category);
return fetch(url.toString(), {
method: "GET",
headers: { Authorization: localStorage.getItem("access_token") || "" },
signal: signal,
}).then(function (response) {
if (!response.ok) throw new Error("HTTP " + response.status);
return response.json();
}).then(function (data) {
if (data.ret !== undefined && data.ret !== 0) throw new Error(data.msg || "获取上传凭证失败");
if (!data.put_url || !data.img_url) throw new Error("上传凭证无效");
return Object.assign({}, data, { put_url: String(data.put_url).replace(/^http:\/\//, "https://") });
});
}
function directUpload(file, category, onProgress, signal) {
return getUploadCredential(file, category, signal).then(function (credential) {
return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open("PUT", credential.put_url);
xhr.setRequestHeader("Content-Type", credential.content_type || file.type || "application/octet-stream");
if (signal) {
if (signal.aborted) {
reject(new DOMException("上传已取消", "AbortError"));
return;
}
signal.addEventListener("abort", function () { xhr.abort(); }, { once: true });
}
if (onProgress) {
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) onProgress(Math.min(99, Math.round((event.loaded / event.total) * 100)));
};
}
xhr.onerror = function () { reject(new Error("直传 OSS 失败,网络错误")); };
xhr.onload = function () {
if (xhr.status < 200 || xhr.status >= 300) {
reject(new Error("直传 OSS 失败,HTTP " + xhr.status));
return;
}
resolve(credential.img_url);
};
xhr.send(file);
});
});
}
function relayUpload(file, category, onProgress, signal) {
return new Promise(function (resolve, reject) {
const form = new FormData();
form.append("imgfile", file);
form.append("category", category);
const xhr = new XMLHttpRequest();
xhr.open("POST", apiBase() + "/api/base/imgupload");
xhr.setRequestHeader("Authorization", localStorage.getItem("access_token") || "");
if (signal) {
if (signal.aborted) {
reject(new DOMException("上传已取消", "AbortError"));
return;
}
signal.addEventListener("abort", function () { xhr.abort(); }, { once: true });
}
if (onProgress) {
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) onProgress(Math.min(99, Math.round((event.loaded / event.total) * 100)));
};
}
xhr.onerror = function () { reject(new Error("网络错误,上传失败")); };
xhr.onabort = function () { reject(new DOMException("上传已取消", "AbortError")); };
xhr.onload = function () {
if (xhr.status < 200 || xhr.status >= 300) {
reject(new Error("HTTP " + xhr.status));
return;
}
let data = {};
try {
data = JSON.parse(xhr.responseText);
} catch (error) {
data = {};
}
if (data.ret !== undefined && data.ret !== 0) {
reject(new Error(data.msg || "上传失败"));
return;
}
if (!data.img_url) {
reject(new Error("上传失败"));
return;
}
resolve(data.img_url);
};
xhr.send(form);
});
}
async function uploadFile(file, onProgress, signal) {
try {
return await directUpload(file, "company", onProgress, signal);
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") throw error;
return relayUpload(file, "company", onProgress, signal);
}
}
function currentTeamId() {
const cached = localStorage.getItem("team_id") || localStorage.getItem("trip_team_id");
if (cached) return cached;
try {
const info = JSON.parse(localStorage.getItem("user_info") || "{}");
return info && info.team_id ? String(info.team_id) : "";
} catch (error) {
return "";
}
}
function apiBase() {
const base = String(window.__TEAM_API_BASE__ || "");
return base && base.charAt(0) !== "%" ? base.replace(/\/+$/, "") : "";
}
function setHeaderScore(value) {
const el = document.getElementById("headerScore");
if (el) el.textContent = String(value == null ? 0 : value);
}
async function loadHeaderScore() {
const cached = localStorage.getItem("team_score");
if (cached !== null) setHeaderScore(cached);
const token = localStorage.getItem("access_token");
if (!token) {
setHeaderScore(0);
return;
}
try {
const response = await fetch(apiBase() + "/api/team/team_list", { headers: { Authorization: token } });
if (!response.ok) return;
const data = await response.json();
if (data.ret !== undefined && data.ret !== 0) return;
const teams = data.data_list || [];
const personal =
teams.find(function (team) { return team.enable_invite === 0 && team.team_name === "我的"; }) ||
teams.find(function (team) { return team.enable_invite === 0; }) ||
teams[0];
if (!personal) return;
localStorage.setItem("team_id", String(personal.team_id));
localStorage.setItem("team_score", String(personal.score == null ? 0 : personal.score));
setHeaderScore(personal.score);
} catch (error) {
console.warn("我的团队积分加载失败", error);
}
}
let scoreRefreshTimer = 0;
function refreshHeaderScoreAfterGeneration(expectedDeduction) {
if (scoreRefreshTimer) {
window.clearTimeout(scoreRefreshTimer);
scoreRefreshTimer = 0;
}
const cached = localStorage.getItem("team_score");
const baseline = cached === null ? null : Number(cached);
const expected = Number(expectedDeduction);
const target =
baseline !== null && Number.isFinite(baseline) && Number.isFinite(expected) && expected > 0
? Math.max(0, baseline - expected)
: null;
let attempts = 0;
const poll = async function () {
scoreRefreshTimer = 0;
attempts += 1;
await loadHeaderScore();
const raw = localStorage.getItem("team_score");
const current = raw === null ? null : Number(raw);
const reached = target !== null && current !== null && Number.isFinite(current) && current <= target;
const changed = baseline === null ? raw !== null : raw !== cached;
if (reached || (target === null && changed) || attempts >= 25) return;
scoreRefreshTimer = window.setTimeout(poll, 1200);
};
scoreRefreshTimer = window.setTimeout(poll, 500);
}
function requireLogin() {
if (localStorage.getItem("access_token")) return true;
window.location.href = "/login/?redirect=/generate";
return false;
}
function bindAccountEntry() {
const scoreBtn = document.getElementById("scoreCapsule");
if (scoreBtn) {
scoreBtn.addEventListener("click", function () {
if (!requireLogin()) return;
window.dispatchEvent(new CustomEvent("laimiao:open-score-detail"));
});
}
const rechargeBtn = document.getElementById("rechargeBtn");
if (rechargeBtn) {
rechargeBtn.addEventListener("click", function () {
if (!requireLogin()) return;
window.dispatchEvent(new CustomEvent("laimiao:open-recharge"));
});
}
window.addEventListener("laimiao:team-updated", function (event) {
const score = event && event.detail ? event.detail.score : null;
if (score !== null) setHeaderScore(score);
});
}
function resolveUploadUrl(url) {
if (!url) return "";
if (/^https?:\/\//i.test(url) || url.indexOf("data:") === 0 || url.indexOf("blob:") === 0) return url;
return url.charAt(0) === "/" ? url : "/" + url;
}
async function readTeamJson(path) {
const token = localStorage.getItem("access_token") || "";
if (!token) throw new Error("未登录");
const response = await fetch(path, { headers: { Authorization: token } });
if (!response.ok) throw new Error("HTTP " + response.status);
const data = await response.json();
if (data.ret !== undefined && data.ret !== 0) throw new Error(data.msg || "请求失败");
return data;
}
function setLibraryEmpty(text) {
if (!uploadLibraryEmpty || !uploadLibraryEmptyText) return;
uploadLibraryEmpty.classList.remove("hidden");
uploadLibraryEmptyText.textContent = text;
}
function showUploadLibraryGrid(show) {
if (!uploadLibraryGrid) return;
uploadLibraryGrid.classList.toggle("hidden", !show);
if (show && uploadLibraryEmpty) uploadLibraryEmpty.classList.add("hidden");
}
async function loadUploadLibrary() {
if (!uploadLibraryEmpty || !uploadLibraryGrid) return;
uploadFolderId = "0";
uploadFolderLabel = "";
uploadSelectedAsset = null;
uploadAssets = [];
if (!localStorage.getItem("access_token") || !currentTeamId()) {
uploadFolders = [];
uploadLibraryLoaded = true;
setLibraryEmpty("素材库为空");
showUploadLibraryGrid(false);
updateLibraryBreadcrumb();
syncUploadConfirm();
return;
}
setLibraryEmpty("加载中...");
showUploadLibraryGrid(false);
try {
const params = new URLSearchParams({
team_id: currentTeamId(),
parent_id: "0",
page: "1",
page_size: "100",
app_type: "0",
});
const data = await readTeamJson(apiBase() + "/api/team/library?" + params.toString());
uploadFolders = (Array.isArray(data.items) ? data.items : [])
.filter(function (item) { return Number(item.type) === 0; })
.map(function (item) {
const ext = item.ext_info || {};
return {
id: String(item.id),
name: item.name || "未命名文件夹",
path: item.path || "/",
count: ext.file_number || 0,
thumbs: Array.isArray(ext.last_img_list) ? ext.last_img_list.map(resolveUploadUrl) : [],
};
});
uploadLibraryLoaded = true;
renderUploadLibrary();
} catch (error) {
uploadFolders = [];
uploadLibraryLoaded = true;
setLibraryEmpty("素材库为空");
showUploadLibraryGrid(false);
updateLibraryBreadcrumb();
}
syncUploadConfirm();
}
async function loadUploadAssets(folderId, folderName) {
uploadFolderId = folderId;
uploadFolderLabel = folderName;
uploadSelectedAsset = null;
uploadAssets = [];
updateLibraryBreadcrumb();
setLibraryEmpty("加载中...");
showUploadLibraryGrid(false);
try {
const params = new URLSearchParams({
team_id: currentTeamId(),
parent_id: folderId,
page: "1",
page_size: "100",
app_type: "0",
});
const data = await readTeamJson(apiBase() + "/api/team/library?" + params.toString());
uploadAssets = (Array.isArray(data.items) ? data.items : [])
.filter(function (item) { return Number(item.type) === 1; })
.map(function (item) {
const ext = item.ext_info || {};
let type = "other";
if (ext.file_type === "img") type = "image";
else if (ext.file_type === "video") type = "video";
else if (ext.file_type === "audio") type = "audio";
let thumb = resolveUploadUrl(item.url);
if (type === "video" && ext.video_info && ext.video_info.cover_img_url) {
thumb = resolveUploadUrl(ext.video_info.cover_img_url);
}
return {
id: String(item.id),
name: item.name || "未命名素材",
type: type,
thumb: thumb,
url: resolveUploadUrl(item.url),
};
});
renderUploadLibrary();
} catch (error) {
uploadAssets = [];
renderUploadLibrary();
}
syncUploadConfirm();
}
function updateLibraryBreadcrumb() {
if (!uploadLibraryBreadcrumb) return;
const inFolder = uploadFolderId !== "0";
uploadLibraryBreadcrumb.classList.toggle("hidden", !inFolder);
uploadLibraryBreadcrumb.classList.toggle("flex", inFolder);
if (uploadFolderName) uploadFolderName.textContent = uploadFolderLabel;
}
function makeUploadSelectBadge(selected) {
const badge = document.createElement("div");
badge.className = "absolute top-3 left-3 w-5 h-5 rounded-full transition-all flex items-center justify-center cursor-pointer "
+ (selected ? "bg-black border-black opacity-100" : "border-2 border-gray-200 bg-white/80 backdrop-blur-sm opacity-0 group-hover:opacity-100 hover:scale-110");
badge.innerHTML = '<span class="material-symbols-outlined text-white text-[14px]">' + (selected ? "check" : "") + "</span>";
return badge;
}
function renderUploadLibrary() {
if (!uploadLibraryGrid || !uploadLibraryEmpty) return;
uploadLibraryGrid.innerHTML = "";
const inFolder = uploadFolderId !== "0";
const items = inFolder ? uploadAssets : uploadFolders;
if (!items.length) {
setLibraryEmpty(inFolder ? "文件夹为空" : "素材库为空");
showUploadLibraryGrid(false);
updateLibraryBreadcrumb();
return;
}
showUploadLibraryGrid(true);
updateLibraryBreadcrumb();
if (!inFolder) {
items.forEach(function (folder) {
const card = document.createElement("div");
card.className = "group cursor-pointer";
const tile = document.createElement("div");
tile.className = "upload-folder-tile bg-gray-50 rounded-2xl border border-gray-100 p-4 transition-all group-hover:border-gray-300 group-hover:shadow-lg overflow-hidden relative";
const center = document.createElement("div");
center.className = "w-full h-full flex items-center justify-center";
const thumbs = document.createElement("div");
thumbs.className = "w-full h-full bg-gray-200 rounded-lg p-1.5 grid grid-cols-2 grid-rows-2 gap-1.5 opacity-60 group-hover:opacity-100 transition-opacity";
for (let i = 0; i < 4; i += 1) {
const thumbUrl = folder.thumbs[i];
if (thumbUrl) {
const img = document.createElement("img");
img.src = thumbUrl;
img.alt = "Thumb";
img.referrerPolicy = "no-referrer";
img.className = "w-full h-full object-cover rounded-sm";
thumbs.appendChild(img);
} else {
const placeholder = document.createElement("div");
placeholder.className = "w-full h-full bg-gray-300 rounded-sm";
thumbs.appendChild(placeholder);
}
}
center.appendChild(thumbs);
tile.appendChild(center);
const meta = document.createElement("div");
meta.className = "mt-4 px-1";
const title = document.createElement("h3");
title.className = "text-sm font-semibold text-gray-900 group-hover:text-black transition-colors truncate";
title.textContent = folder.name;
const count = document.createElement("div");
count.className = "flex items-center gap-3 mt-1 text-[11px] text-gray-400 font-medium";
count.textContent = folder.count + " 个项目";
meta.appendChild(title);
meta.appendChild(count);
card.appendChild(tile);
card.appendChild(meta);
card.addEventListener("click", function () { loadUploadAssets(folder.id, folder.name); });
uploadLibraryGrid.appendChild(card);
});
return;
}
items.forEach(function (asset) {
const card = document.createElement("div");
card.className = "group cursor-pointer";
const tile = document.createElement("div");
const selected = uploadSelectedAsset && uploadSelectedAsset.id === asset.id;
tile.className = "aspect-square rounded-xl border transition-all overflow-hidden relative "
+ (selected ? "border-black border-[3px]" : "border-gray-100 group-hover:border-gray-300");
if (asset.type === "image" && asset.thumb) {
const img = document.createElement("img");
img.src = asset.thumb;
img.alt = asset.name;
img.referrerPolicy = "no-referrer";
img.className = "w-full h-full object-cover";
tile.appendChild(img);
} else if (asset.type === "video") {
const video = document.createElement("div");
video.className = "w-full h-full flex items-center justify-center bg-linear-to-br from-blue-500 to-indigo-600";
video.innerHTML = '<span class="material-symbols-outlined text-white text-4xl">play_circle</span>';
tile.appendChild(video);
} else if (asset.type === "audio") {
const audio = document.createElement("div");
audio.className = "w-full h-full flex items-center justify-center bg-linear-to-br from-purple-500 to-indigo-600";
audio.innerHTML = '<span class="material-symbols-outlined text-white text-4xl">music_note</span>';
tile.appendChild(audio);
} else {
const file = document.createElement("div");
file.className = "w-full h-full bg-white flex flex-col items-center justify-center gap-1.5 p-2";
file.innerHTML = '<span class="material-symbols-outlined text-4xl text-gray-400">description</span><span class="px-1.5 py-0.5 rounded bg-gray-100 text-[10px] font-semibold tracking-wider text-gray-500">FILE</span>';
tile.appendChild(file);
}
const badge = makeUploadSelectBadge(selected);
badge.addEventListener("click", function (event) {
event.stopPropagation();
uploadSelectedAsset = asset;
renderUploadLibrary();
syncUploadConfirm();
});
tile.appendChild(badge);
const type = document.createElement("div");
type.className = "absolute bottom-2 left-2 px-2 py-1 bg-black/60 rounded-md text-[10px] text-white capitalize";
type.textContent = asset.type;
tile.appendChild(type);
const name = document.createElement("p");
name.className = "text-xs text-slate-600 mt-2 truncate";
name.textContent = asset.name;
card.appendChild(tile);
card.appendChild(name);
card.addEventListener("click", function () {
uploadSelectedAsset = asset;
renderUploadLibrary();
syncUploadConfirm();
});
uploadLibraryGrid.appendChild(card);
});
}
async function loadUploadWorks() {
if (!uploadWorksGrid || !uploadWorksEmpty) return;
uploadSelectedWork = null;
if (!localStorage.getItem("access_token") || !currentTeamId()) {
uploadWorks = [];
uploadWorksLoaded = true;
uploadWorksEmpty.classList.remove("hidden");
uploadWorksGrid.classList.add("hidden");
uploadWorksEmptyText.textContent = "暂无作品";
return;
}
uploadWorksEmpty.classList.remove("hidden");
uploadWorksEmptyText.textContent = "加载中...";
uploadWorksGrid.classList.add("hidden");
try {
const params = new URLSearchParams({
team_id: currentTeamId(),
app_type: "0",
page: "1",
page_size: "100",
});
const data = await readTeamJson(apiBase() + "/api/team/drawing_list?" + params.toString());
uploadWorks = (Array.isArray(data.data_list) ? data.data_list : []).map(function (item) {
return {
id: String(item.id),
title: item.workflow_name || "作品 " + item.id,
image: resolveUploadUrl(Array.isArray(item.drawing_url) ? item.drawing_url[0] : ""),
};
});
uploadWorksLoaded = true;
renderUploadWorks();
} catch (error) {
uploadWorks = [];
uploadWorksLoaded = true;
renderUploadWorks();
}
syncUploadConfirm();
}
function renderUploadWorks() {
if (!uploadWorksGrid || !uploadWorksEmpty) return;
uploadWorksGrid.innerHTML = "";
if (!uploadWorks.length) {
uploadWorksEmpty.classList.remove("hidden");
uploadWorksGrid.classList.add("hidden");
uploadWorksEmptyText.textContent = "暂无作品";
return;
}
uploadWorksEmpty.classList.add("hidden");
uploadWorksGrid.classList.remove("hidden");
uploadWorks.forEach(function (work) {
const card = document.createElement("div");
card.className = "group cursor-pointer break-inside-avoid";
const wrap = document.createElement("div");
const selected = uploadSelectedWork && uploadSelectedWork.id === work.id;
wrap.className = "rounded-xl border transition-all overflow-hidden relative "
+ (selected ? "border-black border-[3px]" : "border-gray-100 group-hover:border-gray-300");
const img = document.createElement("img");
img.src = work.image;
img.alt = work.title;
img.loading = "lazy";
img.referrerPolicy = "no-referrer";
img.className = "w-full h-auto";
wrap.appendChild(img);
wrap.appendChild(makeUploadSelectBadge(selected));
const title = document.createElement("p");
title.className = "text-xs text-slate-600 mt-2 truncate";
title.textContent = work.title;
card.appendChild(wrap);
card.appendChild(title);
card.addEventListener("click", function () {
uploadSelectedWork = work;
renderUploadWorks();
syncUploadConfirm();
});
uploadWorksGrid.appendChild(card);
});
}
function handleUploadConfirm() {
if (uploadTab === "library") {
if (!uploadSelectedAsset) return;
addAttachment({
name: uploadSelectedAsset.name,
url: uploadSelectedAsset.url,
type: uploadSelectedAsset.type === "other" ? "other" : uploadSelectedAsset.type,
});
closeUploadModal();
return;
}
if (uploadTab === "works") {
if (!uploadSelectedWork) return;
addAttachment({ name: uploadSelectedWork.title, url: uploadSelectedWork.image, type: "image" });
closeUploadModal();
return;
}
confirmUpload();
}
async function confirmUpload() {
if (!selectedLocalFiles.length || uploadProgress !== null) return;
const files = selectedLocalFiles.slice();
const controller = new AbortController();
uploadAbortController = controller;
if (uploadError) uploadError.hidden = true;
let success = 0;
const failures = [];
for (let i = 0; i < files.length; i += 1) {
if (controller.signal.aborted) break;
const file = files[i];
const position = i + 1;
setUploadProgress(0, position, files.length);
try {
const url = await uploadFile(file, function (percent) {
setUploadProgress(percent, position, files.length);
}, controller.signal);
if (controller.signal.aborted) break;
if (!url) throw new Error("上传失败");
addAttachment({ name: file.name, url: url, type: detectLocalType(file), file: file });
success += 1;
} catch (error) {
if (controller.signal.aborted) break;
failures.push(file.name + ":" + (error instanceof Error ? error.message : String(error)));
}
}
uploadAbortController = null;
setUploadProgress(null);
if (controller.signal.aborted) {
syncUploadUI();
return;
}
if (success > 0) {
closeUploadModal();
showToast(failures.length
? "成功上传 " + success + " 个,失败 " + failures.length + " 个"
: "成功上传 " + success + " 个文件");
} else {
if (uploadError) {
uploadError.textContent = failures.join("\n") || "上传失败";
uploadError.hidden = false;
}
showToast("上传失败");
}
syncUploadUI();
}
function measurePrefix() {
const width = prefix.getBoundingClientRect().width;
if (!width) return;
placeholder.style.left = width + "px";
input.style.textIndent = width + "px";
}
function openDrawer() {
if (mobileDrawer) mobileDrawer.classList.remove("hidden");
}
function closeDrawer() {
if (mobileDrawer) mobileDrawer.classList.add("hidden");
}
collapseBtn.addEventListener("click", toggleCollapse);
recentCollapsed.addEventListener("click", function () { setCollapsed(false); });
newChat.addEventListener("click", handleNewChat);
logoHome.addEventListener("click", function () { window.location.href = "/"; });
userRow.addEventListener("click", function () { goMe(); });
userRow.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
goMe();
}
});
mobileMenu.addEventListener("click", openDrawer);
mobileLogo.addEventListener("click", function () { window.location.href = "/"; });
mobileClose.addEventListener("click", closeDrawer);
mobileBackdrop.addEventListener("click", closeDrawer);
mNewChat.addEventListener("click", function () {
closeDrawer();
handleNewChat();
});
mUserRow.addEventListener("click", function () { closeDrawer(); goMe(); });
if (skillBtn) {
skillBtn.addEventListener("click", function () {
if (skillMenu.hidden) openSkillMenu();
else closeSkillMenu();
});
}
if (skillList) {
skillList.addEventListener("scroll", syncSkillListScroll, { passive: true });
}
if (skillMenuClose) {
skillMenuClose.addEventListener("click", function () {
closeSkillMenu();
skillBtn.focus();
});
}
document.addEventListener("click", function (event) {
if (!skillMenu || skillMenu.hidden || !skillWrap) return;
const path = event.composedPath ? event.composedPath() : [];
if (path.indexOf(skillMenu) !== -1) return;
if (!skillWrap.contains(event.target)) closeSkillMenu();
});
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
closeSkillMenu();
if (uploadModal && !uploadModal.hidden) closeUploadModal();
}
});
input.addEventListener("focus", function () {
const text = currentText();
if (!lastDefaultPrompt || text !== lastDefaultPrompt) return;
const range = document.createRange();
range.selectNodeContents(input);
const selection = window.getSelection();
if (!selection) return;
selection.removeAllRanges();
selection.addRange(range);
});
input.addEventListener("input", function () {
syncPrefix();
syncSend();
});
input.addEventListener("keydown", function (event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
submitPrompt();
}
});
plus.addEventListener("click", openUploadModal);
send.addEventListener("click", submitPrompt);
uploadTabButtons.forEach(function (button) {
button.addEventListener("click", function () { setUploadTab(button.dataset.uploadTab); });
});
if (uploadDropZone) {
uploadDropZone.addEventListener("click", openLocalFilePicker);
uploadDropZone.addEventListener("dragover", function (event) {
event.preventDefault();
uploadDropZone.classList.add("is-dragging");
});
uploadDropZone.addEventListener("dragleave", function (event) {
event.preventDefault();
uploadDropZone.classList.remove("is-dragging");
});
uploadDropZone.addEventListener("drop", function (event) {
event.preventDefault();
uploadDropZone.classList.remove("is-dragging");
handleLocalFiles(event.dataTransfer && event.dataTransfer.files);
});
}
if (uploadClear) {
uploadClear.addEventListener("click", function () {
if (uploadAbortController) {
uploadAbortController.abort();
uploadAbortController = null;
}
resetLocalFile();
});
}
if (uploadConfirm) uploadConfirm.addEventListener("click", handleUploadConfirm);
if (uploadCancel) uploadCancel.addEventListener("click", closeUploadModal);
if (uploadClose) uploadClose.addEventListener("click", closeUploadModal);
if (uploadLibraryRoot) {
uploadLibraryRoot.addEventListener("click", function () {
uploadFolderId = "0";
uploadFolderLabel = "";
uploadSelectedAsset = null;
uploadAssets = [];
renderUploadLibrary();
syncUploadConfirm();
});
}
if (uploadModal) {
uploadModal.addEventListener("click", function (event) {
if (event.target === uploadModal) closeUploadModal();
});
}
document.addEventListener("click", function (event) {
const target = event.target.closest("[data-toast]");
if (!target) return;
event.preventDefault();
showToast(target.getAttribute("data-toast"));
});
window.addEventListener("resize", function () {
measurePrefix();
hideAttachmentHover();
if (skillMenu && !skillMenu.hidden) positionSkillMenu();
});
window.addEventListener("scroll", hideAttachmentHover, true);
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(measurePrefix);
}
renderRecent();
loadRemoteSessions().then(function () {
const target = new URLSearchParams(window.location.search).get("session") || localStorage.getItem("current_session_id");
if (!target) return;
const session = sessions.find(function (item) { return item.id === target || item.agentId === target; });
if (session) selectSession(session);
});
bindAccountEntry();
loadHeaderScore();
loadUserInfo();
setActive("new-chat");
measurePrefix();
updateSkillButton();
loadSkills();
syncSend();
})();
</script>
<script type="module" src="/src/account/main.ts"></script>
<script type="module" src="/src/markdown/main.ts"></script>
</body>
</html>