- 单文件交互式Demo:六大表单模块 + 右侧AI助手面板 - 一键发品(弹窗式)与对话式自主发品(Agent模式) - 多工具串行调度 + 逐工具确认(human-in-the-loop) - 偏好学习机制(localStorage,越用越好用) - Prompt提示词设计文档与项目方法论笔记
1612 lines
80 KiB
HTML
1612 lines
80 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>商品发布 - 旅智工作台</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
|
||
background: #f0f2f5; color: #1f2329;
|
||
height: 100vh; overflow: hidden;
|
||
display: flex; flex-direction: column;
|
||
}
|
||
|
||
/* ============ 顶部导航 ============ */
|
||
.topbar {
|
||
height: 52px; background: #fff; border-bottom: 1px solid #e5e6eb;
|
||
display: flex; align-items: center; padding: 0 20px; flex-shrink: 0; z-index: 10;
|
||
}
|
||
.topbar .logo { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 15px; color: #d96a00; }
|
||
.topbar .logo .icon {
|
||
width: 26px; height: 26px; background: linear-gradient(135deg, #ff7a1a, #ffb300);
|
||
border-radius: 7px; display: flex; align-items: center; justify-content: center;
|
||
color: #fff; font-size: 14px;
|
||
}
|
||
.topbar .breadcrumb { margin-left: 24px; font-size: 13px; color: #86909c; }
|
||
.topbar .breadcrumb b { color: #1f2329; font-weight: 500; }
|
||
.topbar .right { margin-left: auto; display: flex; align-items: center; gap: 16px; font-size: 13px; color: #4e5969; }
|
||
.avatar-sm {
|
||
width: 30px; height: 30px; border-radius: 50%;
|
||
background: linear-gradient(135deg, #6366f1, #8b5cf6); color: #fff;
|
||
display: flex; align-items: center; justify-content: center; font-size: 12px;
|
||
}
|
||
|
||
/* ============ 主体布局 ============ */
|
||
.main { flex: 1; display: flex; overflow: hidden; }
|
||
|
||
/* ============ 左侧:表单区 ============ */
|
||
.form-area { flex: 1; overflow-y: auto; padding: 20px 24px 60px; }
|
||
.page-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
||
.page-head h1 { font-size: 20px; font-weight: 600; }
|
||
.page-head .sub { font-size: 13px; color: #86909c; margin-top: 4px; }
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, #ff7a1a, #ff6600); color: #fff; border: none;
|
||
padding: 9px 22px; border-radius: 8px; font-size: 14px; cursor: pointer;
|
||
font-weight: 500; transition: opacity .2s;
|
||
}
|
||
.btn-primary:hover { opacity: .88; }
|
||
|
||
.card {
|
||
background: #fff; border-radius: 12px; border: 1px solid #e5e6eb;
|
||
padding: 24px; margin-bottom: 16px;
|
||
}
|
||
.card h2 { font-size: 15px; font-weight: 600; margin-bottom: 18px; display: flex; align-items: center; gap: 8px; }
|
||
.card h2 .dot { width: 4px; height: 14px; background: #ff7a1a; border-radius: 2px; }
|
||
.card h2 .badge-step {
|
||
margin-left: auto; font-size: 11px; font-weight: 500; color: #86909c;
|
||
background: #f7f8fa; border-radius: 12px; padding: 3px 10px;
|
||
}
|
||
/* 表单模块内嵌 AI 按钮 */
|
||
.ai-inline-btn {
|
||
margin-left: 8px; font-size: 12px; font-weight: 500;
|
||
color: #ff7a1a; background: #fff; border: 1px solid rgba(255, 122, 26, 0.5);
|
||
border-radius: 12px; padding: 3px 10px; cursor: pointer;
|
||
display: inline-flex; align-items: center; gap: 3px;
|
||
transition: all .15s ease; white-space: nowrap;
|
||
}
|
||
.ai-inline-btn:hover {
|
||
background: #ff7a1a; color: #fff; border-color: #ff7a1a;
|
||
box-shadow: 0 2px 8px rgba(255, 122, 26, 0.35);
|
||
}
|
||
.form-row { display: flex; gap: 16px; margin-bottom: 16px; }
|
||
.form-item { flex: 1; }
|
||
.form-item:last-child { margin-bottom: 0; }
|
||
.form-item label { display: block; font-size: 13px; color: #4e5969; margin-bottom: 6px; }
|
||
.form-item label .req { color: #f53f3f; margin-right: 2px; }
|
||
.form-item input, .form-item select, .form-item textarea {
|
||
width: 100%; border: 1px solid #e5e6eb; border-radius: 8px; padding: 9px 12px;
|
||
font-size: 13px; background: #fff; transition: border-color .2s, box-shadow .2s; font-family: inherit;
|
||
}
|
||
.form-item input:focus, .form-item select:focus, .form-item textarea:focus {
|
||
outline: none; border-color: #ff7a1a; box-shadow: 0 0 0 3px rgba(255,122,26,.12);
|
||
}
|
||
.form-item textarea { resize: vertical; min-height: 92px; line-height: 1.7; }
|
||
.form-item .hint { font-size: 12px; color: #c9cdd4; margin-top: 4px; }
|
||
.counter { font-size: 12px; color: #c9cdd4; margin-top: 4px; text-align: right; }
|
||
.field-highlight { border-color: #ff7a1a !important; box-shadow: 0 0 0 3px rgba(255,122,26,.18) !important; }
|
||
.section-highlight { outline: 2px solid #ff7a1a; outline-offset: 2px; border-radius: 12px; }
|
||
|
||
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
|
||
.chip {
|
||
border: 1px solid #e5e6eb; border-radius: 16px; padding: 5px 14px; font-size: 12px;
|
||
color: #4e5969; cursor: pointer; transition: all .2s; background: #fff;
|
||
}
|
||
.chip:hover { border-color: #ff7a1a; color: #d96a00; }
|
||
.chip.active { background: rgba(255,122,26,.08); border-color: #ff7a1a; color: #d96a00; }
|
||
|
||
/* 富文本编辑器 */
|
||
.rich-editor { border: 1px solid #e5e6eb; border-radius: 8px; overflow: hidden; }
|
||
.rich-toolbar {
|
||
display: flex; gap: 2px; padding: 6px 8px; border-bottom: 1px solid #f2f3f5; background: #fafbfc;
|
||
}
|
||
.rich-toolbar button {
|
||
border: none; background: transparent; width: 28px; height: 26px; border-radius: 6px;
|
||
cursor: pointer; font-size: 13px; color: #4e5969;
|
||
}
|
||
.rich-toolbar button:hover { background: #eef1f4; }
|
||
.rich-toolbar .sep { width: 1px; background: #e5e6eb; margin: 3px 4px; }
|
||
.rich-content {
|
||
min-height: 150px; padding: 12px 14px; font-size: 13px; line-height: 1.9; outline: none;
|
||
}
|
||
.rich-content:empty::before { content: attr(data-placeholder); color: #c9cdd4; }
|
||
.rich-content h3 { font-size: 14px; margin: 10px 0 6px; color: #d96a00; }
|
||
.rich-content ul { padding-left: 20px; }
|
||
.rich-content li { margin-bottom: 4px; }
|
||
.rich-content .img-slot {
|
||
display: flex; align-items: center; justify-content: center; gap: 6px;
|
||
height: 60px; border-radius: 8px; margin: 8px 0; font-size: 12px; color: #86909c;
|
||
background: repeating-linear-gradient(45deg, #f7f8fa, #f7f8fa 10px, #f2f3f5 10px, #f2f3f5 20px);
|
||
border: 1px dashed #d5d9e0;
|
||
}
|
||
|
||
/* 图片上传 */
|
||
.img-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
||
.img-tile {
|
||
aspect-ratio: 4/3; border-radius: 10px; position: relative; overflow: hidden;
|
||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||
gap: 6px; color: #fff; font-size: 13px; cursor: default;
|
||
}
|
||
.img-tile .em { font-size: 26px; }
|
||
.img-tile .tag {
|
||
position: absolute; top: 6px; left: 6px; font-size: 10px; background: rgba(0,0,0,.35);
|
||
padding: 2px 7px; border-radius: 10px; backdrop-filter: blur(2px);
|
||
}
|
||
.img-tile .ai-tag {
|
||
position: absolute; top: 6px; right: 6px; font-size: 10px; background: rgba(255,122,26,.9);
|
||
padding: 2px 7px; border-radius: 10px;
|
||
}
|
||
.img-tile .del {
|
||
position: absolute; bottom: 6px; right: 6px; width: 20px; height: 20px; border-radius: 50%;
|
||
background: rgba(0,0,0,.4); color: #fff; border: none; font-size: 11px; cursor: pointer;
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.img-upload {
|
||
aspect-ratio: 4/3; border: 1.5px dashed #c9cdd4; border-radius: 10px; display: flex;
|
||
flex-direction: column; align-items: center; justify-content: center; gap: 6px;
|
||
color: #86909c; font-size: 12px; cursor: pointer; transition: all .2s; background: #fafbfc;
|
||
}
|
||
.img-upload:hover { border-color: #ff7a1a; color: #d96a00; }
|
||
.img-upload .plus { font-size: 24px; color: #c9cdd4; }
|
||
|
||
/* 行程编辑 */
|
||
.day-card { border: 1px solid #e5e6eb; border-radius: 10px; margin-bottom: 10px; overflow: hidden; }
|
||
.day-head {
|
||
display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: #fafbfc;
|
||
border-bottom: 1px solid #f2f3f5;
|
||
}
|
||
.day-head .dnum {
|
||
background: linear-gradient(135deg, #ff7a1a, #ffb300); color: #fff; font-size: 12px;
|
||
font-weight: 600; border-radius: 6px; padding: 3px 10px; flex-shrink: 0;
|
||
}
|
||
.day-head input {
|
||
flex: 1; border: none; outline: none; font-size: 13px; background: transparent; font-family: inherit;
|
||
}
|
||
.day-head .del-day {
|
||
border: none; background: transparent; color: #c9cdd4; cursor: pointer; font-size: 14px;
|
||
}
|
||
.day-head .del-day:hover { color: #f53f3f; }
|
||
.day-body { padding: 10px 12px; }
|
||
.day-body textarea {
|
||
width: 100%; border: none; outline: none; resize: vertical; font-size: 12px; line-height: 1.7;
|
||
min-height: 44px; font-family: inherit; background: transparent; color: #4e5969;
|
||
}
|
||
.add-day {
|
||
width: 100%; border: 1.5px dashed #c9cdd4; background: #fafbfc; border-radius: 10px;
|
||
padding: 10px; color: #86909c; font-size: 13px; cursor: pointer; transition: all .2s;
|
||
}
|
||
.add-day:hover { border-color: #ff7a1a; color: #d96a00; }
|
||
|
||
/* 价格库存表 */
|
||
.price-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||
.price-table th {
|
||
text-align: left; padding: 8px 10px; background: #fafbfc; color: #86909c;
|
||
font-weight: 500; font-size: 12px; border-bottom: 1px solid #f2f3f5;
|
||
}
|
||
.price-table td { padding: 6px 8px; border-bottom: 1px solid #f7f8fa; }
|
||
.price-table input {
|
||
width: 100%; border: 1px solid transparent; border-radius: 6px; padding: 6px 8px;
|
||
font-size: 13px; font-family: inherit; background: transparent;
|
||
}
|
||
.price-table input:hover, .price-table input:focus { border-color: #e5e6eb; background: #fff; outline: none; }
|
||
.price-table input:focus { border-color: #ff7a1a; }
|
||
.stock-low { color: #b45309; }
|
||
.status-pill { font-size: 11px; border-radius: 10px; padding: 2px 9px; }
|
||
.status-on { background: rgba(34,197,94,.1); color: #15803d; }
|
||
.status-off { background: #f2f3f5; color: #86909c; }
|
||
.del-row { border: none; background: transparent; color: #c9cdd4; cursor: pointer; font-size: 14px; }
|
||
.del-row:hover { color: #f53f3f; }
|
||
.empty-tip { padding: 18px; text-align: center; color: #c9cdd4; font-size: 13px; }
|
||
|
||
/* ============ 右侧:AI 助手 ============ */
|
||
.ai-panel {
|
||
width: 400px; background: #fff; border-left: 1px solid #e5e6eb;
|
||
display: flex; flex-direction: column; position: relative;
|
||
transition: margin-right .35s ease; flex-shrink: 0;
|
||
}
|
||
.ai-panel.collapsed { margin-right: -400px; }
|
||
.ai-header {
|
||
padding: 14px 16px; border-bottom: 1px solid #f2f3f5; display: flex; align-items: center; gap: 10px;
|
||
background: linear-gradient(135deg, rgba(255,122,26,.05), rgba(255,178,0,.05)); flex-shrink: 0;
|
||
}
|
||
.ai-avatar {
|
||
width: 38px; height: 38px; border-radius: 10px;
|
||
background: linear-gradient(135deg, #ff7a1a, #ffb300);
|
||
display: flex; align-items: center; justify-content: center; color: #fff; font-size: 18px;
|
||
position: relative; flex-shrink: 0;
|
||
}
|
||
.ai-avatar::after {
|
||
content: ""; position: absolute; right: -2px; bottom: -2px; width: 10px; height: 10px;
|
||
background: #22c55e; border: 2px solid #fff; border-radius: 50%;
|
||
}
|
||
.ai-header .info { flex: 1; }
|
||
.ai-header .name { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 6px; }
|
||
.ai-header .name .badge {
|
||
font-size: 10px; background: rgba(255,122,26,.1); color: #d96a00;
|
||
padding: 2px 6px; border-radius: 4px; font-weight: 500;
|
||
}
|
||
.ai-header .status { font-size: 12px; color: #86909c; margin-top: 2px; }
|
||
.icon-btn {
|
||
width: 30px; height: 30px; border: 1px solid #e5e6eb; background: #fff; border-radius: 8px;
|
||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||
color: #86909c; font-size: 14px; transition: all .2s;
|
||
}
|
||
.icon-btn:hover { color: #d96a00; border-color: #ff7a1a; }
|
||
|
||
.ai-quick { padding: 12px 14px 4px; border-bottom: 1px solid #f2f3f5; flex-shrink: 0; }
|
||
.ai-quick .title { font-size: 12px; color: #86909c; margin-bottom: 8px; }
|
||
.quick-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding-bottom: 10px; }
|
||
.quick-btn {
|
||
border: 1px solid #e5e6eb; border-radius: 10px; background: #fff; padding: 9px 2px;
|
||
cursor: pointer; text-align: center; transition: all .2s;
|
||
display: flex; flex-direction: column; align-items: center; gap: 5px;
|
||
}
|
||
.quick-btn .qicon { width: 30px; height: 30px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 15px; }
|
||
.quick-btn .qtext { font-size: 12px; color: #4e5969; }
|
||
.quick-btn:hover { border-color: #ff7a1a; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(255,122,26,.12); }
|
||
.q1 { background: rgba(255,122,26,.1); }
|
||
.q2 { background: rgba(255,178,0,.1); }
|
||
.q3 { background: rgba(245,158,11,.12); }
|
||
.q4 { background: rgba(139,92,246,.1); }
|
||
.q5 { background: rgba(236,72,153,.08); }
|
||
.q6 { background: rgba(34,197,94,.1); }
|
||
.q7 { background: rgba(99,102,241,.1); }
|
||
|
||
.ai-messages { flex: 1; overflow-y: auto; padding: 16px 14px; display: flex; flex-direction: column; gap: 14px; }
|
||
.msg { display: flex; gap: 8px; max-width: 100%; animation: msgIn .3s ease; }
|
||
@keyframes msgIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
||
.msg .m-avatar {
|
||
width: 28px; height: 28px; border-radius: 8px; flex-shrink: 0;
|
||
display: flex; align-items: center; justify-content: center; font-size: 13px; color: #fff;
|
||
}
|
||
.msg.ai .m-avatar { background: linear-gradient(135deg, #ff7a1a, #ffb300); }
|
||
.msg.user { flex-direction: row-reverse; }
|
||
.msg.user .m-avatar { background: linear-gradient(135deg, #6366f1, #8b5cf6); }
|
||
.msg .bubble {
|
||
background: #f7f8fa; border-radius: 12px; padding: 10px 12px; font-size: 13px;
|
||
line-height: 1.75; color: #1f2329; max-width: calc(100% - 40px); word-break: break-word;
|
||
}
|
||
.msg.user .bubble { background: linear-gradient(135deg, #ff7a1a, #ff6600); color: #fff; }
|
||
.bubble b { font-weight: 600; }
|
||
.bubble .hl { color: #d96a00; font-weight: 600; }
|
||
|
||
.gen-card {
|
||
background: #fff; border: 1px solid #ffe3c2; border-radius: 12px; padding: 12px;
|
||
margin-top: 8px; box-shadow: 0 2px 8px rgba(15,23,42,.04);
|
||
}
|
||
.gen-card .gc-head { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #d96a00; font-weight: 600; margin-bottom: 8px; }
|
||
.gen-card .gc-body { font-size: 13px; line-height: 1.8; color: #1f2329; background: #f8fafb; border-radius: 8px; padding: 10px 12px; max-height: 260px; overflow-y: auto; }
|
||
.gen-card .gc-body .line { display: block; }
|
||
.gen-card .gc-body .line b { color: #d96a00; }
|
||
.gc-foot { display: flex; gap: 8px; margin-top: 10px; }
|
||
.gc-btn {
|
||
flex: 1; border: 1px solid #ff7a1a; background: rgba(255,122,26,.06); color: #d96a00;
|
||
border-radius: 8px; padding: 7px 0; font-size: 12px; cursor: pointer; transition: all .2s; font-weight: 500;
|
||
}
|
||
.gc-btn:hover { background: #ff7a1a; color: #fff; }
|
||
.gc-btn.ghost { border-color: #e5e6eb; background: #fff; color: #4e5969; }
|
||
.gc-btn.ghost:hover { border-color: #ff7a1a; color: #d96a00; background: #fff; }
|
||
|
||
.tips-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
|
||
.tip-pill { font-size: 11px; background: rgba(255,178,0,.08); color: #d97706; border-radius: 12px; padding: 3px 10px; }
|
||
.warn-pill { background: rgba(245,158,11,.1); color: #b45309; }
|
||
.ok-pill { background: rgba(34,197,94,.1); color: #15803d; }
|
||
|
||
.typing { display: inline-flex; gap: 4px; padding: 4px 2px; }
|
||
.typing span { width: 6px; height: 6px; background: #94a3b8; border-radius: 50%; animation: blink 1.2s infinite; }
|
||
.typing span:nth-child(2) { animation-delay: .2s; }
|
||
.typing span:nth-child(3) { animation-delay: .4s; }
|
||
@keyframes blink { 0%, 80%, 100% { opacity: .3; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-3px); } }
|
||
|
||
.ai-input-area { border-top: 1px solid #f2f3f5; padding: 12px 14px; flex-shrink: 0; background: #fff; }
|
||
.input-wrap { border: 1px solid #e5e6eb; border-radius: 12px; padding: 8px 8px 8px 12px; transition: border-color .2s, box-shadow .2s; }
|
||
.input-wrap:focus-within { border-color: #ff7a1a; box-shadow: 0 0 0 3px rgba(255,122,26,.1); }
|
||
.input-wrap textarea {
|
||
width: 100%; border: none; outline: none; resize: none; font-size: 13px; line-height: 1.6;
|
||
max-height: 90px; font-family: inherit; background: transparent;
|
||
}
|
||
.input-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; }
|
||
.input-tools { display: flex; gap: 4px; }
|
||
.tool-btn {
|
||
width: 28px; height: 28px; border: none; background: transparent; border-radius: 6px;
|
||
cursor: pointer; color: #86909c; font-size: 14px; transition: all .2s;
|
||
}
|
||
.tool-btn:hover { background: #f2f3f5; color: #d96a00; }
|
||
.send-btn {
|
||
background: linear-gradient(135deg, #ff7a1a, #ff6600); border: none; color: #fff;
|
||
width: 60px; height: 30px; border-radius: 8px; font-size: 13px; cursor: pointer;
|
||
transition: opacity .2s; font-weight: 500;
|
||
}
|
||
.send-btn:hover { opacity: .88; }
|
||
.input-hint { text-align: center; font-size: 11px; color: #c9cdd4; margin-top: 8px; }
|
||
|
||
.ai-float {
|
||
position: absolute; right: 0; top: 50%; transform: translateY(-50%);
|
||
width: 36px; height: 92px; background: linear-gradient(135deg, #ff7a1a, #ffb300);
|
||
border-radius: 10px 0 0 10px; color: #fff; border: none; cursor: pointer; font-size: 13px;
|
||
writing-mode: vertical-lr; letter-spacing: 3px; display: none; z-index: 20;
|
||
box-shadow: -4px 0 12px rgba(255,122,26,.25);
|
||
}
|
||
.ai-panel.collapsed .ai-float { display: block; }
|
||
|
||
.toast {
|
||
position: fixed; top: 68px; left: 50%; transform: translateX(-50%) translateY(-12px);
|
||
background: #1f2329; color: #fff; padding: 9px 20px; border-radius: 8px; font-size: 13px;
|
||
opacity: 0; pointer-events: none; transition: all .3s; z-index: 999;
|
||
}
|
||
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||
|
||
::-webkit-scrollbar { width: 6px; }
|
||
::-webkit-scrollbar-thumb { background: #d5d9e0; border-radius: 3px; }
|
||
::-webkit-scrollbar-thumb:hover { background: #b8bec9; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
|
||
/* ============ 自主发品 Agent 卡片 ============ */
|
||
.agent-card {
|
||
border: 1px solid rgba(255, 122, 26, 0.45); border-radius: 12px; overflow: hidden;
|
||
background: linear-gradient(180deg, #fffaf3, #fff); margin-top: 4px;
|
||
}
|
||
.ag-head { background: linear-gradient(135deg, #ffa200, #ff6600); color: #fff; padding: 9px 12px; font-size: 13px; font-weight: 600; }
|
||
.ag-ctx { padding: 8px 12px; font-size: 12px; color: #d96a00; background: #fff7ed; border-bottom: 1px dashed rgba(255,122,26,.25); }
|
||
.ag-steps { padding: 4px 0; }
|
||
.ag-step { display: flex; align-items: center; gap: 8px; padding: 7px 12px; font-size: 12.5px; color: #4e5969; border-bottom: 1px solid #f7f3ee; }
|
||
.ag-step:last-child { border-bottom: none; }
|
||
.ag-step .ag-ico { width: 18px; text-align: center; font-size: 13px; flex-shrink: 0; }
|
||
.ag-step .ag-lbl { flex: 1; }
|
||
.ag-step .ag-note { font-size: 11px; color: #86909c; }
|
||
.ag-step.todo .ag-note { color: #c0c4cc; }
|
||
.ag-step.doing { color: #d96a00; background: #fff8ef; }
|
||
.ag-step.doing .ag-ico { animation: agspin .8s linear infinite; display: inline-block; }
|
||
.ag-step.doing .ag-note { color: #d96a00; }
|
||
.ag-step.done .ag-lbl { color: #1f2329; }
|
||
.ag-step.done .ag-ico { color: #22c55e; }
|
||
@keyframes agspin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
|
||
.ag-foot { padding: 8px 12px; font-size: 11px; color: #86909c; background: #fafafa; border-top: 1px solid #f0f0f0; }
|
||
.ag-chip { display: inline-block; background: #fff; border: 1px solid rgba(255,122,26,.4); color: #ff7a1a; border-radius: 10px; padding: 1px 7px; font-size: 11px; margin: 0 3px 3px 0; }
|
||
|
||
/* ============ 串行确认栏 ============ */
|
||
.ag-confirm-bar {
|
||
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
|
||
margin-top: 10px; padding: 9px 12px;
|
||
background: linear-gradient(135deg, #fff8ef, #fffaf3);
|
||
border: 1px solid rgba(255,122,26,.3); border-radius: 8px;
|
||
}
|
||
.ag-cf-btn {
|
||
background: linear-gradient(135deg, #ffa200, #ff6600); color: #fff; border: none;
|
||
padding: 7px 18px; border-radius: 6px; font-size: 12.5px; cursor: pointer;
|
||
font-weight: 600; font-family: inherit; transition: all .2s; white-space: nowrap;
|
||
}
|
||
.ag-cf-btn:hover { transform: translateY(-1px); box-shadow: 0 3px 10px rgba(255,122,26,.35); }
|
||
.ag-regen-btn { padding: 7px 14px; font-size: 12px; border-radius: 6px; border: 1px solid #e5e6eb; background: #fff; color: #4e5969; cursor: pointer; font-family: inherit; transition: all .15s; white-space: nowrap; }
|
||
.ag-regen-btn:hover { border-color: #ff7a1a; color: #d96a00; }
|
||
.ag-cd { font-size: 11px; color: #d96a00; margin-left: auto; white-space: nowrap; }
|
||
.ag-cd.manual { color: #86909c; }
|
||
.ag-mode-row {
|
||
display: flex; align-items: center; gap: 6px;
|
||
padding: 7px 12px; background: #fff7ed; border-bottom: 1px solid rgba(255,122,26,.12);
|
||
}
|
||
.ag-mode-label { font-size: 11px; color: #86909c; }
|
||
.ag-mt-btn {
|
||
border: 1px solid rgba(255,122,26,.3); background: #fff; color: #d96a00;
|
||
padding: 3px 10px; border-radius: 10px; font-size: 11px; cursor: pointer;
|
||
font-family: inherit; transition: all .15s;
|
||
}
|
||
.ag-mt-btn.active { background: #ff7a1a; color: #fff; border-color: #ff7a1a; }
|
||
.serial-card .gc-foot { display: none; }
|
||
|
||
/* ============ 一键发品 ============ */
|
||
.btn-magic {
|
||
background: linear-gradient(135deg, #ffa200, #ff6600); color: #fff; border: none;
|
||
padding: 10px 26px; border-radius: 10px; font-size: 15px; cursor: pointer; font-weight: 600;
|
||
box-shadow: 0 4px 14px rgba(255,122,26,.35); transition: all .2s; font-family: inherit;
|
||
}
|
||
.btn-magic:hover { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(255,122,26,.45); }
|
||
.btn-ghost-w {
|
||
border: 1px solid #e5e6eb; background: #fff; color: #4e5969; padding: 9px 22px;
|
||
border-radius: 8px; font-size: 14px; cursor: pointer; transition: all .2s; font-family: inherit;
|
||
}
|
||
.btn-ghost-w:hover { border-color: #ff7a1a; color: #d96a00; }
|
||
|
||
.modal-mask {
|
||
position: fixed; inset: 0; background: rgba(15,23,42,.45); backdrop-filter: blur(3px);
|
||
display: none; align-items: center; justify-content: center; z-index: 1000;
|
||
}
|
||
.modal-mask.show { display: flex; }
|
||
.modal {
|
||
width: 530px; max-height: 84vh; overflow-y: auto; background: #fff; border-radius: 16px;
|
||
padding: 24px; animation: pop .25s ease;
|
||
}
|
||
@keyframes pop { from { opacity: 0; transform: scale(.95) translateY(10px); } to { opacity: 1; transform: none; } }
|
||
.modal h3 { font-size: 17px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
||
.modal .sub { font-size: 12px; color: #86909c; margin-top: 4px; }
|
||
.learn-hint {
|
||
margin-top: 14px; background: linear-gradient(135deg, rgba(255,178,0,.1), rgba(255,122,26,.08));
|
||
border: 1px solid #ffe3c2; border-radius: 10px; padding: 12px; font-size: 12px; line-height: 1.8; color: #4e5969;
|
||
}
|
||
.learn-hint b { color: #d96a00; }
|
||
.stat-row { display: flex; gap: 10px; margin-top: 14px; }
|
||
.stat-box { flex: 1; background: #f7f8fa; border-radius: 10px; padding: 10px; text-align: center; }
|
||
.stat-box .v { font-size: 18px; font-weight: 700; color: #d96a00; }
|
||
.stat-box .v small { font-size: 12px; font-weight: 400; color: #86909c; }
|
||
.stat-box .k { font-size: 11px; color: #86909c; margin-top: 2px; }
|
||
.level-wrap { margin-top: 14px; }
|
||
.level-bar { height: 6px; background: #f2f3f5; border-radius: 3px; overflow: hidden; margin-top: 6px; }
|
||
.level-bar i { display: block; height: 100%; background: linear-gradient(90deg, #ffa200, #ff6600); border-radius: 3px; transition: width .6s; }
|
||
.level-text { font-size: 12px; color: #4e5969; display: flex; justify-content: space-between; }
|
||
.conf-row { display: flex; gap: 12px; margin-top: 16px; }
|
||
.step-list { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }
|
||
.step-item {
|
||
display: flex; align-items: center; gap: 10px; font-size: 13px; color: #86909c;
|
||
padding: 10px 12px; border: 1px solid #f2f3f5; border-radius: 10px; background: #fafbfc; transition: all .3s;
|
||
}
|
||
.step-item .s-icon {
|
||
width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center;
|
||
justify-content: center; font-size: 12px; background: #f2f3f5; color: #86909c; flex-shrink: 0;
|
||
}
|
||
.step-item.doing { border-color: #ffd9ae; background: rgba(255,178,0,.06); color: #1f2329; }
|
||
.step-item.doing .s-icon { background: linear-gradient(135deg, #ffa200, #ff6600); color: #fff; animation: spin 1s linear infinite; }
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
.step-item.done { color: #1f2329; }
|
||
.step-item.done .s-icon { background: #22c55e; color: #fff; }
|
||
.step-item .s-note { margin-left: auto; font-size: 11px; color: #c9cdd4; }
|
||
.step-item.done .s-note { color: #15803d; }
|
||
.modal-foot { display: flex; gap: 10px; margin-top: 20px; justify-content: flex-end; }
|
||
.done-view { text-align: center; padding: 8px 0; }
|
||
.done-view .big { font-size: 42px; }
|
||
.done-view h4 { font-size: 16px; margin: 8px 0 4px; }
|
||
.done-view .dsub { font-size: 12px; color: #86909c; }
|
||
.oc-wide {
|
||
width: 100%; border: none; background: linear-gradient(135deg, #ffa200, #ff6600); color: #fff;
|
||
border-radius: 10px; padding: 10px; font-size: 13px; font-weight: 600; cursor: pointer;
|
||
margin-bottom: 10px; box-shadow: 0 3px 10px rgba(255,122,26,.25); transition: all .2s; font-family: inherit;
|
||
}
|
||
.oc-wide:hover { transform: translateY(-1px); box-shadow: 0 5px 14px rgba(255,122,26,.35); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- 顶部导航 -->
|
||
<div class="topbar">
|
||
<div class="logo"><div class="icon">✈</div> 旅智工作台</div>
|
||
<div class="breadcrumb">商品管理 / <b>发布新商品</b></div>
|
||
<div class="right">
|
||
<span>草稿自动保存于 1 分钟前</span>
|
||
<div class="avatar-sm">李</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="main">
|
||
<!-- 左侧:商品发布表单 -->
|
||
<div class="form-area" id="formArea">
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>发布新商品</h1>
|
||
<div class="sub">完善商品信息,或用「一键发品」30 秒搞定 · 右侧 AI 助手随时待命 ↗</div>
|
||
</div>
|
||
<div style="display:flex; gap:10px">
|
||
<button class="btn-ghost-w" onclick="toast('已提交审核(演示)')">提交审核</button>
|
||
<button class="btn-magic" onclick="openOC()">⚡ 一键发品</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 1. 商品标题 -->
|
||
<div class="card" id="card-title">
|
||
<h2><span class="dot"></span>商品标题<button class="ai-inline-btn" onclick="formAI('title')" title="AI 生成 3 个高转化标题">✨ AI 生成标题</button><span class="badge-step">模块 1/6</span></h2>
|
||
<div class="form-row">
|
||
<div class="form-item" style="flex:2">
|
||
<label><span class="req">*</span>商品标题</label>
|
||
<input id="f-title" type="text" maxlength="40" placeholder="例:云南昆明+大理+丽江6日5晚纯玩跟团游">
|
||
<div class="counter"><span id="titleCount">0</span>/40 字</div>
|
||
</div>
|
||
<div class="form-item">
|
||
<label>目的地</label>
|
||
<select id="f-dest">
|
||
<option>云南(昆明/大理/丽江)</option>
|
||
<option>四川(成都/九寨沟)</option>
|
||
<option>海南(三亚)</option>
|
||
<option>新疆(乌鲁木齐/喀纳斯)</option>
|
||
<option>西藏(拉萨/林芝)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-item">
|
||
<label>行程天数</label>
|
||
<select id="f-days">
|
||
<option>6天5晚</option><option>3天2晚</option><option>5天4晚</option>
|
||
<option>7天6晚</option><option>8天7晚</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-item">
|
||
<label>商品类型</label>
|
||
<select id="f-type">
|
||
<option>跟团游</option><option>自由行</option><option>定制游</option>
|
||
<option>半自助游</option><option>当地玩乐</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-item" style="flex:2">
|
||
<label>适合人群</label>
|
||
<div class="chips" id="f-tags">
|
||
<span class="chip active">亲子家庭</span>
|
||
<span class="chip">蜜月情侣</span>
|
||
<span class="chip">银发无忧</span>
|
||
<span class="chip">摄影爱好</span>
|
||
<span class="chip">小众深度</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 2. 详细介绍 -->
|
||
<div class="card" id="card-intro">
|
||
<h2><span class="dot"></span>详细介绍<button class="ai-inline-btn" onclick="formAI('intro')" title="AI 生成四段式卖点摘要">✨ AI 生成卖点</button><span class="badge-step">模块 2/6</span></h2>
|
||
<div class="form-item">
|
||
<label><span class="req">*</span>详细介绍(列表页展示的卖点摘要)</label>
|
||
<textarea id="f-intro" rows="5" placeholder="突出行程亮点、住宿标准、服务承诺等核心卖点…"></textarea>
|
||
<div class="hint">建议 150-300 字,聚焦用户最关心的 3-4 个卖点</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 3. 商品详情 -->
|
||
<div class="card" id="card-detail">
|
||
<h2><span class="dot"></span>商品详情<button class="ai-inline-btn" onclick="formAI('detail')" title="AI 生成结构化图文详情">✨ AI 生成详情</button><span class="badge-step">模块 3/6</span></h2>
|
||
<div class="form-item">
|
||
<label><span class="req">*</span>详情页图文内容</label>
|
||
<div class="rich-editor">
|
||
<div class="rich-toolbar">
|
||
<button title="加粗">B</button>
|
||
<button title="斜体">I</button>
|
||
<button title="标题">H</button>
|
||
<div class="sep"></div>
|
||
<button title="插入图片">🖼️</button>
|
||
<button title="插入视频">🎬</button>
|
||
<div class="sep"></div>
|
||
<button title="行程模块">🗺️</button>
|
||
<button title="费用说明">¥</button>
|
||
</div>
|
||
<div class="rich-content" id="f-detail" contenteditable="true" data-placeholder="支持图文混排,AI 可生成结构化详情内容…"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 4. 商品图片 -->
|
||
<div class="card" id="card-img">
|
||
<h2><span class="dot"></span>商品图片<button class="ai-inline-btn" onclick="formAI('image')" title="AI 推荐拍摄脚本与配图清单">✨ AI 配图建议</button><span class="badge-step">模块 4/6</span></h2>
|
||
<div class="form-item">
|
||
<label><span class="req">*</span>主图与详情图(首张为主图,建议 800×600 以上)</label>
|
||
<div class="img-grid" id="imgGrid"></div>
|
||
<div class="hint" style="margin-top:10px">AI 可根据行程内容推荐拍摄脚本与配图清单</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 5. 行程编辑 -->
|
||
<div class="card" id="card-itinerary">
|
||
<h2><span class="dot"></span>行程编辑<button class="ai-inline-btn" onclick="formAI('itinerary')" title="AI 按天数生成逐日行程">✨ AI 规划行程</button><span class="badge-step">模块 5/6</span></h2>
|
||
<div id="itineraryList"></div>
|
||
<button class="add-day" onclick="addDay()">+ 添加一天行程</button>
|
||
</div>
|
||
|
||
<!-- 6. 价格库存 -->
|
||
<div class="card" id="card-price">
|
||
<h2><span class="dot"></span>价格库存<button class="ai-inline-btn" onclick="formAI('price')" title="AI 生成团期排期与定价方案">✨ AI 排期定价</button><span class="badge-step">模块 6/6</span></h2>
|
||
<table class="price-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:150px">出发日期</th>
|
||
<th>成人价(元)</th>
|
||
<th>儿童价(元)</th>
|
||
<th style="width:90px">库存</th>
|
||
<th style="width:80px">状态</th>
|
||
<th style="width:40px"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="priceBody"></tbody>
|
||
</table>
|
||
<button class="add-day" style="margin-top:10px" onclick="addPriceRow()">+ 添加团期</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:AI 助手 -->
|
||
<div class="ai-panel" id="aiPanel">
|
||
<button class="ai-float" onclick="togglePanel()">AI 助手</button>
|
||
<div class="ai-header">
|
||
<div class="ai-avatar">🤖</div>
|
||
<div class="info">
|
||
<div class="name">旅智小助 <span class="badge">商品发布专精</span></div>
|
||
<div class="status">在线 · 已读取当前页面表单内容</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="icon-btn" title="收起面板" onclick="togglePanel()">»</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ai-quick">
|
||
<div class="title">⚡ 快捷生成(对应左侧 6 大模块)</div>
|
||
<button class="oc-wide" onclick="openOC()">⚡ 一键发品 · 30 秒完成 6 大模块</button>
|
||
<div class="quick-grid">
|
||
<button class="quick-btn" onclick="quickAction('title')">
|
||
<span class="qicon q1">📝</span><span class="qtext">商品标题</span>
|
||
</button>
|
||
<button class="quick-btn" onclick="quickAction('intro')">
|
||
<span class="qicon q2">💎</span><span class="qtext">详细介绍</span>
|
||
</button>
|
||
<button class="quick-btn" onclick="quickAction('detail')">
|
||
<span class="qicon q7">📄</span><span class="qtext">商品详情</span>
|
||
</button>
|
||
<button class="quick-btn" onclick="quickAction('image')">
|
||
<span class="qicon q5">🖼️</span><span class="qtext">配图建议</span>
|
||
</button>
|
||
<button class="quick-btn" onclick="quickAction('itinerary')">
|
||
<span class="qicon q3">🗺️</span><span class="qtext">行程规划</span>
|
||
</button>
|
||
<button class="quick-btn" onclick="quickAction('price')">
|
||
<span class="qicon q4">💰</span><span class="qtext">价格库存</span>
|
||
</button>
|
||
<button class="quick-btn" onclick="quickAction('seo')">
|
||
<span class="qicon q6">🔍</span><span class="qtext">SEO 词</span>
|
||
</button>
|
||
<button class="quick-btn" onclick="quickAction('check')">
|
||
<span class="qicon q6">🛡️</span><span class="qtext">合规检查</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ai-messages" id="msgList"></div>
|
||
|
||
<div class="ai-input-area">
|
||
<div class="input-wrap">
|
||
<textarea id="chatInput" rows="1" placeholder="例如:帮我优化标题和详情 / 帮我发个海南亲子6天自由行" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMessage();}"></textarea>
|
||
<div class="input-foot">
|
||
<div class="input-tools">
|
||
<button class="tool-btn" title="参考示例">💡</button>
|
||
<button class="tool-btn" title="上传图片">🖼️</button>
|
||
</div>
|
||
<button class="send-btn" onclick="sendMessage()">发送</button>
|
||
</div>
|
||
</div>
|
||
<div class="input-hint">Enter 发送 · Shift+Enter 换行 · 内容由 AI 生成,请核对后发布</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 一键发品弹窗 -->
|
||
<div class="modal-mask" id="ocMask" onclick="if(event.target===this)closeOC()">
|
||
<div class="modal">
|
||
<!-- 视图1:配置 -->
|
||
<div id="ocConfig">
|
||
<h3>⚡ 一键发品</h3>
|
||
<div class="sub">选好基础信息,AI 自动完成 6 大模块填写,30 秒搞定发品</div>
|
||
<div class="learn-hint" id="ocLearn" style="display:none"></div>
|
||
<div class="stat-row">
|
||
<div class="stat-box"><div class="v" id="ocCount">0</div><div class="k">累计发品(次)</div></div>
|
||
<div class="stat-box"><div class="v" id="ocPrefN">0</div><div class="k">已沉淀偏好(条)</div></div>
|
||
<div class="stat-box"><div class="v">1<small> 分钟</small></div><div class="k">发品耗时(传统约 30 分钟)</div></div>
|
||
</div>
|
||
<div class="level-wrap">
|
||
<div class="level-text"><span id="ocLevel"></span><span id="ocLevelNext"></span></div>
|
||
<div class="level-bar"><i id="ocBar" style="width:0%"></i></div>
|
||
</div>
|
||
<div class="conf-row">
|
||
<div class="form-item">
|
||
<label>目的地</label>
|
||
<select id="oc-dest">
|
||
<option>云南(昆明/大理/丽江)</option>
|
||
<option>四川(成都/九寨沟)</option>
|
||
<option>海南(三亚)</option>
|
||
<option>新疆(乌鲁木齐/喀纳斯)</option>
|
||
<option>西藏(拉萨/林芝)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-item">
|
||
<label>天数</label>
|
||
<select id="oc-days">
|
||
<option>6天5晚</option><option>3天2晚</option><option>5天4晚</option>
|
||
<option>7天6晚</option><option>8天7晚</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-item">
|
||
<label>类型</label>
|
||
<select id="oc-type">
|
||
<option>跟团游</option><option>自由行</option><option>定制游</option>
|
||
<option>半自助游</option><option>当地玩乐</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-item">
|
||
<label>适合人群</label>
|
||
<div class="chips" id="oc-tags">
|
||
<span class="chip active">亲子家庭</span>
|
||
<span class="chip">蜜月情侣</span>
|
||
<span class="chip">银发无忧</span>
|
||
<span class="chip">摄影爱好</span>
|
||
<span class="chip">小众深度</span>
|
||
</div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<button class="btn-ghost-w" onclick="closeOC()">取消</button>
|
||
<button class="btn-magic" onclick="startOC()">⚡ 开始生成</button>
|
||
</div>
|
||
</div>
|
||
<!-- 视图2:生成进度 -->
|
||
<div id="ocProgress" style="display:none">
|
||
<h3>🤖 AI 正在发品…</h3>
|
||
<div class="sub">按您的偏好逐个模块生成,完成后可随时接手微调</div>
|
||
<div class="step-list" id="ocSteps"></div>
|
||
</div>
|
||
<!-- 视图3:完成 -->
|
||
<div id="ocDone" style="display:none">
|
||
<div class="done-view">
|
||
<div class="big">🎉</div>
|
||
<h4>发品完成!</h4>
|
||
<div class="dsub">6 大模块已全部生成,请核对后提交审核</div>
|
||
</div>
|
||
<div class="learn-hint" id="ocNewPref"></div>
|
||
<div class="modal-foot">
|
||
<button class="btn-ghost-w" onclick="closeOC()">返回核对</button>
|
||
<button class="btn-magic" onclick="closeOC();toast('已提交审核(演示)')">提交审核</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast" id="toast">✓ 已填入</div>
|
||
|
||
<script>
|
||
/* ================= 状态 ================= */
|
||
const $ = id => document.getElementById(id);
|
||
const msgList = $('msgList');
|
||
let ctx = { dest: '云南', days: '6天5晚', type: '跟团游', theme: '亲子家庭' };
|
||
let itineraryDays = [];
|
||
let priceRows = [];
|
||
let images = [];
|
||
const generated = {}; // 每类动作最新生成结果
|
||
const esc = s => s.replace(/</g, '<');
|
||
let silentBatch = false; // 一键发品批量模式:静默填入,不逐条弹提示
|
||
|
||
/* ================= 工具 ================= */
|
||
function toast(t) {
|
||
if (silentBatch) return;
|
||
const el = $('toast');
|
||
el.textContent = '✓ ' + t;
|
||
el.classList.add('show');
|
||
setTimeout(() => el.classList.remove('show'), 1800);
|
||
}
|
||
function scrollToBottom() {
|
||
requestAnimationFrame(() => { msgList.scrollTop = msgList.scrollHeight; });
|
||
}
|
||
function readForm() {
|
||
ctx.dest = $('f-dest').value.replace(/(.*)/, '') || ctx.dest;
|
||
ctx.days = $('f-days').value || ctx.days;
|
||
ctx.type = $('f-type').value || ctx.type;
|
||
const tag = document.querySelector('#f-tags .chip.active');
|
||
if (tag) ctx.theme = tag.textContent;
|
||
}
|
||
function flashCard(cardId) {
|
||
if (silentBatch) return;
|
||
const el = $(cardId);
|
||
el.classList.add('section-highlight');
|
||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
setTimeout(() => el.classList.remove('section-highlight'), 1800);
|
||
}
|
||
|
||
/* ================= 消息渲染 ================= */
|
||
function addMsg(role, html) {
|
||
const div = document.createElement('div');
|
||
div.className = 'msg ' + role;
|
||
div.innerHTML = role === 'ai'
|
||
? '<div class="m-avatar">🤖</div><div class="bubble">' + html + '</div>'
|
||
: '<div class="m-avatar">李</div><div class="bubble">' + html + '</div>';
|
||
msgList.appendChild(div);
|
||
scrollToBottom();
|
||
return div;
|
||
}
|
||
function typing() { return addMsg('ai', '<div class="typing"><span></span><span></span><span></span></div>'); }
|
||
function replaceTyping(div, html) { div.querySelector('.bubble').innerHTML = html; scrollToBottom(); }
|
||
function delay(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||
|
||
/* ================= 表单渲染:图片 ================= */
|
||
const imgPalette = [
|
||
{ g: 'linear-gradient(135deg,#ffb300,#ff7a1a)', em: '🚤' },
|
||
{ g: 'linear-gradient(135deg,#f59e0b,#ef4444)', em: '🏮' },
|
||
{ g: 'linear-gradient(135deg,#8b5cf6,#ec4899)', em: '🏔️' },
|
||
{ g: 'linear-gradient(135deg,#22c55e,#ffb300)', em: '🏨' },
|
||
{ g: 'linear-gradient(135deg,#f97316,#facc15)', em: '🍲' },
|
||
{ g: 'linear-gradient(135deg,#6366f1,#ffb300)', em: '🌅' },
|
||
];
|
||
function renderImages() {
|
||
const grid = $('imgGrid');
|
||
let html = images.map((im, i) => {
|
||
const p = imgPalette[i % imgPalette.length];
|
||
return `<div class="img-tile" style="background:${p.g}">
|
||
<span class="tag">${i === 0 ? '主图' : '详情图 ' + i}</span>
|
||
${im.ai ? '<span class="ai-tag">AI 建议</span>' : ''}
|
||
<span class="em">${p.em}</span><span>${im.label}</span>
|
||
<button class="del" onclick="removeImage(${i})">✕</button>
|
||
</div>`;
|
||
}).join('');
|
||
html += `<div class="img-upload" onclick="uploadImg()"><span class="plus">+</span>上传图片</div>`;
|
||
grid.innerHTML = html;
|
||
}
|
||
function removeImage(i) { images.splice(i, 1); renderImages(); }
|
||
function uploadImg() {
|
||
images.push({ label: '新上传图片', ai: false });
|
||
renderImages();
|
||
toast('已添加图片占位(演示)');
|
||
}
|
||
|
||
/* ================= 表单渲染:行程 ================= */
|
||
function renderItinerary() {
|
||
const list = $('itineraryList');
|
||
if (!itineraryDays.length) {
|
||
list.innerHTML = '<div class="empty-tip">暂无行程,点击下方按钮添加,或让 AI 一键生成 ↗</div>';
|
||
return;
|
||
}
|
||
list.innerHTML = itineraryDays.map((d, i) => `
|
||
<div class="day-card">
|
||
<div class="day-head">
|
||
<span class="dnum">D${i + 1}</span>
|
||
<input value="${esc(d.title)}" placeholder="当日主题,如:抵达昆明·入住酒店" oninput="itineraryDays[${i}].title=this.value">
|
||
<button class="del-day" title="删除当天" onclick="removeDay(${i})">✕</button>
|
||
</div>
|
||
<div class="day-body">
|
||
<textarea placeholder="当日安排描述…" oninput="itineraryDays[${i}].desc=this.value">${esc(d.desc)}</textarea>
|
||
</div>
|
||
</div>`).join('');
|
||
}
|
||
function addDay() { itineraryDays.push({ title: '', desc: '' }); renderItinerary(); }
|
||
function removeDay(i) { itineraryDays.splice(i, 1); renderItinerary(); }
|
||
|
||
/* ================= 表单渲染:价格库存 ================= */
|
||
function renderPrice() {
|
||
const body = $('priceBody');
|
||
if (!priceRows.length) {
|
||
body.innerHTML = '<tr><td colspan="6"><div class="empty-tip">暂无团期,点击下方按钮添加,或让 AI 生成排期方案 ↗</div></td></tr>';
|
||
return;
|
||
}
|
||
body.innerHTML = priceRows.map((r, i) => `
|
||
<tr>
|
||
<td><input value="${r.date}" oninput="priceRows[${i}].date=this.value"></td>
|
||
<td><input type="number" value="${r.adult}" oninput="priceRows[${i}].adult=this.value"></td>
|
||
<td><input type="number" value="${r.child}" oninput="priceRows[${i}].child=this.value"></td>
|
||
<td><input type="number" value="${r.stock}" class="${r.stock < 6 ? 'stock-low' : ''}" oninput="priceRows[${i}].stock=+this.value;renderPrice()"></td>
|
||
<td><span class="status-pill ${r.on ? 'status-on' : 'status-off'}" style="cursor:pointer" onclick="priceRows[${i}].on=!priceRows[${i}].on;renderPrice()">${r.on ? '在售' : '停售'}</span></td>
|
||
<td><button class="del-row" onclick="removePriceRow(${i})">✕</button></td>
|
||
</tr>`).join('');
|
||
}
|
||
function addPriceRow() {
|
||
priceRows.push({ date: '2026-10-01', adult: 3299, child: 2299, stock: 20, on: true });
|
||
renderPrice();
|
||
}
|
||
function removePriceRow(i) { priceRows.splice(i, 1); renderPrice(); }
|
||
|
||
/* ================= 一键填入 ================= */
|
||
const applyMap = {
|
||
title() {
|
||
const opts = generated.title.data;
|
||
const idx = generated.title.used || 0;
|
||
const t = opts[idx % opts.length];
|
||
generated.title.used = idx + 1;
|
||
$('f-title').value = t;
|
||
$('titleCount').textContent = t.length;
|
||
$('f-title').classList.add('field-highlight');
|
||
setTimeout(() => $('f-title').classList.remove('field-highlight'), 1600);
|
||
flashCard('card-title');
|
||
toast('已填入商品标题(方案 ' + ((idx % opts.length) + 1) + ')');
|
||
},
|
||
intro() {
|
||
$('f-intro').value = generated.intro.data;
|
||
$('f-intro').classList.add('field-highlight');
|
||
setTimeout(() => $('f-intro').classList.remove('field-highlight'), 1600);
|
||
flashCard('card-intro');
|
||
toast('已填入详细介绍');
|
||
},
|
||
detail() {
|
||
$('f-detail').innerHTML = generated.detail.data;
|
||
flashCard('card-detail');
|
||
toast('已生成商品详情图文结构');
|
||
},
|
||
image() {
|
||
generated.image.data.forEach(im => images.push({ label: im.label, ai: true }));
|
||
renderImages();
|
||
flashCard('card-img');
|
||
toast('已按 AI 清单添加 ' + generated.image.data.length + ' 张配图');
|
||
},
|
||
itinerary() {
|
||
itineraryDays = generated.itinerary.data.map(d => ({ ...d }));
|
||
renderItinerary();
|
||
flashCard('card-itinerary');
|
||
toast('已填入 ' + itineraryDays.length + ' 天行程');
|
||
},
|
||
price() {
|
||
priceRows = generated.price.data.map(r => ({ ...r }));
|
||
renderPrice();
|
||
flashCard('card-price');
|
||
toast('已生成 ' + priceRows.length + ' 个团期');
|
||
}
|
||
};
|
||
function applyGen(type) { if (generated[type]) applyMap[type](); }
|
||
function regen(type) { quickAction(type, true); }
|
||
|
||
/* ================= 快捷动作数据 ================= */
|
||
const genData = {
|
||
title() {
|
||
readForm();
|
||
const t = [
|
||
`【纯玩无购物】${ctx.dest}${ctx.days}精品${ctx.type}·5A景区全含·${ctx.theme}首选`,
|
||
`${ctx.dest}${ctx.days}深度${ctx.type}|一价全含0自费·住${ctx.dest === '云南' ? '海景' : '景区'}酒店`,
|
||
`【${ctx.theme}必选】${ctx.dest}${ctx.days}${ctx.type}·随走随停·金牌向导·赠${ctx.dest === '云南' ? '洱海游船' : '旅拍'}体验`
|
||
];
|
||
return {
|
||
pre: `结合 <span class="hl">${ctx.dest}·${ctx.days}·${ctx.type}·${ctx.theme}</span> 生成 3 个高点击标题(可多次点击填入切换方案):`,
|
||
card: `<div class="gen-card">
|
||
<div class="gc-head">✨ 标题方案(三选一)</div>
|
||
<div class="gc-body">${t.map((x, i) => `<span class="line"><b>${i + 1}.</b> ${x}</span>`).join('')}</div>
|
||
<div class="gc-foot">
|
||
<button class="gc-btn" onclick="applyGen('title')">⚡ 一键填入「商品标题」</button>
|
||
<button class="gc-btn ghost" onclick="regen('title')">🔄 换一版</button>
|
||
</div>
|
||
<div class="tips-row">
|
||
<span class="tip-pill ok-pill">含目的地+天数+类型,利于搜索</span>
|
||
<span class="tip-pill">「无购物」为平台高转化词</span>
|
||
</div>
|
||
</div>`,
|
||
data: t
|
||
};
|
||
},
|
||
intro() {
|
||
readForm();
|
||
const d = `【${ctx.dest}${ctx.days}${ctx.type}·${ctx.theme}优选】全程 0 购物 0 自费,景区门票一价全含,金牌持证导游全程讲解。\n精选 ${ctx.days === '3天2晚' ? '2' : '4'} 晚高分酒店,${ctx.dest === '云南' ? '大理海景房 + 丽江古城客栈' : '近景区步行可达'},住的舒心才是真度假。\n特色体验:${ctx.dest === '云南' ? '白族扎染 + 洱海游船 + 野生菌火锅' : '深度环线,不赶路'},孩子玩得开心、大人拍得尽兴。\n服务承诺:行程透明无隐形消费,24h 专属管家,未成团提前 3 天全额退款。`;
|
||
return {
|
||
pre: `围绕「${ctx.theme}」人群痛点,按「亮点—住宿—体验—承诺」四段式生成详细介绍:`,
|
||
card: `<div class="gen-card">
|
||
<div class="gc-head">✨ 详细介绍(列表页卖点摘要)</div>
|
||
<div class="gc-body">${d.split('\n').map(l => `<span class="line">${l}</span>`).join('')}</div>
|
||
<div class="gc-foot">
|
||
<button class="gc-btn" onclick="applyGen('intro')">⚡ 一键填入「详细介绍」</button>
|
||
<button class="gc-btn ghost" onclick="regen('intro')">🔄 换一版</button>
|
||
</div>
|
||
<div class="tips-row">
|
||
<span class="tip-pill ok-pill">约 200 字,符合最佳展示长度</span>
|
||
<span class="tip-pill">含服务承诺,降低决策门槛</span>
|
||
</div>
|
||
</div>`,
|
||
data: d
|
||
};
|
||
},
|
||
detail() {
|
||
readForm();
|
||
const html = `<h3>🌟 行程亮点</h3>
|
||
<ul>
|
||
<li>全程 <b>0 购物 0 自费</b>,行程透明,把时间还给风景</li>
|
||
<li>${ctx.dest}经典 IP 全含:${ctx.dest === '云南' ? '石林、洱海、玉龙雪山、丽江古城' : '核心景区深度游览'}</li>
|
||
<li>${ctx.theme}专属节奏:每日行车 ≤ 3.5 小时,不赶路不早起(可睡到自然醒)</li>
|
||
</ul>
|
||
<h3>🏨 住宿安排</h3>
|
||
<ul>
|
||
<li>${ctx.dest === '云南' ? '大理 1 晚海景房 + 丽江 2 晚古城客栈' : '全程高分舒适型酒店'}</li>
|
||
<li>提前公布酒店名单,拒绝到店换酒店</li>
|
||
</ul>
|
||
<h3>🍜 舌尖体验</h3>
|
||
<ul>
|
||
<li>${ctx.dest === '云南' ? '野生菌火锅 + 白族风味餐 + 过桥米线' : '当地特色风味餐 ' + 3 + ' 次'}</li>
|
||
</ul>
|
||
<h3>🛡️ 服务保障</h3>
|
||
<ul>
|
||
<li>持证金牌导游 + 24h 专属管家</li>
|
||
<li>未成团提前 3 天全额退款,出行无忧</li>
|
||
</ul>
|
||
<div class="img-slot">🖼️ 此处建议插入:行程地图 / 实拍酒店 / 餐饮实拍(见「配图建议」)</div>`;
|
||
return {
|
||
pre: `已生成结构化商品详情(亮点/住宿/餐饮/保障 + 配图占位),可直接在编辑器中继续润色:`,
|
||
card: `<div class="gen-card">
|
||
<div class="gc-head">✨ 商品详情(图文结构)</div>
|
||
<div class="gc-body"><span class="line"><b>行程亮点</b> · 0购物0自费 / 经典IP全含 / ${ctx.theme}节奏</span><span class="line"><b>住宿安排</b> · ${ctx.dest === '云南' ? '海景房+古城客栈' : '高分酒店'}</span><span class="line"><b>舌尖体验</b> · ${ctx.dest === '云南' ? '野生菌火锅+风味餐' : '特色餐 3 次'}</span><span class="line"><b>服务保障</b> · 金牌导游 / 24h管家 / 全额退款承诺</span><span class="line"><b>配图占位</b> · 行程地图 / 酒店实拍 / 餐饮实拍</span></div>
|
||
<div class="gc-foot">
|
||
<button class="gc-btn" onclick="applyGen('detail')">⚡ 写入「商品详情」编辑器</button>
|
||
<button class="gc-btn ghost" onclick="regen('detail')">🔄 换一版</button>
|
||
</div>
|
||
</div>`,
|
||
data: html
|
||
};
|
||
},
|
||
image() {
|
||
readForm();
|
||
const list = [
|
||
{ label: `${ctx.dest}全景主图(横版大图)`, use: '主图' },
|
||
{ label: '酒店实拍·房间内饰', use: '详情' },
|
||
{ label: '核心景区打卡机位', use: '详情' },
|
||
{ label: '特色餐食实拍', use: '详情' },
|
||
{ label: `${ctx.theme}互动瞬间抓拍`, use: '详情' },
|
||
{ label: '行程地图示意', use: '详情' }
|
||
];
|
||
return {
|
||
pre: `根据行程内容生成 <b>6 张配图拍摄脚本</b>(含用途与构图建议):`,
|
||
card: `<div class="gen-card">
|
||
<div class="gc-head">✨ 配图清单(转化率优选顺序)</div>
|
||
<div class="gc-body">${list.map((im, i) => `<span class="line"><b>${i + 1}.</b> ${im.label} <span style="color:#86909c;font-size:11px">[${im.use}]</span></span>`).join('')}</div>
|
||
<div class="gc-foot">
|
||
<button class="gc-btn" onclick="applyGen('image')">⚡ 按清单添加到「商品图片」</button>
|
||
<button class="gc-btn ghost" onclick="regen('image')">🔄 换一版</button>
|
||
</div>
|
||
<div class="tips-row">
|
||
<span class="tip-pill ok-pill">主图含地标,点击率 +18%</span>
|
||
<span class="tip-pill">实拍图 > 精修图,差评更少</span>
|
||
</div>
|
||
</div>`,
|
||
data: list.map(x => ({ label: x.use === '主图' ? x.label : x.label.replace(/(.*)/, '') }))
|
||
};
|
||
},
|
||
itinerary() {
|
||
readForm();
|
||
const isYn = ctx.dest === '云南';
|
||
const days = [
|
||
{ title: isYn ? '抵达昆明·专车接站' : `抵达${ctx.dest}·专车接站`, desc: '落地即接,24h 接站服务,入住酒店后自由活动,推荐逛逛翠湖公园。' },
|
||
{ title: isYn ? '石林风景区 → 楚雄' : '核心景区深度游', desc: '上午游览 5A 石林,午后赴楚雄,晚观彝族歌舞表演。' },
|
||
{ title: isYn ? '大理古城 → 洱海游船 → 双廊' : '慢节奏主题日', desc: '上午漫步大理古城,午后洱海游船,双廊海边下午茶,看日落。' },
|
||
{ title: isYn ? '丽江古城 → 玉龙雪山 → 蓝月谷' : '自由活动日', desc: '索道登玉龙雪山4506m,蓝月谷拍照,晚逛丽江四方街。' },
|
||
{ title: isYn ? '束河古镇 → 返回昆明' : '自由活动·休整', desc: '睡到自然醒,逛束河古镇,午后大巴返回昆明。' },
|
||
{ title: '返程日·专车送站', desc: '根据航班时间安排送站,行程圆满结束。' }
|
||
].slice(0, parseInt(ctx.days) || 6);
|
||
return {
|
||
pre: `按 <span class="hl">${ctx.days}·${ctx.type}·${ctx.theme}</span> 节奏规划每日行程,张弛有度:`,
|
||
card: `<div class="gen-card">
|
||
<div class="gc-head">✨ 行程框架(每日含主题与安排)</div>
|
||
<div class="gc-body">${days.map((d, i) => `<span class="line"><b>D${i + 1}</b> ${d.title}</span>`).join('')}</div>
|
||
<div class="gc-foot">
|
||
<button class="gc-btn" onclick="applyGen('itinerary')">⚡ 填入「行程编辑」</button>
|
||
<button class="gc-btn ghost" onclick="regen('itinerary')">🔄 换一版</button>
|
||
</div>
|
||
<div class="tips-row">
|
||
<span class="tip-pill ok-pill">每日行车 ≤ 3.5h,适合${ctx.theme}</span>
|
||
<span class="tip-pill">D1/D6 只做接驳,差评率更低</span>
|
||
</div>
|
||
</div>`,
|
||
data: days
|
||
};
|
||
},
|
||
price() {
|
||
readForm();
|
||
const rows = [
|
||
{ date: '2026-09-12', adult: 3299, child: 2299, stock: 18, on: true },
|
||
{ date: '2026-09-26', adult: 3499, child: 2399, stock: 12, on: true },
|
||
{ date: '2026-10-01', adult: 4199, child: 2899, stock: 25, on: true },
|
||
{ date: '2026-10-03', adult: 4199, child: 2899, stock: 5, on: true },
|
||
{ date: '2026-10-17', adult: 3399, child: 2349, stock: 20, on: true }
|
||
];
|
||
return {
|
||
pre: `基于近 30 天 <span class="hl">${ctx.dest}·${ctx.days}·${ctx.type}</span> 成交数据,生成 5 个团期排期与定价(含国庆档):`,
|
||
card: `<div class="gen-card">
|
||
<div class="gc-head">✨ 价格库存方案</div>
|
||
<div class="gc-body"><span class="line">📊 市场区间:<b>¥2,899-¥4,199</b>(${ctx.theme}均价 ¥3,480)</span><span class="line">🎯 平季定价:<b>¥3,299</b>(低于均价 5%,点击率预估 +23%)</span><span class="line">📈 国庆档:<b>¥4,199</b>·库存充足(预计售罄率 85%)</span><span class="line">💡 策略:首月立减 ¥300 冲新品榜,稳定后回调</span></div>
|
||
<div class="gc-foot">
|
||
<button class="gc-btn" onclick="applyGen('price')">⚡ 填入「价格库存」表</button>
|
||
<button class="gc-btn ghost" onclick="regen('price')">🔄 换一版</button>
|
||
</div>
|
||
<div class="tips-row">
|
||
<span class="tip-pill ok-pill">低于均价 5%,排序更优</span>
|
||
<span class="tip-pill warn-pill">10-03 团期库存偏低,建议 +10</span>
|
||
</div>
|
||
</div>`,
|
||
data: rows
|
||
};
|
||
},
|
||
seo() {
|
||
readForm();
|
||
const kw = `${ctx.dest}旅游,${ctx.dest}${ctx.days},${ctx.type},${ctx.theme}旅游,${ctx.dest}纯玩无购物,${ctx.dest}自由行攻略`;
|
||
return {
|
||
pre: `提取 <b>6 个高流量关键词</b>,按搜索热度排序:`,
|
||
card: `<div class="gen-card">
|
||
<div class="gc-head">✨ SEO 关键词(月均搜索指数)</div>
|
||
<div class="gc-body">${kw.split(',').map((k, i) => `<span class="line"><b>${i + 1}.</b> ${k} <span style="color:#86909c;font-size:11px">热度 ${[8200, 6500, 5400, 3900, 2800, 1900][i]}</span></span>`).join('')}</div>
|
||
<div class="gc-foot">
|
||
<button class="gc-btn" onclick="toast('SEO 词已复制(演示)')">📋 复制关键词</button>
|
||
<button class="gc-btn ghost" onclick="regen('seo')">🔄 换一版</button>
|
||
</div>
|
||
</div>`,
|
||
data: kw
|
||
};
|
||
},
|
||
check() {
|
||
return {
|
||
pre: `已对 6 大模块进行 <b>广告法 + 平台规则</b> 双重合规扫描:`,
|
||
card: `<div class="gen-card">
|
||
<div class="gc-head">🛡️ 合规检查报告</div>
|
||
<div class="gc-body">
|
||
<span class="line">✅ 标题:无「最」「第一」等极限词</span>
|
||
<span class="line">✅ 详细介绍:无「国家级」「顶级」违禁表述</span>
|
||
<span class="line">⚠️ 建议:「金牌导游」表述需补充资质证明材料</span>
|
||
<span class="line">⚠️ 建议:「全额退款」承诺需与退改政策页一致</span>
|
||
<span class="line">ℹ️ 提示:主图 800×600 以上,避免文字遮挡地标</span>
|
||
</div>
|
||
<div class="tips-row">
|
||
<span class="tip-pill ok-pill">极限词 0 风险</span>
|
||
<span class="tip-pill warn-pill">2 项待补充</span>
|
||
<span class="tip-pill">审核通过率预估 96%</span>
|
||
</div>
|
||
</div>`
|
||
};
|
||
}
|
||
};
|
||
|
||
const actionLabel = {
|
||
title: '生成商品标题', intro: '生成详细介绍', detail: '生成商品详情', image: '配图建议',
|
||
itinerary: '规划行程', price: '价格库存方案', seo: 'SEO 关键词', check: '合规检查'
|
||
};
|
||
|
||
/* 表单模块内嵌 AI 按钮入口:面板收起时自动展开,再触发对应工具 */
|
||
function formAI(type) {
|
||
const panel = $('aiPanel');
|
||
if (panel.classList.contains('collapsed')) panel.classList.remove('collapsed');
|
||
quickAction(type);
|
||
}
|
||
|
||
async function quickAction(type, silent) {
|
||
readForm();
|
||
if (!silent) addMsg('user', '点击了「' + actionLabel[type] + '」');
|
||
const tp = typing();
|
||
await delay(900 + Math.random() * 600);
|
||
const g = genData[type]();
|
||
generated[type] = { data: g.data, used: 0 };
|
||
replaceTyping(tp, g.pre + g.card);
|
||
}
|
||
|
||
/* ================= 串行确认机制 ================= */
|
||
let serialState = null; // { resolve, timer, msgEl, prefix, index, type, autoMode }
|
||
let autoConfirmMode = true; // 默认自动确认
|
||
|
||
function buildConfirmBar(prefix, index, type, autoMode) {
|
||
const hasApply = !!applyMap[type];
|
||
const label = hasApply ? '✅ 确认填入' : '✅ 确认';
|
||
const cd = autoMode
|
||
? `<span class="ag-cd" id="cd${prefix}${index}">5s 后自动确认</span>`
|
||
: `<span class="ag-cd manual">⏸ 等待您确认…</span>`;
|
||
return `<div class="ag-confirm-bar" id="cb${prefix}${index}">
|
||
<button class="ag-cf-btn" onclick="serialConfirm()">${label}</button>
|
||
<button class="ag-regen-btn" onclick="serialRegen()">🔄 换一版</button>
|
||
${cd}
|
||
</div>`;
|
||
}
|
||
|
||
function startSerialCountdown() {
|
||
if (!serialState || !serialState.autoMode) return;
|
||
let remaining = 5;
|
||
const cdEl = $('cd' + serialState.prefix + serialState.index);
|
||
serialState.timer = setInterval(() => {
|
||
remaining--;
|
||
if (remaining <= 0) {
|
||
clearInterval(serialState.timer);
|
||
serialState.timer = null;
|
||
const { resolve } = serialState;
|
||
serialState = null;
|
||
resolve();
|
||
} else if (cdEl) {
|
||
cdEl.textContent = remaining + 's 后自动确认';
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
async function showStepCard(prefix, index, type, autoMode) {
|
||
const tp = typing();
|
||
await delay(500 + Math.random() * 400);
|
||
const g = genData[type]();
|
||
generated[type] = { data: g.data, used: 0 };
|
||
replaceTyping(tp, '<div class="serial-card">' + g.pre + g.card + buildConfirmBar(prefix, index, type, autoMode) + '</div>');
|
||
await new Promise(resolve => {
|
||
serialState = { resolve, timer: null, msgEl: tp, prefix, index, type, autoMode };
|
||
if (autoMode) startSerialCountdown();
|
||
});
|
||
}
|
||
|
||
function serialConfirm() {
|
||
if (!serialState) return;
|
||
if (serialState.timer) { clearInterval(serialState.timer); serialState.timer = null; }
|
||
// 标记确认栏为已确认
|
||
const bar = $('cb' + serialState.prefix + serialState.index);
|
||
if (bar) {
|
||
bar.style.opacity = '.55';
|
||
bar.innerHTML = '<span style="color:#22c55e;font-size:12px;font-weight:600;">✓ 已确认,填入表单中…</span>';
|
||
}
|
||
const { resolve } = serialState;
|
||
serialState = null;
|
||
resolve();
|
||
}
|
||
|
||
function serialRegen() {
|
||
if (!serialState) return;
|
||
if (serialState.timer) { clearInterval(serialState.timer); serialState.timer = null; }
|
||
const { msgEl, prefix, index, type, autoMode } = serialState;
|
||
readForm();
|
||
const g = genData[type]();
|
||
generated[type] = { data: g.data, used: 0 };
|
||
replaceTyping(msgEl, '<div class="serial-card">' + g.pre + g.card + buildConfirmBar(prefix, index, type, autoMode) + '</div>');
|
||
if (autoMode) startSerialCountdown();
|
||
}
|
||
|
||
function setAutoConfirm(val) {
|
||
autoConfirmMode = val;
|
||
document.querySelectorAll('.ag-mt-btn').forEach(b => {
|
||
b.classList.toggle('active', (b.dataset.auto === 'true') === val);
|
||
});
|
||
if (!serialState) return;
|
||
if (serialState.timer) { clearInterval(serialState.timer); serialState.timer = null; }
|
||
serialState.autoMode = val;
|
||
// 替换确认栏
|
||
const oldBar = $('cb' + serialState.prefix + serialState.index);
|
||
if (oldBar) oldBar.outerHTML = buildConfirmBar(serialState.prefix, serialState.index, serialState.type, val);
|
||
if (val) startSerialCountdown();
|
||
}
|
||
|
||
/* ================= 自主发品 Agent(对话式多工具编排) ================= */
|
||
const agentSteps = [
|
||
{ label: '商品标题', type: 'title', note: '3 方案·已选高转化款填入' },
|
||
{ label: '详细介绍', type: 'intro', note: '四段式卖点已填入' },
|
||
{ label: '商品详情', type: 'detail', note: '图文结构已写入编辑器' },
|
||
{ label: '商品图片', type: 'image', note: '6 张配图清单已添加' },
|
||
{ label: '行程编辑', type: 'itinerary', note: '逐日行程已填入' },
|
||
{ label: '价格库存', type: 'price', note: '5 个团期 + 定价已填入' },
|
||
{ label: 'SEO 关键词', type: 'seo', note: '6 个高流量词已提取' },
|
||
{ label: '合规检查', type: 'check', note: '扫描完成·0 极限词风险' }
|
||
];
|
||
|
||
/* 从自然语言里提取配置并回写左侧表单 */
|
||
function parseBrief(t) {
|
||
const o = {};
|
||
const destMap = [['云南','云南(昆明/大理/丽江)'],['四川','四川(成都/九寨沟)'],['海南','海南(三亚)'],['三亚','海南(三亚)'],['新疆','新疆(乌鲁木齐/喀纳斯)'],['西藏','西藏(拉萨/林芝)']];
|
||
for (const [k, v] of destMap) if (t.includes(k)) { o.dest = v; o.destShort = k === '三亚' ? '海南' : k; break; }
|
||
for (const ty of ['跟团游','自由行','定制游','半自助游','当地玩乐']) if (t.includes(ty)) { o.type = ty; break; }
|
||
const thMap = [['亲子','亲子家庭'],['蜜月','蜜月情侣'],['银发','银发无忧'],['摄影','摄影爱好'],['小众','小众深度'],['深度','小众深度']];
|
||
for (const [k, v] of thMap) if (t.includes(k)) { o.theme = v; break; }
|
||
const dm = t.match(/(\d+)\s*[天日]/);
|
||
if (dm) {
|
||
const n = +dm[1];
|
||
o.days = ['3天2晚','5天4晚','6天5晚','7天6晚','8天7晚'].find(d => d.startsWith(n + '天')) || (n + '天' + (n - 1) + '晚');
|
||
}
|
||
return o;
|
||
}
|
||
|
||
function applyBriefToForm(b) {
|
||
if (b.dest) $('f-dest').value = b.dest;
|
||
if (b.days) $('f-days').value = b.days;
|
||
if (b.type) $('f-type').value = b.type;
|
||
if (b.theme) document.querySelectorAll('#f-tags .chip').forEach(x => x.classList.toggle('active', x.textContent === b.theme));
|
||
readForm();
|
||
}
|
||
|
||
/* 判断是否为“自主发品”意图 */
|
||
const agentIntentRe = /(发品|发布|上架|帮我发|全部生成|全都|六大模块|6大模块|30秒|搞定|一键发)/;
|
||
|
||
async function agentRun(rawText) {
|
||
const b = parseBrief(rawText);
|
||
applyBriefToForm(b);
|
||
|
||
const destShort = ctx.dest.replace(/(.*)/, '');
|
||
const ctxLine = `${ctx.dest} · ${ctx.days} · ${ctx.type} · ${ctx.theme}`;
|
||
addMsg('ai', `🚀 好的,我来自主完成这次发品。<br>已识别配置:<b>${ctxLine}</b>${b.dest ? '(来自您的描述)' : '(沿用当前表单)'}。<br>我将依次调用 <b>8 个工具</b>,每个工具生成后<b>展示卡片等您确认</b>再填入表单:`);
|
||
|
||
// 任务清单卡片
|
||
const card = addMsg('ai',
|
||
`<div class="agent-card">
|
||
<div class="ag-head">🤖 自主发品任务清单</div>
|
||
<div class="ag-ctx">📋 上下文:${ctxLine} | 预计 8 步</div>
|
||
<div class="ag-mode-row">
|
||
<span class="ag-mode-label">确认模式</span>
|
||
<button class="ag-mt-btn ${autoConfirmMode ? 'active' : ''}" data-auto="true" onclick="setAutoConfirm(true)">⚡ 自动确认</button>
|
||
<button class="ag-mt-btn ${!autoConfirmMode ? 'active' : ''}" data-auto="false" onclick="setAutoConfirm(false)">✋ 手动确认</button>
|
||
</div>
|
||
<div class="ag-steps" id="agSteps">` +
|
||
agentSteps.map((s, i) => `<div class="ag-step todo" id="agS${i}"><span class="ag-ico">·</span><span class="ag-lbl">${s.label}</span><span class="ag-note">等待中</span></div>`).join('') +
|
||
`</div>
|
||
<div class="ag-foot">💡 每个工具生成后展示卡片,自动模式 5s 后自动确认填入,手动模式需您点击确认。可随时切换模式。</div>
|
||
</div>`);
|
||
|
||
for (let i = 0; i < agentSteps.length; i++) {
|
||
const s = agentSteps[i];
|
||
const el = $('agS' + i);
|
||
el.className = 'ag-step doing';
|
||
el.querySelector('.ag-ico').textContent = '↻';
|
||
el.querySelector('.ag-note').textContent = '生成中…';
|
||
await showStepCard('ag', i, s.type, autoConfirmMode);
|
||
if (applyMap[s.type]) applyMap[s.type]();
|
||
el.className = 'ag-step done';
|
||
el.querySelector('.ag-ico').textContent = '✓';
|
||
el.querySelector('.ag-note').textContent = s.note;
|
||
}
|
||
|
||
// 沉淀偏好(与一键发品共用记忆,越用越好用)
|
||
const newPrefs = [
|
||
`${destShort}路线偏好:${ctx.days}${ctx.type}`,
|
||
`「${ctx.theme}」人群文案风格`,
|
||
'纯玩无购物话术偏好',
|
||
'平季低于市场 5% 定价策略',
|
||
'首月立减冲榜促销习惯'
|
||
];
|
||
ocStore.count++;
|
||
ocStore.prefs = ocStore.prefs.concat(newPrefs);
|
||
ocStore.last = {
|
||
dest: destShort, days: ctx.days, type: ctx.type, theme: ctx.theme,
|
||
destSel: ctx.dest, daysSel: ctx.days, typeSel: ctx.type,
|
||
styleWord: ocStore.count >= 3 ? `${ctx.theme}友好·${ocStore.count >= 10 ? '爆款结构复刻' : '您的惯用话术'}` : '纯玩无购物'
|
||
};
|
||
saveOC();
|
||
|
||
$('formArea').scrollTo({ top: 0, behavior: 'smooth' });
|
||
const lv = ocLevelInfo();
|
||
addMsg('ai', `✅ <b>自主发品完成!</b>6 大模块已全部填入左侧表单,另完成 SEO 关键词提取与合规扫描。<br>本次沉淀偏好 <b>${newPrefs.length} 条</b>,累计发品 <b>${ocStore.count} 次</b>(${lv.name})。<br>可直接点顶部「提交审核」,或对任一模块点「🔄 换一版」微调。`);
|
||
}
|
||
|
||
/* ================= 多工具串行编排(部分模块自主调度) ================= */
|
||
const toolKeywords = [
|
||
{ type: 'title', kws: ['标题', '名字'] },
|
||
{ type: 'intro', kws: ['介绍', '卖点'] },
|
||
{ type: 'detail', kws: ['详情'] },
|
||
{ type: 'image', kws: ['图', '配图', '拍摄'] },
|
||
{ type: 'itinerary', kws: ['行程', '路线', '规划'] },
|
||
{ type: 'price', kws: ['价', '定价', '库存', '团期', '多少钱'] },
|
||
{ type: 'seo', kws: ['seo', '关键词', '搜索'] },
|
||
{ type: 'check', kws: ['合规', '违禁', '广告法'] },
|
||
];
|
||
|
||
/* 从自然语言中提取用户提到的工具(去重、保序) */
|
||
function parseMultiTools(text) {
|
||
const lower = text.toLowerCase();
|
||
const found = [], seen = new Set();
|
||
for (const { type, kws } of toolKeywords) {
|
||
if (seen.has(type)) continue;
|
||
if (kws.some(k => lower.includes(k))) { found.push(type); seen.add(type); }
|
||
}
|
||
return found;
|
||
}
|
||
|
||
/* 多工具串行编排:仅跑用户指定的模块,非全量发品 */
|
||
async function multiToolRun(rawText, toolTypes) {
|
||
const b = parseBrief(rawText);
|
||
if (Object.keys(b).length) applyBriefToForm(b);
|
||
|
||
const destShort = ctx.dest.replace(/(.*)/, '');
|
||
const ctxLine = `${ctx.dest} · ${ctx.days} · ${ctx.type} · ${ctx.theme}`;
|
||
const steps = agentSteps.filter(s => toolTypes.includes(s.type));
|
||
const labels = steps.map(s => s.label).join(' → ');
|
||
|
||
addMsg('ai', `🛠️ 好的,我来自主调度 <b>${steps.length}</b> 个模块。<br>已识别上下文:<b>${ctxLine}</b>${b.dest ? '(来自您的描述)' : '(沿用当前表单)'}。<br>将依次执行:<b>${labels}</b>,每个工具生成后<b>展示卡片等您确认</b>再填入表单:`);
|
||
|
||
// 任务清单卡片
|
||
const card = addMsg('ai',
|
||
`<div class="agent-card">
|
||
<div class="ag-head">🛠️ 多工具串行任务</div>
|
||
<div class="ag-ctx">📋 ${ctxLine} | 共 ${steps.length} 步</div>
|
||
<div class="ag-mode-row">
|
||
<span class="ag-mode-label">确认模式</span>
|
||
<button class="ag-mt-btn ${autoConfirmMode ? 'active' : ''}" data-auto="true" onclick="setAutoConfirm(true)">⚡ 自动确认</button>
|
||
<button class="ag-mt-btn ${!autoConfirmMode ? 'active' : ''}" data-auto="false" onclick="setAutoConfirm(false)">✋ 手动确认</button>
|
||
</div>
|
||
<div class="ag-steps" id="mtSteps">` +
|
||
steps.map((s, i) => `<div class="ag-step todo" id="mtS${i}"><span class="ag-ico">·</span><span class="ag-lbl">${s.label}</span><span class="ag-note">等待中</span></div>`).join('') +
|
||
`</div>
|
||
<div class="ag-foot">💡 每个工具生成后展示卡片,自动模式 5s 后自动确认填入,手动模式需您点击确认。可随时切换模式。</div>
|
||
</div>`);
|
||
|
||
for (let i = 0; i < steps.length; i++) {
|
||
const s = steps[i];
|
||
const el = $('mtS' + i);
|
||
el.className = 'ag-step doing';
|
||
el.querySelector('.ag-ico').textContent = '↻';
|
||
el.querySelector('.ag-note').textContent = '生成中…';
|
||
await showStepCard('mt', i, s.type, autoConfirmMode);
|
||
if (applyMap[s.type]) applyMap[s.type]();
|
||
el.className = 'ag-step done';
|
||
el.querySelector('.ag-ico').textContent = '✓';
|
||
el.querySelector('.ag-note').textContent = s.note;
|
||
}
|
||
|
||
// 滚动到第一个修改的表单模块
|
||
const firstType = steps[0].type;
|
||
const cardMap = { title: 'card-title', intro: 'card-intro', detail: 'card-detail', image: 'card-img', itinerary: 'card-itinerary', price: 'card-price' };
|
||
if (cardMap[firstType]) $(cardMap[firstType]).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
|
||
addMsg('ai', `✅ <b>${steps.length} 个模块生成完毕!</b>${steps.map(s => s.label).join('、')} 已填入左侧表单。<br>不满意可点对应模块的「🔄 换一版」微调,或说「<b>帮我把这个商品发布了</b>」让我一口气完成全部模块。`);
|
||
}
|
||
|
||
/* ================= 自由对话 ================= */
|
||
const canned = [
|
||
{ k: ['标题', '名字'], f: () => quickAction('title') },
|
||
{ k: ['介绍', '卖点'], f: () => quickAction('intro') },
|
||
{ k: ['详情'], f: () => quickAction('detail') },
|
||
{ k: ['图', '配图', '拍摄'], f: () => quickAction('image') },
|
||
{ k: ['行程', '规划', '路线'], f: () => quickAction('itinerary') },
|
||
{ k: ['价', '定价', '库存', '团期', '多少钱'], f: () => quickAction('price') },
|
||
{ k: ['seo', '关键词', '搜索'], f: () => quickAction('seo') },
|
||
{ k: ['合规', '违禁', '广告法'], f: () => quickAction('check') },
|
||
];
|
||
|
||
async function sendMessage() {
|
||
const input = $('chatInput');
|
||
const text = input.value.trim();
|
||
if (!text) return;
|
||
// 如果有串行步骤在等待确认,自动确认后继续处理新消息
|
||
if (serialState) serialConfirm();
|
||
input.value = '';
|
||
addMsg('user', esc(text));
|
||
const tp = typing();
|
||
await delay(700);
|
||
// 优先:多工具串行(用户同时提到 2+ 模块)
|
||
const tools = parseMultiTools(text);
|
||
if (tools.length >= 2) {
|
||
replaceTyping(tp, `明白,我来自主调度 ${tools.length} 个模块 🛠️`);
|
||
await delay(400);
|
||
multiToolRun(text, tools);
|
||
return;
|
||
}
|
||
// 全量自主发品
|
||
if (agentIntentRe.test(text)) {
|
||
replaceTyping(tp, '明白,我来自主完成这次发品 🚀');
|
||
await delay(400);
|
||
agentRun(text);
|
||
return;
|
||
}
|
||
const hit = canned.find(c => c.k.some(k => text.toLowerCase().includes(k)));
|
||
if (hit) {
|
||
replaceTyping(tp, '好的,马上为您处理 👇');
|
||
await delay(400);
|
||
hit.f();
|
||
return;
|
||
}
|
||
const replies = [
|
||
`收到!我已读取当前表单:目的地 <span class="hl">${ctx.dest}</span>、类型 <span class="hl">${ctx.type}</span>。您可以点上方快捷按钮生成内容,或直接说需求,例如:<br>·「帮我写个适合${ctx.theme}的标题」<br>·「国庆团期怎么定价」`,
|
||
`关于「${esc(text.slice(0, 12))}${text.length > 12 ? '…' : ''}」,建议先在左侧表单选好目的地和天数,我生成的内容会更精准。也可以试试快捷区的 <b>价格库存</b>,我会基于市场数据给出排期方案~`,
|
||
`我可以帮您拆解:先定目标人群(${ctx.theme}?蜜月?),再定核心卖点(纯玩/性价比/体验感),然后按框架生成标题→介绍→详情→行程。直接点快捷按钮即可开始 ✨`,
|
||
];
|
||
replaceTyping(tp, replies[Math.floor(Math.random() * replies.length)]);
|
||
}
|
||
|
||
/* ================= 标题计数 / 标签选择 ================= */
|
||
$('f-title').addEventListener('input', function () {
|
||
$('titleCount').textContent = this.value.length;
|
||
});
|
||
document.querySelectorAll('#f-tags .chip').forEach(c => {
|
||
c.addEventListener('click', () => {
|
||
document.querySelectorAll('#f-tags .chip').forEach(x => x.classList.remove('active'));
|
||
c.classList.add('active');
|
||
});
|
||
});
|
||
document.querySelectorAll('#oc-tags .chip').forEach(c => {
|
||
c.addEventListener('click', () => {
|
||
document.querySelectorAll('#oc-tags .chip').forEach(x => x.classList.remove('active'));
|
||
c.classList.add('active');
|
||
});
|
||
});
|
||
|
||
/* ================= 面板开关 ================= */
|
||
function togglePanel() { $('aiPanel').classList.toggle('collapsed'); }
|
||
|
||
/* ================= 一键发品(越用越好用) ================= */
|
||
let ocStore = { count: 0, prefs: [], last: null };
|
||
try { ocStore = JSON.parse(localStorage.getItem('fliggy_oc') || 'null') || ocStore; } catch (e) {}
|
||
function saveOC() { try { localStorage.setItem('fliggy_oc', JSON.stringify(ocStore)); } catch (e) {} }
|
||
|
||
function ocLevelInfo() {
|
||
if (ocStore.count >= 10) return { name: 'Lv.3 爆款复刻', next: '已解锁:自动复用您的历史爆款结构与定价', pct: 100 };
|
||
if (ocStore.count >= 3) return { name: 'Lv.2 风格匹配', next: '再用 ' + (10 - ocStore.count) + ' 次解锁「Lv.3 爆款复刻」', pct: ocStore.count / 10 * 100 };
|
||
return { name: 'Lv.1 通用模板', next: '再用 ' + (3 - ocStore.count) + ' 次解锁「Lv.2 风格匹配」(生成将贴合您的风格)', pct: ocStore.count / 10 * 100 };
|
||
}
|
||
|
||
function openOC() {
|
||
// 按学习到的偏好预填
|
||
const last = ocStore.last;
|
||
if (last) {
|
||
$('oc-dest').value = last.destSel || $('oc-dest').value;
|
||
$('oc-days').value = last.daysSel || $('oc-days').value;
|
||
$('oc-type').value = last.typeSel || $('oc-type').value;
|
||
document.querySelectorAll('#oc-tags .chip').forEach(c =>
|
||
c.classList.toggle('active', c.textContent === last.theme));
|
||
}
|
||
const hint = $('ocLearn');
|
||
if (ocStore.count > 0 && last) {
|
||
hint.style.display = 'block';
|
||
hint.innerHTML = `🧠 <b>越用越好用</b>:已根据您最近 ${ocStore.count} 次发品习惯<b>自动预填</b>(${last.dest}·${last.days}·${last.type}·${last.theme}),生成内容将沿用您偏好的「${last.styleWord}」风格。`;
|
||
} else {
|
||
hint.style.display = 'none';
|
||
}
|
||
const lv = ocLevelInfo();
|
||
$('ocCount').textContent = ocStore.count;
|
||
$('ocPrefN').textContent = ocStore.prefs.length;
|
||
$('ocLevel').textContent = lv.name;
|
||
$('ocLevelNext').textContent = lv.next;
|
||
$('ocBar').style.width = lv.pct + '%';
|
||
$('ocConfig').style.display = 'block';
|
||
$('ocProgress').style.display = 'none';
|
||
$('ocDone').style.display = 'none';
|
||
$('ocMask').classList.add('show');
|
||
}
|
||
function closeOC() { $('ocMask').classList.remove('show'); }
|
||
|
||
const ocStepsDef = [
|
||
{ label: '商品标题', type: 'title', note: '已选高转化方案' },
|
||
{ label: '详细介绍', type: 'intro', note: '四段式卖点' },
|
||
{ label: '商品详情', type: 'detail', note: '图文结构就绪' },
|
||
{ label: '商品图片', type: 'image', note: '配图清单 6 张' },
|
||
{ label: '行程编辑', type: 'itinerary',note: '逐日框架就绪' },
|
||
{ label: '价格库存', type: 'price', note: '团期+定价就绪' }
|
||
];
|
||
|
||
async function startOC() {
|
||
const destSel = $('oc-dest').value, daysSel = $('oc-days').value, typeSel = $('oc-type').value;
|
||
const themeEl = document.querySelector('#oc-tags .chip.active');
|
||
const theme = themeEl ? themeEl.textContent : '亲子家庭';
|
||
// 同步配置到左侧表单(生成依赖表单上下文)
|
||
$('f-dest').value = destSel;
|
||
$('f-days').value = daysSel;
|
||
$('f-type').value = typeSel;
|
||
document.querySelectorAll('#f-tags .chip').forEach(x =>
|
||
x.classList.toggle('active', x.textContent === theme));
|
||
|
||
$('ocConfig').style.display = 'none';
|
||
$('ocProgress').style.display = 'block';
|
||
$('ocSteps').innerHTML = ocStepsDef.map((s, i) =>
|
||
`<div class="step-item" id="ocS${i}"><span class="s-icon">·</span><span>${s.label}</span><span class="s-note">等待中</span></div>`).join('');
|
||
|
||
silentBatch = true;
|
||
for (let i = 0; i < ocStepsDef.length; i++) {
|
||
const s = ocStepsDef[i];
|
||
const el = $('ocS' + i);
|
||
el.className = 'step-item doing';
|
||
el.querySelector('.s-icon').textContent = '↻';
|
||
el.querySelector('.s-note').textContent = '生成中…';
|
||
await delay(650 + Math.random() * 450);
|
||
const g = genData[s.type]();
|
||
generated[s.type] = { data: g.data, used: 0 };
|
||
applyMap[s.type]();
|
||
el.className = 'step-item done';
|
||
el.querySelector('.s-icon').textContent = '✓';
|
||
el.querySelector('.s-note').textContent = s.note;
|
||
}
|
||
silentBatch = false;
|
||
|
||
// 沉淀偏好(越用越好好的核心)
|
||
const dest = destSel.replace(/(.*)/, '');
|
||
const newPrefs = [
|
||
`${dest}路线偏好:${daysSel}${typeSel}`,
|
||
`「${theme}」人群文案风格`,
|
||
'纯玩无购物话术偏好',
|
||
'平季低于市场 5% 定价策略',
|
||
'首月立减冲榜促销习惯'
|
||
];
|
||
ocStore.count++;
|
||
ocStore.prefs = ocStore.prefs.concat(newPrefs);
|
||
ocStore.last = {
|
||
dest, days: daysSel, type: typeSel, theme,
|
||
destSel, daysSel, typeSel,
|
||
styleWord: ocStore.count >= 3 ? `${theme}友好·${ocStore.count >= 10 ? '爆款结构复刻' : '您的惯用话术'}` : '纯玩无购物'
|
||
};
|
||
saveOC();
|
||
|
||
$('ocProgress').style.display = 'none';
|
||
$('ocDone').style.display = 'block';
|
||
const lv = ocLevelInfo();
|
||
$('ocNewPref').innerHTML = `🧠 <b>本次为您沉淀 ${newPrefs.length} 条偏好</b>(累计 ${ocStore.prefs.length} 条):<br>` +
|
||
newPrefs.map(p => '· ' + p).join('<br>') +
|
||
`<br><b>${lv.name}</b> · ${lv.next}<br>下次一键发品将自动预填并按您的风格生成。`;
|
||
|
||
$('formArea').scrollTo({ top: 0, behavior: 'smooth' });
|
||
toast('6 大模块已生成完毕');
|
||
addMsg('ai', `已完成 <b>一键发品</b> ✅ 标题、介绍、详情、配图、行程、价格库存全部就位。<br>本次沉淀偏好 <b>${ocStore.prefs.length} 条</b>,累计发品 <b>${ocStore.count} 次</b>——用得越多,生成越贴合您的风格。<br>任何模块不满意,点对应快捷按钮「🔄 换一版」即可。`);
|
||
}
|
||
|
||
/* ================= 初始化 ================= */
|
||
renderImages();
|
||
renderItinerary();
|
||
renderPrice();
|
||
|
||
window.addEventListener('load', async () => {
|
||
const tp1 = addMsg('ai', '<div class="typing"><span></span><span></span><span></span></div>');
|
||
await delay(700);
|
||
replaceTyping(tp1, `您好,我是 <b>旅智小助</b> 🤖<br>我对应左侧 6 大表单模块,可以帮您:<br>· 📝 商品标题 — 3 个方案一键切换填入<br>· 💎 详细介绍 — 四段式卖点文案<br>· 📄 商品详情 — 结构化图文(含配图占位)<br>· 🖼️ 配图建议 — 6 张图拍摄脚本<br>· 🗺️ 行程编辑 — 逐日行程一键填入<br>· 💰 价格库存 — 团期排期 + 定价策略<br><br>💬 <b>自主发品</b>:说「<b>帮我优化标题和详情</b>」或「<b>帮我发个云南亲子 6 天跟团游</b>」,我会<b>逐个工具生成 → 展示卡片 → 等您确认</b>再填入表单(⚡自动 5s 确认,可切✋手动)~`);
|
||
await delay(600);
|
||
const tp2 = typing();
|
||
await delay(900);
|
||
replaceTyping(tp2, `💡 <b>演示提示</b>:<br>· 说「<b>帮我优化标题和详情</b>」→ 串行调度指定模块<br>· 说「<b>帮我发个海南亲子 6 天自由行</b>」→ 自主调用全部 8 个工具<br>· 每步<b>生成卡片 → 等您确认</b>再填入(⚡自动 5s / ✋手动可随时切换)<br>· 也可点 <b>⚡ 一键发品</b> 用弹窗方式完成<br>都会沉淀偏好,<b>用得越多越懂你</b>。`);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|