All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tri.promptfx.ui.trace.PromptTraceCardList.kt Maven / Gradle / Ivy

The newest version!
/*-
 * #%L
 * tri.promptfx:promptfx
 * %%
 * Copyright (C) 2023 - 2025 Johns Hopkins University Applied Physics Laboratory
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package tri.promptfx.ui.trace

import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.collections.ObservableList
import javafx.event.EventTarget
import javafx.scene.control.MultipleSelectionModel
import javafx.scene.control.TextInputDialog
import javafx.scene.layout.Priority
import kotlinx.coroutines.runBlocking
import tornadofx.*
import tri.ai.openai.jsonWriter
import tri.ai.prompt.trace.AiPromptTrace
import tri.ai.prompt.trace.AiPromptTraceSupport
import tri.promptfx.PromptFxConfig.Companion.DIR_KEY_TRACE
import tri.promptfx.PromptFxConfig.Companion.FF_ALL
import tri.promptfx.PromptFxConfig.Companion.FF_JSON
import tri.promptfx.PromptFxController
import tri.promptfx.PromptFxWorkspace
import tri.promptfx.buildsendresultmenu
import tri.promptfx.promptFxFileChooser
import tri.promptfx.tools.PromptTraceFilter
import tri.util.ui.checklistmenu
import tri.util.ui.graphic

/** UI for a list of [AiPromptTrace]s. */
class PromptTraceCardList: Fragment() {

    private val isRemovable: Boolean by param(false)
    private val isShowFilter: Boolean by param(false)
    private val toolbarLabel: String by param("Results:")
    val prompts: ObservableList> by param()

    private val controller: PromptFxController by inject()
    private val isGlobalHistoryView = controller.promptHistory.prompts === prompts
    private val promptFilter: PromptTraceFilter = find()
    private val filteredPrompts = observableListOf>()
    private lateinit var promptSelectionModel: MultipleSelectionModel>
    val selectedPrompt = SimpleObjectProperty>()

    init {
        prompts.onChange {
            promptFilter.updateFilterOptions(it.list)
            refilter()
        }
        promptFilter.filter.onChange { refilter() }
        promptFilter.updateFilterOptions(prompts)
        refilter()
    }

    override val root = vbox {
        vgrow = Priority.ALWAYS
        val header = toolbar {
            text(toolbarLabel)
            spacer()
        }
        if (isShowFilter) {
            toolbar {
                label("Filter by:")
                checklistmenu("view", promptFilter.viewFilters) { refilter() }
                checklistmenu("model", promptFilter.modelFilters) { refilter() }
                checklistmenu("status", promptFilter.statusFilters) { refilter() }
                checklistmenu("type", promptFilter.typeFilters) { refilter() }
            }
        }
        val list = listview(filteredPrompts) {
            vgrow = Priority.ALWAYS
            promptSelectionModel = selectionModel
            selectedPrompt.bind(selectionModel.selectedItemProperty())
            cellFormat {
                graphic = PromptTraceCard().apply { setTrace(it) }.root
            }
            // add context menu
            lazyContextmenu {
                item("Details...") {
                    enableWhen(selectionModel.selectedItemProperty().isNotNull)
                    action {
                        val selected = selectionModel.selectedItem
                        if (selected != null)
                            find().apply {
                                setTrace(selected)
                                openModal()
                            }
                    }
                }
                item("Try in template view", graphic = FontAwesomeIcon.SEND.graphic) {
                    enableWhen(selectionModel.selectedItemProperty().booleanBinding {
                        it?.prompt?.prompt?.isNotBlank() == true
                    })
                    action {
                        val selected = selectionModel.selectedItem
                        if (selected != null)
                            find().launchTemplateView(selected)
                    }
                }
                if (!isGlobalHistoryView) {
                    item("Open in prompt history view", graphic = FontAwesomeIcon.SEARCH.graphic) {
                        enableWhen(selectionModel.selectedItemProperty().booleanBinding {
                            it?.prompt?.prompt?.isNotBlank() == true
                        })
                        action {
                            val selected = selectionModel.selectedItem
                            if (selected != null)
                                find().launchHistoryView(selected)
                        }
                    }
                }
                buildsendresultmenu("result", selectedPrompt.value?.firstValue?.toString(), find())
            }
        }
        with (header) {
            button("", FontAwesomeIcon.SEND.graphic) {
                tooltip("Try out the selected prompt and inputs in the Prompt Template view.")
                enableWhen(list.selectionModel.selectedItemProperty().isNotNull)
                action {
                    val selected = list.selectedItem
                    if (selected != null)
                        find().launchTemplateView(selected)
                }
            }
            if (isRemovable) {
                button("", FontAwesomeIcon.REMOVE.graphic) {
                    tooltip("Remove selected prompt traces.")
                    enableWhen(list.selectionModel.selectedItemProperty().isNotNull)
                    action {
                        confirm(
                            "Remove traces?",
                            "Are you sure you want to remove selected prompt traces?",
                            owner = currentWindow
                        ) {
                            val selection = list.selectionModel.selectedItems.toList()
                            prompts.removeAll(selection)
                            list.selectionModel.clearSelection()
                        }
                    }
                }
            }
            button("", FontAwesomeIcon.DOWNLOAD.graphic) {
                tooltip("Export prompt traces as JSON.")
                enableWhen(prompts.sizeProperty.greaterThan(0))
                action {
                    exportPromptTraceList()
                }
            }
            if (isRemovable) {
                button("", FontAwesomeIcon.TRASH.graphic) {
                    enableWhen(prompts.sizeProperty.greaterThan(0))
                    action {
                        confirm("Clear all prompt traces?", "Are you sure you want to clear all prompt traces?", owner = currentWindow) {
                            prompts.clear()
                            list.selectionModel.clearSelection()
                        }
                    }
                }
            }

            // settings enabled for global history only
            if (isGlobalHistoryView) {
                button("", FontAwesomeIcon.GEARS.graphic) {
                    tooltip("Adjust prompt history settings.")
                    action {
                        TextInputDialog(controller.promptHistory.maxHistorySize.value.toString()).apply {
                            initOwner(currentWindow)
                            title = "Adjust Prompt History Settings"
                            headerText = "Enter max # of prompt traces to keep in history."
                            contentText = "Max Entries:"
                            showAndWait().ifPresent {
                                it.toIntOrNull()?.let { controller.promptHistory.maxHistorySize.set(it) }
                            }
                        }
                    }
                }
            }
        }
    }

    private fun exportPromptTraceList() {
        val promptTraces = filteredPrompts.toList()
        promptFxFileChooser(
            dirKey = DIR_KEY_TRACE,
            title = "Export Prompt Traces as JSON",
            filters = arrayOf(FF_JSON, FF_ALL),
            mode = FileChooserMode.Save
        ) { file ->
            file.firstOrNull()?.let {
                runAsync {
                    runBlocking {
                        jsonWriter.writeValue(it, promptTraces)
                    }
                }
            }
        }
    }

    private fun refilter() {
        if (isShowFilter) {
            val filter = promptFilter.filter.value
            filteredPrompts.setAll(prompts.toList().filter(filter))
        } else {
            filteredPrompts.setAll(prompts.toList())
        }
    }

    fun selectPromptTrace(prompt: AiPromptTraceSupport<*>) {
        promptSelectionModel.select(prompt)
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy