|
1339 | 1339 |
|
1340 | 1340 | <!-- Input bar --> |
1341 | 1341 | <div class="input-area"> |
1342 | | - <!-- One-click Analyze when files are attached (above the input wrapper) --> |
1343 | | - <div |
1344 | | - v-if="readyAttachments.length && messages.length === 0" |
1345 | | - class="attach-analyze-row" |
1346 | | - > |
1347 | | - <button |
1348 | | - class="attach-analyze-btn" |
1349 | | - :disabled="streaming" |
1350 | | - @click="sendQuestion(analyzePrompt)" |
1351 | | - :title="`Inspect ${readyAttachments.length} attached file${readyAttachments.length > 1 ? 's' : ''} and surface FinOps insights`" |
1352 | | - > |
1353 | | - <span |
1354 | | - >Analyze {{ readyAttachments.length }} file{{ |
1355 | | - readyAttachments.length > 1 ? "s" : "" |
1356 | | - }}</span |
1357 | | - > |
1358 | | - </button> |
1359 | | - </div> |
| 1342 | + <!-- (Removed) Analyze CTA above input — replaced by the Analyze button inside the input bar. --> |
1360 | 1343 |
|
1361 | 1344 | <div |
1362 | 1345 | class="input-wrapper" |
@@ -2907,6 +2890,43 @@ function buildEChartsOption(raw) { |
2907 | 2890 | Array.isArray(d) ? String(d[0]) : d.name, |
2908 | 2891 | ); |
2909 | 2892 |
|
| 2893 | + // X-axis label formatter: wrap long category names onto 2 lines so they're |
| 2894 | + // readable without going horizontal. Splits on spaces, dashes, dots, slashes. |
| 2895 | + // Falls back to a hard mid-string break if there is no separator. |
| 2896 | + const wrapXLabel = (raw) => { |
| 2897 | + const s = String(raw ?? ""); |
| 2898 | + if (s.length <= 14) return s; |
| 2899 | + // Find the best mid-ish split character |
| 2900 | + const seps = [/\s+/, /-/, /\./, /\//]; |
| 2901 | + for (const re of seps) { |
| 2902 | + const parts = s.split(re); |
| 2903 | + if (parts.length >= 2) { |
| 2904 | + // Greedy 2-line wrap aiming for balance |
| 2905 | + const half = Math.ceil(parts.length / 2); |
| 2906 | + const a = parts.slice(0, half).join(" "); |
| 2907 | + const b = parts.slice(half).join(" "); |
| 2908 | + return `${a}\n${b}`; |
| 2909 | + } |
| 2910 | + } |
| 2911 | + // Hard split |
| 2912 | + const mid = Math.ceil(s.length / 2); |
| 2913 | + return `${s.slice(0, mid)}\n${s.slice(mid)}`; |
| 2914 | + }; |
| 2915 | + // Decide rotation: only rotate if the longest single label is huge AND many |
| 2916 | + // categories — otherwise prefer the 2-line wrap which stays horizontal. |
| 2917 | + const longest = categories.reduce((m, c) => Math.max(m, String(c).length), 0); |
| 2918 | + const xRotate = categories.length > 14 && longest > 18 ? 30 : 0; |
| 2919 | + const xAxisLabel = { |
| 2920 | + fontSize: 11, |
| 2921 | + color: "#1f2328", |
| 2922 | + fontWeight: 500, |
| 2923 | + interval: 0, |
| 2924 | + rotate: xRotate, |
| 2925 | + lineHeight: 13, |
| 2926 | + formatter: wrapXLabel, |
| 2927 | + }; |
| 2928 | + const yAxisLabel = { fontSize: 11, color: "#1f2328", fontWeight: 500 }; |
| 2929 | +
|
2910 | 2930 | // Detect multi-series: objects with keys beyond "name" and "value" |
2911 | 2931 | const firstItem = dataArr[0]; |
2912 | 2932 | const isMultiSeries = |
@@ -2940,14 +2960,14 @@ function buildEChartsOption(raw) { |
2940 | 2960 | name: xAxisName, |
2941 | 2961 | nameLocation: "center", |
2942 | 2962 | nameGap: 30, |
2943 | | - axisLabel: { fontSize: 10, rotate: categories.length > 10 ? 45 : 0 }, |
| 2963 | + axisLabel: xAxisLabel, |
2944 | 2964 | }, |
2945 | 2965 | yAxis: { |
2946 | 2966 | type: "value", |
2947 | 2967 | name: yAxisName, |
2948 | 2968 | nameLocation: "center", |
2949 | 2969 | nameGap: 45, |
2950 | | - axisLabel: { fontSize: 10 }, |
| 2970 | + axisLabel: yAxisLabel, |
2951 | 2971 | }, |
2952 | 2972 | series: seriesKeys.map((key) => ({ |
2953 | 2973 | name: key, |
@@ -2987,14 +3007,14 @@ function buildEChartsOption(raw) { |
2987 | 3007 | name: xAxisName, |
2988 | 3008 | nameLocation: "center", |
2989 | 3009 | nameGap: 30, |
2990 | | - axisLabel: { fontSize: 10, rotate: categories.length > 10 ? 45 : 0 }, |
| 3010 | + axisLabel: xAxisLabel, |
2991 | 3011 | }, |
2992 | 3012 | yAxis: { |
2993 | 3013 | type: "value", |
2994 | 3014 | name: yAxisName, |
2995 | 3015 | nameLocation: "center", |
2996 | 3016 | nameGap: 45, |
2997 | | - axisLabel: { fontSize: 10 }, |
| 3017 | + axisLabel: yAxisLabel, |
2998 | 3018 | }, |
2999 | 3019 | series: seriesKeys.map((key, idx) => ({ |
3000 | 3020 | name: key, |
@@ -3022,14 +3042,14 @@ function buildEChartsOption(raw) { |
3022 | 3042 | name: xAxisName, |
3023 | 3043 | nameLocation: "center", |
3024 | 3044 | nameGap: 30, |
3025 | | - axisLabel: { fontSize: 10, rotate: categories.length > 10 ? 45 : 0 }, |
| 3045 | + axisLabel: xAxisLabel, |
3026 | 3046 | }, |
3027 | 3047 | yAxis: { |
3028 | 3048 | type: "value", |
3029 | 3049 | name: yAxisName, |
3030 | 3050 | nameLocation: "center", |
3031 | 3051 | nameGap: 45, |
3032 | | - axisLabel: { fontSize: 10 }, |
| 3052 | + axisLabel: yAxisLabel, |
3033 | 3053 | }, |
3034 | 3054 | series: [ |
3035 | 3055 | { |
@@ -3869,7 +3889,7 @@ async function requestAnalyze() { |
3869 | 3889 | const list = readyAttachments.value; |
3870 | 3890 | if (list.length) { |
3871 | 3891 | const names = list.map((a) => `'${a.fileName}'`).join(", "); |
3872 | | - input.value = `Analyze the uploaded file${list.length > 1 ? "s" : ""} ${names}. Find the biggest cost waste and tell me exactly what to do about it. Only use data from the upload — do not give generic advice.`; |
| 3892 | + input.value = `Analyze the uploaded file${list.length > 1 ? "s" : ""} ${names}. Find the biggest cost waste and tell me exactly what to do about it.`; |
3873 | 3893 | } else { |
3874 | 3894 | input.value = |
3875 | 3895 | "Find the biggest cost waste and tell me what to do about it."; |
|
0 commit comments