5185 lines
255 KiB
HTML
5185 lines
255 KiB
HTML
<!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\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, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">");
|
||
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>
|