contents/ui/components/ProviderCostSection.qml

import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import "." as Components
import "../CostPresentation.js" as CostPresentation
import "../QuotaWindowCost.js" as QuotaWindowCost

ColumnLayout {
    id: tokenCostSection
    objectName: "providerLocalCostSection"

    required property var applet
    required property var providerData
    property string accountSelectionKey: applet.accountKey(providerData)
    property string accountSelectionLabel: applet.accountLabel(providerData)
    property bool presentationVisible: false
    property bool detailsExpanded: false
    property var daySelectionMemo: ({ points: [], index: -1 })
    readonly property bool costHistoryShowsTokens: applet.costHistoryShowsTokens
    // Pins belong to the shown account: the addressing identity and the raw
    // account label both participate, so a reselect under either spelling
    // clears them. The label is read from the unprojected snapshot because
    // privacy mode flattens it; the key additionally catches spacing-distinct
    // accounts that collapse to one label.
    readonly property string selectionScope: providerData ? JSON.stringify([providerData.provider, accountSelectionKey,
        accountSelectionLabel, tokenCost ? tokenCost.historyDays : 0, tokenCost ? tokenCost.period : ""]) : ""
    readonly property var selectedDay: CostPresentation.selectedCostDay(tokenCost ? tokenCost.daily : [], chartPoints, costChart.selectedIndex)
    readonly property bool hasVisibleDetails: detailsExpanded || selectedDay !== null

    onSelectionScopeChanged: {
        detailsExpanded = false;
        clearDaySelection();
    }
    onCostHistoryShowsTokensChanged: clearDaySelection()

    Components.CostLabels {
        id: costLabels
        applet: tokenCostSection.applet
    }

    function amountText(amounts, mode) {
        return CostPresentation.hasMetricValue(amounts, false) ? applet.qualifiedCostValue(applet.amountString(amounts.cost, amounts.currency), mode) : i18n("Cost unavailable");
    }

    function tokensText(amounts) {
        return CostPresentation.hasMetricValue(amounts, true) ? applet.usageCountText(amounts.tokens, "tokens") : i18n("Tokens unavailable");
    }

    function daySummaryText() {
        var summary = CostPresentation.amountSummary(applet.costNumberFormat, selectedDay, function (tokens) {
            return applet.usageCountText(tokens, "tokens");
        });
        var excluded = excludedRequestsText(CostPresentation.acceptedIncompleteRequests(selectedDay));
        return excluded.length > 0 ? summary + " · " + excluded : summary;
    }

    // The CLI left these requests out of the row's amounts; the count is stated
    // beside the row instead of being folded into a figure.
    function excludedRequestsText(count) {
        return count > 0 ? i18np("%1 incomplete request excluded", "%1 incomplete requests excluded", count) : "";
    }

    function clearDaySelection() {
        if (!costChart)
            return;
        daySelectionMemo = { points: costChart.points, index: -1 };
        costChart.selectedIndex = -1;
        costChart.hoveredIndex = -1;
    }

    function rememberDaySelection() {
        // Ignore the chart's index clamp while a new points array is awaiting reconciliation.
        if (daySelectionMemo.points === costChart.points)
            daySelectionMemo = { points: costChart.points, index: costChart.selectedIndex };
    }

    function reconcileDaySelection() {
        var points = costChart.points;
        var index = CostPresentation.costDayIndexAfterRefresh(daySelectionMemo.points, daySelectionMemo.index, points);
        daySelectionMemo = { points: points, index: index };
        costChart.selectedIndex = index;
        costChart.hoveredIndex = -1;
    }

    readonly property var tokenCost: tokenCostSection.providerData ? tokenCostSection.providerData.tokenCost : null
    // Mutated only by CostPresentation.memoizedChartPoints; never reassigned, so it adds no binding dependency.
    readonly property var chartPointsMemo: ({})
    readonly property var chartPoints: tokenCost ? applet.costChartPoints(tokenCost.daily, chartPointsMemo) : []
    readonly property var costTrustSummary: CostPresentation.costTrustSummary(tokenCost ? [tokenCost] : [])
    readonly property string costErrorText: applet.privateErrorText(applet.costErrorText)
    readonly property bool supportsLocalCost: tokenCostSection.providerData && applet.tokenCostHint(tokenCostSection.providerData.provider).length > 0

    visible: tokenCostSection.tokenCost ? true : tokenCostSection.supportsLocalCost && tokenCostSection.costErrorText.length > 0
    Layout.fillWidth: true
    spacing: Kirigami.Units.smallSpacing

    Components.TimeLabels {
        id: timeLabels
    }

    Kirigami.Separator {
        Layout.fillWidth: true
    }

    RowLayout {
        Layout.fillWidth: true

        PlainHeading {
            text: i18n("Cost")
            level: 4
            type: Kirigami.Heading.Type.Primary
            Layout.fillWidth: true
            elide: Text.ElideRight
        }

        Components.PlainComboBox {
            objectName: "providerCostMetricCombo"
            visible: tokenCostSection.tokenCost !== null
            // A view option inside a secondary section: flat, so it does not
            // outweigh the quota meters above it.
            flat: true
            textRole: "text"
            valueRole: "value"
            model: [
                {
                    text: i18n("Cost"),
                    value: "cost"
                },
                {
                    text: i18n("Tokens"),
                    value: "tokens"
                }
            ]
            currentIndex: tokenCostSection.costHistoryShowsTokens ? 1 : 0
            Accessible.name: i18n("History metric")
            onActivated: function (index) {
                tokenCostSection.applet.setCostHistoryMetric(valueAt(index));
                // The interactive pick severs the currentIndex binding; restore
                // it so the combo keeps tracking settings changes made outside
                // this section.
                currentIndex = Qt.binding(function () {
                    return tokenCostSection.costHistoryShowsTokens ? 1 : 0;
                });
            }
        }
    }

    PlainPlasmaLabel {
        visible: tokenCostSection.costErrorText.length > 0
        text: tokenCostSection.tokenCost
            ? i18n("Some local history is unavailable: %1", tokenCostSection.costErrorText)
            : i18n("Cost unavailable: %1", tokenCostSection.costErrorText)
        color: Kirigami.Theme.negativeTextColor
        Layout.fillWidth: true
        wrapMode: Text.WordWrap
    }

    GridLayout {
        id: costSummaryGrid
        objectName: "costSummaryGrid"
        visible: tokenCostSection.tokenCost !== null
        Layout.fillWidth: true
        columns: 2
        columnSpacing: Kirigami.Units.largeSpacing

        Repeater {
            model: tokenCostSection.tokenCost ? [
                {
                    label: i18n("Today"),
                    amounts: CostPresentation.todayAmounts(tokenCostSection.tokenCost, tokenCostSection.applet.panelClockMs),
                    valueMode: "plain"
                },
                {
                    label: tokenCostSection.tokenCost.windowLabel,
                    amounts: tokenCostSection.tokenCost.totals,
                    valueMode: tokenCostSection.tokenCost.valueMode
                }
            ] : []

            delegate: ColumnLayout {
                required property var modelData
                Layout.fillWidth: true
                Layout.preferredWidth: 1
                spacing: Kirigami.Units.smallSpacing / 2

                PlainPlasmaLabel {
                    text: modelData.label
                    font: Kirigami.Theme.smallFont
                    opacity: tokenCostSection.applet.secondaryTextOpacity
                    Layout.fillWidth: true
                    wrapMode: Text.Wrap
                }
                PlainPlasmaLabel {
                    text: tokenCostSection.amountText(modelData.amounts, modelData.valueMode)
                    font.weight: Font.DemiBold
                    Layout.fillWidth: true
                    wrapMode: Text.Wrap
                }
                PlainPlasmaLabel {
                    text: tokenCostSection.tokensText(modelData.amounts)
                    font: Kirigami.Theme.smallFont
                    opacity: tokenCostSection.applet.secondaryTextOpacity
                    Layout.fillWidth: true
                    wrapMode: Text.Wrap
                }
            }
        }
    }

    Components.CostTrustNotice {
        noticeScope: "provider:" + (tokenCostSection.providerData ? tokenCostSection.providerData.provider : "")
        stateOwner: tokenCostSection.applet
        presentationVisible: tokenCostSection.presentationVisible
        summary: tokenCostSection.costTrustSummary
    }

    Components.InteractiveChart {
        id: costChart
        objectName: "providerCostChart"
        onPointsChanged: tokenCostSection.reconcileDaySelection()
        onSelectedIndexChanged: tokenCostSection.rememberDaySelection()
        visible: tokenCostSection.chartPoints.length > 0
        applet: tokenCostSection.applet
        points: tokenCostSection.chartPoints
        accent: applet.providerReadableColor(providerData ? providerData.provider : "")
        kind: "bar"
        accessibleTitle: tokenCostSection.costHistoryShowsTokens ? i18n("Daily token history") : i18n("Daily cost history")
        Layout.topMargin: Kirigami.Units.smallSpacing / 2
    }

    // The axis already dates both ends of the range and the summary grid
    // names the window, so only the latest day's figure remains here. A
    // selected day already shows its figure in the chart readout and the
    // drill-down heading, so the line would only repeat it a third time.
    PlainPlasmaLabel {
        id: costSparklineSummaryLabel
        objectName: "costSparklineSummaryLabel"

        visible: tokenCostSection.chartPoints.length > 0 && tokenCostSection.selectedDay === null
        text: tokenCostSection.tokenCost ? costLabels.costSparklineSummary(tokenCostSection.tokenCost.daily) : ""
        font: Kirigami.Theme.smallFont
        opacity: applet.secondaryTextOpacity
        Layout.fillWidth: true
        elide: Text.ElideRight
    }

    // A disclosure, not a primary action: a flat button keeps it from
    // outweighing the figures it reveals.
    Components.DisclosureButton {
        objectName: "costDetailsToggle"
        visible: tokenCostSection.tokenCost !== null
        expanded: tokenCostSection.hasVisibleDetails
        plainText: tokenCostSection.hasVisibleDetails ? i18n("Hide details") : i18n("Show details")
        onClicked: {
            if (tokenCostSection.hasVisibleDetails) {
                tokenCostSection.detailsExpanded = false;
                tokenCostSection.clearDaySelection();
            } else {
                tokenCostSection.detailsExpanded = true;
            }
        }
    }

    ColumnLayout {
        id: costDrillDownSection

        readonly property var detailData: tokenCostSection.selectedDay ? {
            totals: tokenCostSection.selectedDay,
            models: tokenCostSection.selectedDay.models,
            modelsTruncated: tokenCostSection.selectedDay.modelsTruncated
        } : tokenCostSection.tokenCost
        readonly property var breakdownRows: costLabels.costBreakdownRows(detailData)
        readonly property var modelRows: costLabels.costModelRows(detailData)
        readonly property real metricValueColumnWidth: Kirigami.Units.gridUnit * 9

        objectName: "costDrillDownSection"
        visible: tokenCostSection.tokenCost !== null && tokenCostSection.hasVisibleDetails
        Layout.fillWidth: true
        spacing: Kirigami.Units.smallSpacing

        RowLayout {
            Layout.fillWidth: true

            PlainPlasmaLabel {
                objectName: "costDetailsTitle"
                text: tokenCostSection.selectedDay ? i18n("Details for %1", costLabels.costDayLabel(tokenCostSection.selectedDay.label)) : i18n("Cost details")
                font.weight: Font.DemiBold
                Layout.fillWidth: true
                wrapMode: Text.Wrap
            }

            Components.PlainButton {
                plainText: i18n("All days")
                visible: tokenCostSection.selectedDay !== null
                onClicked: {
                    tokenCostSection.detailsExpanded = true;
                    tokenCostSection.clearDaySelection();
                }
            }
        }

        PlainPlasmaLabel {
            visible: tokenCostSection.selectedDay !== null
            text: tokenCostSection.daySummaryText()
            Layout.fillWidth: true
            wrapMode: Text.Wrap
        }

        PlainPlasmaLabel {
            objectName: "costModelsEmptyNotice"
            visible: costDrillDownSection.modelRows.length === 0
            text: tokenCostSection.selectedDay !== null ? i18n("No model breakdown for this day.")
                : i18n("No model breakdown for this period.")
            Layout.fillWidth: true
            wrapMode: Text.Wrap
        }

        PlainPlasmaLabel {
            objectName: "costModelsPartialNotice"
            visible: costDrillDownSection.detailData !== null && costDrillDownSection.detailData.modelsTruncated === true
            text: i18n("Model breakdown is partial.")
            Layout.fillWidth: true
            wrapMode: Text.Wrap
        }

        PlainPlasmaLabel {
            visible: !tokenCostSection.selectedDay && tokenCostSection.tokenCost && costLabels.costPerMillionLine(tokenCostSection.tokenCost).length > 0
            text: tokenCostSection.tokenCost ? costLabels.costPerMillionLine(tokenCostSection.tokenCost) : ""
            font: Kirigami.Theme.smallFont
            opacity: applet.secondaryTextOpacity
            Layout.fillWidth: true
            elide: Text.ElideRight
        }

        ColumnLayout {
            visible: costDrillDownSection.breakdownRows.length > 0
            Layout.fillWidth: true
            spacing: Kirigami.Units.smallSpacing / 2

            Repeater {
                model: costDrillDownSection.breakdownRows

                delegate: RowLayout {
                    required property var modelData

                    Layout.fillWidth: true
                    spacing: Kirigami.Units.smallSpacing

                    PlainPlasmaLabel {
                        text: modelData.label
                        font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                        opacity: applet.secondaryTextOpacity
                        Layout.fillWidth: true
                        elide: Text.ElideRight
                    }

                    PlainPlasmaLabel {
                        id: costBreakdownValueLabel

                        text: modelData.value
                        font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                        opacity: applet.valueTextOpacity
                        font.weight: Font.Medium
                        horizontalAlignment: Text.AlignRight
                        Layout.preferredWidth: costDrillDownSection.metricValueColumnWidth
                        Layout.maximumWidth: costDrillDownSection.metricValueColumnWidth
                        elide: Text.ElideRight
                    }
                }
            }
        }

        Kirigami.Separator {
            visible: costDrillDownSection.modelRows.length > 0
            Layout.fillWidth: true
            opacity: 0.55
        }

        ColumnLayout {
            visible: costDrillDownSection.modelRows.length > 0
            Layout.fillWidth: true
            spacing: Kirigami.Units.smallSpacing / 2

            PlainPlasmaLabel {
                id: costModelsHeading

                text: i18n("Models")
                font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                font.weight: Font.DemiBold
                opacity: applet.secondaryTextOpacity
                Layout.fillWidth: true
                elide: Text.ElideRight
            }

            Repeater {
                model: costDrillDownSection.modelRows

                delegate: ColumnLayout {
                    required property var modelData

                    Layout.fillWidth: true
                    spacing: 0

                    RowLayout {
                        Layout.fillWidth: true
                        spacing: Kirigami.Units.smallSpacing

                        PlainPlasmaLabel {
                            text: modelData.label
                            font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                            opacity: applet.secondaryTextOpacity
                            Layout.fillWidth: true
                            elide: Text.ElideRight
                        }

                        PlainPlasmaLabel {
                            id: costModelValueLabel

                            text: modelData.value
                            font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                            opacity: applet.valueTextOpacity
                            font.weight: Font.Medium
                            horizontalAlignment: Text.AlignRight
                            Layout.preferredWidth: costDrillDownSection.metricValueColumnWidth
                            Layout.maximumWidth: costDrillDownSection.metricValueColumnWidth
                            elide: Text.ElideRight
                        }
                    }

                    PlainPlasmaLabel {
                        objectName: "costModelExcludedRequests"
                        visible: text.length > 0
                        text: tokenCostSection.excludedRequestsText(modelData.incompleteRequests)
                        font: Kirigami.Theme.smallFont
                        opacity: applet.secondaryTextOpacity
                        Layout.fillWidth: true
                        wrapMode: Text.Wrap
                    }
                }
            }
        }
    }

    ColumnLayout {
        id: quotaWindowSection

        // The weekly quota row supplies the boundaries; the local daily history
        // supplies the amounts. Only a row that reports its window length can
        // be split, so a restored cache row shows nothing until a live refresh.
        readonly property var weeklyRow: {
            var rows = tokenCostSection.providerData && Array.isArray(tokenCostSection.providerData.rows)
                ? tokenCostSection.providerData.rows : [];
            for (var i = 0; i < rows.length; i++) {
                if (rows[i] && rows[i].windowMinutes === 7 * 24 * 60 && typeof rows[i].resetsAt === "string")
                    return rows[i];
            }
            return null;
        }
        readonly property var windows: weeklyRow && tokenCostSection.tokenCost
            ? QuotaWindowCost.windows(tokenCostSection.tokenCost.daily, Date.parse(weeklyRow.resetsAt),
                weeklyRow.windowMinutes, applet.panelClockMs)
            : []
        readonly property bool anyEstimated: windows.some(function(window) {
            return window.startEstimated || window.endEstimated;
        })

        function instantText(timestampMs) {
            return timeLabels.monthDayTime(timestampMs);
        }
        function rangeText(window) {
            return i18n("%1 - %2", instantText(window.startMs), instantText(window.endMs));
        }
        function amountText(window) {
            var currency = tokenCostSection.tokenCost ? tokenCostSection.tokenCost.currency : "USD";
            var historyIncomplete = tokenCostSection.tokenCost
                && tokenCostSection.tokenCost.historyCoverageEstablished === false;
            var cost = window.cost === null ? i18n("Cost unavailable")
                : (window.costPartial || historyIncomplete ? i18n("at least %1", applet.amountString(window.cost, currency))
                    : applet.amountString(window.cost, currency));
            var tokens = window.tokens === null ? i18n("Tokens unavailable")
                : (window.tokensPartial || historyIncomplete ? i18n("at least %1", applet.usageCountText(window.tokens, "tokens"))
                    : applet.usageCountText(window.tokens, "tokens"));
            // Separators and the estimate mark are symbols, composed outside the
            // catalog like the other " · " joins in this section.
            var text = cost + " · " + tokens;
            return window.startEstimated || window.endEstimated ? "≈ " + text : text;
        }

        objectName: "quotaWindowSection"
        visible: tokenCostSection.detailsExpanded && windows.length > 0
        Layout.fillWidth: true
        spacing: Kirigami.Units.smallSpacing / 2

        PlainPlasmaLabel {
            text: i18n("Quota weeks")
            font.weight: Font.DemiBold
            Layout.fillWidth: true
            elide: Text.ElideRight
        }

        Repeater {
            model: quotaWindowSection.windows

            delegate: RowLayout {
                required property var modelData

                Layout.fillWidth: true
                spacing: Kirigami.Units.smallSpacing

                PlainPlasmaLabel {
                    text: quotaWindowSection.rangeText(modelData)
                    font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                    font.weight: modelData.current ? Font.DemiBold : Font.Normal
                    opacity: modelData.current ? 1 : applet.secondaryTextOpacity
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                }

                PlainPlasmaLabel {
                    text: quotaWindowSection.amountText(modelData)
                    font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                    horizontalAlignment: Text.AlignRight
                    elide: Text.ElideLeft
                }
            }
        }

        PlainPlasmaLabel {
            visible: quotaWindowSection.anyEstimated
            text: "≈ " + i18n("The history is kept per day, so a day containing a reset counts toward one week only.")
            font: Kirigami.Theme.smallFont
            opacity: applet.secondaryTextOpacity
            wrapMode: Text.WordWrap
            Layout.fillWidth: true
        }
    }

    ColumnLayout {
        id: costHistoryChartSection

        readonly property var rows: costLabels.costHistoryRows(tokenCostSection.tokenCost)
        readonly property string peakLine: tokenCostSection.tokenCost ? costLabels.costPeakLine(tokenCostSection.tokenCost.daily) : ""
        readonly property string averageLine: tokenCostSection.tokenCost ? costLabels.costAverageDailyLine(tokenCostSection.tokenCost.daily, tokenCostSection.tokenCost.averageDaily) : ""
        readonly property color accent: applet.providerReadableColor(tokenCostSection.providerData ? tokenCostSection.providerData.provider : "")

        visible: tokenCostSection.detailsExpanded && rows.length > 1
        Layout.fillWidth: true
        spacing: Kirigami.Units.smallSpacing / 2

        RowLayout {
            id: costHistoryHeaderRow

            Layout.fillWidth: true
            spacing: Kirigami.Units.smallSpacing

            PlainPlasmaLabel {
                text: i18n("Cost history")
                font.weight: Font.DemiBold
                Layout.alignment: Qt.AlignBaseline
                Layout.fillWidth: true
                elide: Text.ElideRight
            }

            PlainPlasmaLabel {
                visible: costHistoryChartSection.averageLine.length > 0
                text: costHistoryChartSection.averageLine
                font: Kirigami.Theme.smallFont
                opacity: applet.secondaryTextOpacity
                horizontalAlignment: Text.AlignRight
                Layout.alignment: Qt.AlignBaseline
                elide: Text.ElideRight
            }
        }

        PlainPlasmaLabel {
            visible: costHistoryChartSection.peakLine.length > 0
            text: costHistoryChartSection.peakLine
            font: Kirigami.Theme.smallFont
            opacity: applet.secondaryTextOpacity
            Layout.fillWidth: true
            elide: Text.ElideRight
        }

        Repeater {
            model: costHistoryChartSection.rows

            delegate: ColumnLayout {
                required property var modelData

                Layout.fillWidth: true
                spacing: 0

                RowLayout {
                    id: costHistoryMetricRow

                    Layout.fillWidth: true
                    spacing: Kirigami.Units.smallSpacing

                    PlainPlasmaLabel {
                        id: costHistoryDateLabel

                        text: modelData.label
                        font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                        opacity: applet.secondaryTextOpacity
                        Layout.preferredWidth: Kirigami.Units.gridUnit * 5
                        elide: Text.ElideRight
                    }

                    Rectangle {
                        id: costHistoryBarTrack

                        Layout.fillWidth: true
                        Layout.preferredHeight: applet.compactMeterTrackHeight
                        radius: height / 2
                        color: applet.withAlpha(Kirigami.Theme.textColor, 0.055)
                        clip: true
                        antialiasing: true

                        Rectangle {
                            width: parent.width * Math.max(0, Math.min(100, modelData.percent)) / 100
                            height: parent.height
                            radius: parent.radius
                            antialiasing: true
                            gradient: Gradient {
                                orientation: Gradient.Horizontal

                                GradientStop {
                                    position: 0
                                    color: applet.withAlpha(costHistoryChartSection.accent, modelData.isPeak ? 0.72 : 0.46)
                                }

                                GradientStop {
                                    position: 1
                                    color: applet.withAlpha(costHistoryChartSection.accent, modelData.isPeak ? 1 : 0.8)
                                }
                            }

                            Behavior on width {
                                NumberAnimation {
                                    duration: Kirigami.Units.longDuration
                                    easing.type: Easing.OutCubic
                                }
                            }
                        }
                    }

                    PlainPlasmaLabel {
                        id: costHistoryValueLabel

                        text: modelData.value
                        font.pixelSize: Kirigami.Theme.smallFont.pixelSize
                        opacity: modelData.isPeak ? applet.valueTextOpacity : applet.secondaryTextOpacity
                        font.weight: modelData.isPeak ? Font.DemiBold : Font.Normal
                        horizontalAlignment: Text.AlignRight
                        Layout.preferredWidth: Kirigami.Units.gridUnit * 8
                        elide: Text.ElideRight
                    }
                }

                PlainPlasmaLabel {
                    objectName: "costHistoryExcludedRequests"
                    visible: text.length > 0
                    text: tokenCostSection.excludedRequestsText(modelData.incompleteRequests)
                    font: Kirigami.Theme.smallFont
                    opacity: applet.secondaryTextOpacity
                    Layout.fillWidth: true
                    wrapMode: Text.Wrap
                }
            }
        }
    }

    ColumnLayout {
        visible: tokenCostSection.detailsExpanded
        Layout.fillWidth: true
        Components.ProjectCostSection {
            applet: tokenCostSection.applet
            providerCosts: tokenCostSection.tokenCost ? [tokenCostSection.tokenCost] : []
        }
    }

    PlainPlasmaLabel {
        visible: tokenCostSection.tokenCost && tokenCostSection.tokenCost.hintLine.length > 0 ? true : false
        text: tokenCostSection.tokenCost ? tokenCostSection.tokenCost.hintLine : ""
        font: Kirigami.Theme.smallFont
        opacity: applet.secondaryTextOpacity
        Layout.fillWidth: true
        wrapMode: Text.WordWrap
    }
}