|
@@ -18,11 +18,11 @@
|
|
@for (product of projectProducts; track product.id) {
|
|
@for (product of projectProducts; track product.id) {
|
|
<button
|
|
<button
|
|
class="space-tab"
|
|
class="space-tab"
|
|
- [class.active]="activeProductId === product.id"
|
|
|
|
|
|
+ [class.active]="activeProductId == product.id"
|
|
(click)="selectProduct(product.id)">
|
|
(click)="selectProduct(product.id)">
|
|
<span class="space-icon">{{ getSpaceIcon(product.type) }}</span>
|
|
<span class="space-icon">{{ getSpaceIcon(product.type) }}</span>
|
|
<span>{{ getProductDisplayName(product) }}</span>
|
|
<span>{{ getProductDisplayName(product) }}</span>
|
|
- <span class="progress-indicator" [style.width.%]="calculateProductCompletion(product.id)"></span>
|
|
|
|
|
|
+ <span class="progress-indicator" [style.width]="calculateProductCompletion(product.id) + '%'"></span>
|
|
</button>
|
|
</button>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
@@ -34,20 +34,20 @@
|
|
<div class="segment-buttons">
|
|
<div class="segment-buttons">
|
|
<button
|
|
<button
|
|
class="segment-btn"
|
|
class="segment-btn"
|
|
- [class.active]="requirementsSegment === 'global'"
|
|
|
|
|
|
+ [class.active]="requirementsSegment == 'global'"
|
|
(click)="selectRequirementsSegment('global')">
|
|
(click)="selectRequirementsSegment('global')">
|
|
全局需求
|
|
全局需求
|
|
</button>
|
|
</button>
|
|
@if (isMultiProductProject) {
|
|
@if (isMultiProductProject) {
|
|
<button
|
|
<button
|
|
class="segment-btn"
|
|
class="segment-btn"
|
|
- [class.active]="requirementsSegment === 'spaces'"
|
|
|
|
|
|
+ [class.active]="requirementsSegment == 'spaces'"
|
|
(click)="selectRequirementsSegment('spaces')">
|
|
(click)="selectRequirementsSegment('spaces')">
|
|
产品需求
|
|
产品需求
|
|
</button>
|
|
</button>
|
|
<button
|
|
<button
|
|
class="segment-btn"
|
|
class="segment-btn"
|
|
- [class.active]="requirementsSegment === 'cross-space'"
|
|
|
|
|
|
+ [class.active]="requirementsSegment == 'cross-space'"
|
|
(click)="selectRequirementsSegment('cross-space')">
|
|
(click)="selectRequirementsSegment('cross-space')">
|
|
跨产品协调
|
|
跨产品协调
|
|
</button>
|
|
</button>
|
|
@@ -56,7 +56,7 @@
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- 全局需求 -->
|
|
<!-- 全局需求 -->
|
|
- @if (requirementsSegment === 'global') {
|
|
|
|
|
|
+ @if (requirementsSegment == 'global') {
|
|
<div class="global-requirements">
|
|
<div class="global-requirements">
|
|
<!-- 参考图片 -->
|
|
<!-- 参考图片 -->
|
|
<div class="card reference-images-card">
|
|
<div class="card reference-images-card">
|
|
@@ -66,8 +66,91 @@
|
|
参考图片
|
|
参考图片
|
|
</h3>
|
|
</h3>
|
|
<p class="card-subtitle">上传风格、空间或材质参考图</p>
|
|
<p class="card-subtitle">上传风格、空间或材质参考图</p>
|
|
|
|
+ <div class="ai-analysis-actions">
|
|
|
|
+ @if (referenceImages.length > 0 && canEdit) {
|
|
|
|
+ <button
|
|
|
|
+ class="btn btn-sm btn-primary"
|
|
|
|
+ (click)="analyzeReferenceImages()"
|
|
|
|
+ [disabled]="aiAnalyzingImages">
|
|
|
|
+ @if (aiAnalyzingImages) {
|
|
|
|
+ <div class="spinner-small">
|
|
|
|
+ <div class="spinner-circle"></div>
|
|
|
|
+ </div>
|
|
|
|
+ AI分析中...
|
|
|
|
+ } @else {
|
|
|
|
+ <ion-icon name="sparkles"></ion-icon>
|
|
|
|
+ AI分析图片
|
|
|
|
+ }
|
|
|
|
+ </button>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<div class="card-content">
|
|
|
|
+ <!-- AI图片分析结果 -->
|
|
|
|
+ @if (aiAnalysisResults.imageAnalysis && aiAnalysisResults.imageAnalysis.length > 0) {
|
|
|
|
+ <div class="ai-analysis-results">
|
|
|
|
+ <div class="analysis-header">
|
|
|
|
+ <span class="badge badge-success">
|
|
|
|
+ <ion-icon name="checkmark-circle"></ion-icon>
|
|
|
|
+ AI分析完成
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-grid">
|
|
|
|
+ @for (analysis of aiAnalysisResults.imageAnalysis; track analysis.imageId) {
|
|
|
|
+
|
|
|
|
+ @if (analysisImageMap[analysis.imageId]) {
|
|
|
|
+ <div class="analysis-item">
|
|
|
|
+ <div class="analysis-image">
|
|
|
|
+ <img [src]="analysisImageMap[analysis.imageId]?.url" [alt]="analysisImageMap[analysis.imageId]?.name" />
|
|
|
|
+ <div class="confidence-badge">
|
|
|
|
+ <span>AI识别置信度: {{ (analysis.confidence * 100).toFixed(1) }}%</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-content">
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>风格元素</h5>
|
|
|
|
+ <div class="tags">
|
|
|
|
+ @for (element of analysis.styleElements; track $index) {
|
|
|
|
+ <span class="badge badge-secondary">{{ element }}</span>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>色彩搭配</h5>
|
|
|
|
+ <div class="color-palette">
|
|
|
|
+ @for (color of analysis.colorPalette; track $index) {
|
|
|
|
+ <div class="color-swatch" [style.background-color]="color" [title]="color"></div>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>材质分析</h5>
|
|
|
|
+ <div class="tags">
|
|
|
|
+ @for (material of analysis.materialAnalysis; track $index) {
|
|
|
|
+ <span class="badge badge-tertiary">{{ material }}</span>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>布局特征</h5>
|
|
|
|
+ <div class="tags">
|
|
|
|
+ @for (feature of analysis.layoutFeatures; track $index) {
|
|
|
|
+ <span class="badge badge-outline">{{ feature }}</span>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>空间氛围</h5>
|
|
|
|
+ <p>{{ analysis.mood }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ }
|
|
|
|
+
|
|
<div class="images-grid">
|
|
<div class="images-grid">
|
|
@for (image of getFilteredReferenceImages(); track image.id) {
|
|
@for (image of getFilteredReferenceImages(); track image.id) {
|
|
<div class="image-item">
|
|
<div class="image-item">
|
|
@@ -79,6 +162,12 @@
|
|
@if (image.spaceId) {
|
|
@if (image.spaceId) {
|
|
<span class="badge badge-outline">{{ getProductDisplayNameById(image.spaceId || '') }}</span>
|
|
<span class="badge badge-outline">{{ getProductDisplayNameById(image.spaceId || '') }}</span>
|
|
}
|
|
}
|
|
|
|
+ @if (aiAnalysisResults.imageAnalysis?.find(a => a.imageId == image.id)) {
|
|
|
|
+ <span class="badge badge-success">
|
|
|
|
+ <ion-icon name="sparkles"></ion-icon>
|
|
|
|
+ 已分析
|
|
|
|
+ </span>
|
|
|
|
+ }
|
|
@if (canEdit) {
|
|
@if (canEdit) {
|
|
<button
|
|
<button
|
|
class="btn-icon btn-danger"
|
|
class="btn-icon btn-danger"
|
|
@@ -121,9 +210,107 @@
|
|
CAD文件
|
|
CAD文件
|
|
</h3>
|
|
</h3>
|
|
<p class="card-subtitle">上传户型图或施工图纸</p>
|
|
<p class="card-subtitle">上传户型图或施工图纸</p>
|
|
|
|
+ <div class="ai-analysis-actions">
|
|
|
|
+ @if (cadFiles.length > 0 && canEdit) {
|
|
|
|
+ <button
|
|
|
|
+ class="btn btn-sm btn-primary"
|
|
|
|
+ (click)="analyzeCADFiles()"
|
|
|
|
+ [disabled]="aiAnalyzingCAD">
|
|
|
|
+ @if (aiAnalyzingCAD) {
|
|
|
|
+ <div class="spinner-small">
|
|
|
|
+ <div class="spinner-circle"></div>
|
|
|
|
+ </div>
|
|
|
|
+ AI分析中...
|
|
|
|
+ } @else {
|
|
|
|
+ <ion-icon name="sparkles"></ion-icon>
|
|
|
|
+ AI分析CAD
|
|
|
|
+ }
|
|
|
|
+ </button>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<div class="card-content">
|
|
- @if (getFilteredCADFiles().length === 0) {
|
|
|
|
|
|
+ <!-- AI CAD分析结果 -->
|
|
|
|
+ @if (aiAnalysisResults.cadAnalysis && aiAnalysisResults.cadAnalysis.length > 0) {
|
|
|
|
+ <div class="ai-analysis-results">
|
|
|
|
+ <div class="analysis-header">
|
|
|
|
+ <span class="badge badge-success">
|
|
|
|
+ <ion-icon name="checkmark-circle"></ion-icon>
|
|
|
|
+ CAD分析完成
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="cad-analysis-grid">
|
|
|
|
+ @for (analysis of aiAnalysisResults.cadAnalysis; track analysis.fileId) {
|
|
|
|
+
|
|
|
|
+ @if (analysisFileMap[analysis.fileId]) {
|
|
|
|
+ <div class="cad-analysis-item">
|
|
|
|
+ <div class="cad-file-info">
|
|
|
|
+ <ion-icon name="document-text" class="file-icon"></ion-icon>
|
|
|
|
+ <div class="file-details">
|
|
|
|
+ <h4>{{ analysisFileMap[analysis.fileId]?.name }}</h4>
|
|
|
|
+ <p>{{ formatFileSize(analysisFileMap[analysis.fileId]?.size || 0) }}</p>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="cad-analysis-content">
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>空间结构</h5>
|
|
|
|
+ <div class="structure-info">
|
|
|
|
+ <div class="info-item">
|
|
|
|
+ <span class="label">总面积:</span>
|
|
|
|
+ <span class="value">{{ analysis.spaceStructure.totalArea }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="info-item">
|
|
|
|
+ <span class="label">房间数量:</span>
|
|
|
|
+ <span class="value">{{ analysis.spaceStructure.roomCount }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="info-item">
|
|
|
|
+ <span class="label">布局类型:</span>
|
|
|
|
+ <span class="value">{{ analysis.spaceStructure.layoutType }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>尺寸信息</h5>
|
|
|
|
+ <div class="dimensions-info">
|
|
|
|
+ <div class="info-item">
|
|
|
|
+ <span class="label">长度:</span>
|
|
|
|
+ <span class="value">{{ analysis.dimensions.length }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="info-item">
|
|
|
|
+ <span class="label">宽度:</span>
|
|
|
|
+ <span class="value">{{ analysis.dimensions.width }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="info-item">
|
|
|
|
+ <span class="label">层高:</span>
|
|
|
|
+ <span class="value">{{ analysis.dimensions.height }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>限制因素</h5>
|
|
|
|
+ <div class="tags">
|
|
|
|
+ @for (constraint of analysis.constraints; track $index) {
|
|
|
|
+ <span class="badge badge-warning">{{ constraint }}</span>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h5>优化机会</h5>
|
|
|
|
+ <div class="tags">
|
|
|
|
+ @for (opportunity of analysis.opportunities; track $index) {
|
|
|
|
+ <span class="badge badge-success">{{ opportunity }}</span>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @if (getFilteredCADFiles().length == 0) {
|
|
<div class="empty-state">
|
|
<div class="empty-state">
|
|
<ion-icon name="document-outline" class="icon-large"></ion-icon>
|
|
<ion-icon name="document-outline" class="icon-large"></ion-icon>
|
|
<p>暂无CAD文件</p>
|
|
<p>暂无CAD文件</p>
|
|
@@ -139,6 +326,12 @@
|
|
@if (file.spaceId) {
|
|
@if (file.spaceId) {
|
|
<span class="badge badge-outline">{{ getProductDisplayNameById(file.spaceId || '') }}</span>
|
|
<span class="badge badge-outline">{{ getProductDisplayNameById(file.spaceId || '') }}</span>
|
|
}
|
|
}
|
|
|
|
+ @if (aiAnalysisResults.cadAnalysis?.find(a => a.fileId == file.id)) {
|
|
|
|
+ <span class="badge badge-success">
|
|
|
|
+ <ion-icon name="sparkles"></ion-icon>
|
|
|
|
+ 已分析
|
|
|
|
+ </span>
|
|
|
|
+ }
|
|
</div>
|
|
</div>
|
|
@if (canEdit) {
|
|
@if (canEdit) {
|
|
<button
|
|
<button
|
|
@@ -314,10 +507,10 @@
|
|
}
|
|
}
|
|
|
|
|
|
<!-- 空间需求 -->
|
|
<!-- 空间需求 -->
|
|
- @if (requirementsSegment === 'spaces' && isMultiProductProject) {
|
|
|
|
|
|
+ @if (requirementsSegment == 'spaces' && isMultiProductProject) {
|
|
<div class="space-requirements">
|
|
<div class="space-requirements">
|
|
@for (space of projectProducts; track space.id) {
|
|
@for (space of projectProducts; track space.id) {
|
|
- <div class="card space-requirement-card" [class.active]="activeProductId === space.id">
|
|
|
|
|
|
+ <div class="card space-requirement-card" [class.active]="activeProductId == space.id">
|
|
<div class="card-header">
|
|
<div class="card-header">
|
|
<h3 class="card-title">
|
|
<h3 class="card-title">
|
|
<span class="space-icon">{{ getSpaceIcon(space.type) }}</span>
|
|
<span class="space-icon">{{ getSpaceIcon(space.type) }}</span>
|
|
@@ -383,7 +576,7 @@
|
|
}
|
|
}
|
|
|
|
|
|
<!-- 跨空间协调需求 -->
|
|
<!-- 跨空间协调需求 -->
|
|
- @if (requirementsSegment === 'cross-space' && isMultiProductProject) {
|
|
|
|
|
|
+ @if (requirementsSegment == 'cross-space' && isMultiProductProject) {
|
|
<div class="cross-space-requirements">
|
|
<div class="cross-space-requirements">
|
|
<div class="card">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="card-header">
|
|
@@ -397,7 +590,7 @@
|
|
</button>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<div class="card-content">
|
|
- @if (crossSpaceRequirements.length === 0) {
|
|
|
|
|
|
+ @if (crossSpaceRequirements.length == 0) {
|
|
<div class="empty-state">
|
|
<div class="empty-state">
|
|
<ion-icon name="links-outline" class="icon-large"></ion-icon>
|
|
<ion-icon name="links-outline" class="icon-large"></ion-icon>
|
|
<p>暂无跨空间协调需求</p>
|
|
<p>暂无跨空间协调需求</p>
|
|
@@ -434,6 +627,97 @@
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ <!-- 综合AI分析 -->
|
|
|
|
+ @if (aiAnalysisResults.comprehensiveAnalysis) {
|
|
|
|
+ <div class="card comprehensive-analysis-card">
|
|
|
|
+ <div class="card-header">
|
|
|
|
+ <h3 class="card-title">
|
|
|
|
+ <ion-icon name="analytics"></ion-icon>
|
|
|
|
+ 综合AI分析
|
|
|
|
+ </h3>
|
|
|
|
+ <span class="badge badge-success">
|
|
|
|
+ <ion-icon name="checkmark-circle"></ion-icon>
|
|
|
|
+ 分析完成
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="card-content">
|
|
|
|
+ <div class="comprehensive-analysis-content">
|
|
|
|
+ <!-- 整体风格 -->
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h4>整体风格定位</h4>
|
|
|
|
+ <p>{{ aiAnalysisResults.comprehensiveAnalysis.overallStyle }}</p>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- AI推荐色彩方案 -->
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h4>AI推荐色彩方案</h4>
|
|
|
|
+ <div class="ai-color-scheme">
|
|
|
|
+ <div class="color-item">
|
|
|
|
+ <div class="color-swatch" [style.background-color]="aiAnalysisResults.comprehensiveAnalysis.colorScheme.primary" [title]="aiAnalysisResults.comprehensiveAnalysis.colorScheme.primary"></div>
|
|
|
|
+ <span class="color-label">主色调</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="color-item">
|
|
|
|
+ <div class="color-swatch" [style.background-color]="aiAnalysisResults.comprehensiveAnalysis.colorScheme.secondary" [title]="aiAnalysisResults.comprehensiveAnalysis.colorScheme.secondary"></div>
|
|
|
|
+ <span class="color-label">副色调</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="color-item">
|
|
|
|
+ <div class="color-swatch" [style.background-color]="aiAnalysisResults.comprehensiveAnalysis.colorScheme.accent" [title]="aiAnalysisResults.comprehensiveAnalysis.colorScheme.accent"></div>
|
|
|
|
+ <span class="color-label">点缀色</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 材质推荐 -->
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h4>AI材质推荐</h4>
|
|
|
|
+ <div class="tags">
|
|
|
|
+ @for (material of aiAnalysisResults.comprehensiveAnalysis.materialRecommendations; track $index) {
|
|
|
|
+ <span class="badge badge-tertiary">{{ material }}</span>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 布局优化建议 -->
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h4>布局优化建议</h4>
|
|
|
|
+ <ul class="optimization-list">
|
|
|
|
+ @for (optimization of aiAnalysisResults.comprehensiveAnalysis.layoutOptimization; track $index) {
|
|
|
|
+ <li>{{ optimization }}</li>
|
|
|
|
+ }
|
|
|
|
+ </ul>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- AI预算评估 -->
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h4>AI预算评估</h4>
|
|
|
|
+ <div class="budget-assessment">
|
|
|
|
+ <div class="budget-range">
|
|
|
|
+ <span class="label">预估范围:</span>
|
|
|
|
+ <span class="value">¥{{ aiAnalysisResults.comprehensiveAnalysis.budgetAssessment.estimatedMin?.toLocaleString() }} - ¥{{ aiAnalysisResults.comprehensiveAnalysis.budgetAssessment.estimatedMax?.toLocaleString() }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="risk-level">
|
|
|
|
+ <span class="label">风险等级:</span>
|
|
|
|
+ <span class="badge" [class]="getRiskLevelClass(aiAnalysisResults.comprehensiveAnalysis.budgetAssessment.riskLevel)">
|
|
|
|
+ {{ getRiskLevelName(aiAnalysisResults.comprehensiveAnalysis.budgetAssessment.riskLevel) }}
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 风险因素 -->
|
|
|
|
+ <div class="analysis-section">
|
|
|
|
+ <h4>潜在风险因素</h4>
|
|
|
|
+ <div class="tags">
|
|
|
|
+ @for (risk of aiAnalysisResults.comprehensiveAnalysis.riskFactors; track $index) {
|
|
|
|
+ <span class="badge badge-warning">{{ risk }}</span>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ }
|
|
|
|
+
|
|
<!-- AI生成方案 -->
|
|
<!-- AI生成方案 -->
|
|
<div class="card ai-solution-card">
|
|
<div class="card ai-solution-card">
|
|
<div class="card-header">
|
|
<div class="card-header">
|
|
@@ -444,7 +728,7 @@
|
|
<div class="completion-indicator">
|
|
<div class="completion-indicator">
|
|
<span class="label">需求完成度</span>
|
|
<span class="label">需求完成度</span>
|
|
<div class="progress-bar">
|
|
<div class="progress-bar">
|
|
- <div class="progress-fill" [style.width.%]="calculateRequirementsCompleteness()"></div>
|
|
|
|
|
|
+ <div class="progress-fill" [style.width]="calculateRequirementsCompleteness() + '%'"></div>
|
|
</div>
|
|
</div>
|
|
<span class="progress-text">{{ calculateRequirementsCompleteness() }}%</span>
|
|
<span class="progress-text">{{ calculateRequirementsCompleteness() }}%</span>
|
|
</div>
|
|
</div>
|
|
@@ -458,8 +742,8 @@
|
|
<button
|
|
<button
|
|
class="btn btn-primary"
|
|
class="btn btn-primary"
|
|
(click)="generateAISolution()"
|
|
(click)="generateAISolution()"
|
|
- [disabled]="generating">
|
|
|
|
- @if (generating) {
|
|
|
|
|
|
+ [disabled]="generating || aiGeneratingComprehensive">
|
|
|
|
+ @if (generating || aiGeneratingComprehensive) {
|
|
<div class="spinner-small">
|
|
<div class="spinner-small">
|
|
<div class="spinner-circle"></div>
|
|
<div class="spinner-circle"></div>
|
|
</div>
|
|
</div>
|
|
@@ -482,7 +766,7 @@
|
|
<button
|
|
<button
|
|
class="btn btn-outline btn-sm"
|
|
class="btn btn-outline btn-sm"
|
|
(click)="generateAISolution()"
|
|
(click)="generateAISolution()"
|
|
- [disabled]="generating">
|
|
|
|
|
|
+ [disabled]="generating || aiGeneratingComprehensive">
|
|
<ion-icon name="refresh"></ion-icon>
|
|
<ion-icon name="refresh"></ion-icon>
|
|
重新生成
|
|
重新生成
|
|
</button>
|
|
</button>
|
|
@@ -598,6 +882,71 @@
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
+ <!-- AI聊天助手 -->
|
|
|
|
+ <div class="card ai-chat-card">
|
|
|
|
+ <div class="card-header">
|
|
|
|
+ <h3 class="card-title">
|
|
|
|
+ <ion-icon name="chatbubbles"></ion-icon>
|
|
|
|
+ AI设计助手
|
|
|
|
+ </h3>
|
|
|
|
+ <button
|
|
|
|
+ class="btn btn-sm btn-outline"
|
|
|
|
+ (click)="toggleAIChat()">
|
|
|
|
+ <ion-icon [name]="showAIChat ? 'chevron-up' : 'chevron-down'"></ion-icon>
|
|
|
|
+ {{ showAIChat ? '收起' : '展开' }}
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ @if (showAIChat) {
|
|
|
|
+ <div class="card-content">
|
|
|
|
+ <div class="ai-chat-container">
|
|
|
|
+ <!-- 聊天消息列表 -->
|
|
|
|
+ <div class="chat-messages" #chatMessages>
|
|
|
|
+ @for (message of aiChatMessages; track message.id) {
|
|
|
|
+ <div class="message" [class.user-message]="message.role == 'user'" [class.ai-message]="message.role == 'assistant'">
|
|
|
|
+ <div class="message-avatar">
|
|
|
|
+ @if (message.role == 'user') {
|
|
|
|
+ <ion-icon name="person-circle"></ion-icon>
|
|
|
|
+ } @else {
|
|
|
|
+ <ion-icon name="sparkles"></ion-icon>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+ <div class="message-content">
|
|
|
|
+ <div class="message-text">{{ message.content }}</div>
|
|
|
|
+ <div class="message-time">{{ message.timestamp | date:'HH:mm' }}</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ }
|
|
|
|
+ @if (aiChatMessages.length == 0) {
|
|
|
|
+ <div class="empty-chat">
|
|
|
|
+ <ion-icon name="chatbubble-ellipses-outline" class="icon-large"></ion-icon>
|
|
|
|
+ <p>向AI设计助手咨询任何家装问题</p>
|
|
|
|
+ </div>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 输入区域 -->
|
|
|
|
+ <div class="chat-input">
|
|
|
|
+ <div class="input-group">
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ class="form-input"
|
|
|
|
+ [(ngModel)]="aiChatInput"
|
|
|
|
+ (keydown.enter)="sendAIChatMessage()"
|
|
|
|
+ placeholder="询问家装设计相关问题..."
|
|
|
|
+ [disabled]="aiAnalyzing" />
|
|
|
|
+ <button
|
|
|
|
+ class="btn btn-primary"
|
|
|
|
+ (click)="sendAIChatMessage()"
|
|
|
|
+ [disabled]="!aiChatInput.trim() || aiAnalyzing">
|
|
|
|
+ <ion-icon name="send"></ion-icon>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ }
|
|
|
|
+ </div>
|
|
|
|
+
|
|
<!-- 操作按钮 -->
|
|
<!-- 操作按钮 -->
|
|
@if (canEdit) {
|
|
@if (canEdit) {
|
|
<div class="action-buttons">
|
|
<div class="action-buttons">
|