Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import com.chaomixian.vflow.core.types.basic.VDictionary
import com.chaomixian.vflow.core.types.complex.VImage
import com.chaomixian.vflow.core.utils.StorageManager
import com.chaomixian.vflow.core.workflow.model.ActionStep
import com.chaomixian.vflow.core.workflow.model.ActionStepExecutionSettings
import com.chaomixian.vflow.core.workflow.model.Workflow
import com.chaomixian.vflow.core.workflow.model.WorkflowReentryBehavior
import com.chaomixian.vflow.core.workflow.module.logic.*
import com.chaomixian.vflow.services.ExecutionNotificationManager
import com.chaomixian.vflow.services.ExecutionNotificationState
import com.chaomixian.vflow.services.ExecutionUIService
import com.chaomixian.vflow.services.ServiceStateBus
import com.chaomixian.vflow.ui.workflow_editor.ActionEditorSheet
import kotlinx.coroutines.*
import kotlinx.coroutines.TimeoutCancellationException
import java.io.File
Expand Down Expand Up @@ -538,14 +538,15 @@ object WorkflowExecutor {
DebugLogger.d("WorkflowExecutor", "[${workflow.name}][#${pc + 1}] -> 执行: ${module.metadata.name}")

// --- 错误处理与重试逻辑 ---
val errorPolicy = step.parameters[ActionEditorSheet.KEY_ERROR_POLICY] as? String ?: ActionEditorSheet.POLICY_STOP
val retryCount = (step.parameters[ActionEditorSheet.KEY_RETRY_COUNT] as? Number)?.toInt() ?: 3
val retryInterval = (step.parameters[ActionEditorSheet.KEY_RETRY_INTERVAL] as? Number)?.toLong() ?: 1000L
val executionSettings = ActionStepExecutionSettings.fromParameters(step.parameters)
val errorPolicy = executionSettings.policy
val retryCount = executionSettings.retryCount
val retryInterval = executionSettings.retryIntervalMillis

var attempt = 0
var finalResult: ExecutionResult? = null

while (attempt <= retryCount || errorPolicy != ActionEditorSheet.POLICY_RETRY) {
while (attempt <= retryCount || errorPolicy != ActionStepExecutionSettings.POLICY_RETRY) {
if (attempt > 0) {
DebugLogger.w("WorkflowExecutor", "步骤执行失败,正在进行第 $attempt 次重试 (等待 ${retryInterval}ms)...")
// 在模块内部进度更新时,也刷新通知
Expand All @@ -563,7 +564,7 @@ object WorkflowExecutor {
}

// 只有策略是 RETRY 且是 Failure 时才继续循环
if (errorPolicy == ActionEditorSheet.POLICY_RETRY && finalResult is ExecutionResult.Failure) {
if (errorPolicy == ActionStepExecutionSettings.POLICY_RETRY && finalResult is ExecutionResult.Failure) {
attempt++
if (attempt > retryCount) break
} else {
Expand All @@ -582,7 +583,7 @@ object WorkflowExecutor {
is ExecutionResult.Failure -> {
DebugLogger.e("WorkflowExecutor", "模块执行失败: ${result.errorTitle} - ${result.errorMessage}")

if (errorPolicy == ActionEditorSheet.POLICY_SKIP) {
if (errorPolicy == ActionStepExecutionSettings.POLICY_SKIP) {
DebugLogger.w("WorkflowExecutor", "根据策略,跳过错误继续执行。")

// 1. 如果模块提供了 partialOutputs,使用它们作为基础
Expand Down
16 changes: 11 additions & 5 deletions app/src/main/java/com/chaomixian/vflow/core/module/definitions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ data class EditorAction(
*/
abstract class CustomEditorViewHolder(val view: View)


/**
* 模块用户界面提供者接口。
* 将模块的 Android View 相关逻辑从核心模块逻辑中分离出来,实现解耦。
*/
interface ModuleUIProvider {

/**
* 创建在工作流步骤卡片中显示的自定义预览视图。
* 如果返回 null,则通常会回退到使用模块的 getSummary() 方法。
Expand All @@ -94,7 +94,7 @@ interface ModuleUIProvider {
step: ActionStep,
allSteps: List<ActionStep>,
onStartActivityForResult: ((Intent, (resultCode: Int, data: Intent?) -> Unit) -> Unit)? = null
): View?
): View? = null

/**
* 创建用于编辑模块参数的自定义用户界面。
Expand All @@ -115,21 +115,27 @@ interface ModuleUIProvider {
onMagicVariableRequested: ((inputId: String) -> Unit)? = null,
allSteps: List<ActionStep>? = null,
onStartActivityForResult: ((Intent, (resultCode: Int, data: Intent?) -> Unit) -> Unit)? = null
): CustomEditorViewHolder
): CustomEditorViewHolder = object : CustomEditorViewHolder(View(context)) {}

/**
* 从自定义编辑器界面中读取用户输入的参数值。
* @param holder 包含编辑器视图的 CustomEditorViewHolder 实例。
* @return 一个包含参数ID和对应值的 Map。
*/
fun readFromEditor(holder: CustomEditorViewHolder): Map<String, Any?>
fun readFromEditor(holder: CustomEditorViewHolder): Map<String, Any?> = emptyMap()

/**
* 声明此 UI 提供者具体处理了哪些输入参数的界面渲染。
* 对于在此返回的输入ID,通用的参数编辑界面将不会为它们创建默认的UI控件。
* @return 一个包含已处理的输入参数ID的集合。
*/
fun getHandledInputIds(): Set<String>
fun getHandledInputIds(): Set<String> = emptySet()

/**
* 是否提供独立的自定义编辑器。
* 默认视为存在自定义编辑器;getHandledInputIds 只负责声明哪些输入由自定义 UI 接管。
*/
fun hasCustomEditor(): Boolean = true
}

/**
Expand Down
38 changes: 0 additions & 38 deletions app/src/main/java/com/chaomixian/vflow/core/pill/PillBuilder.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.chaomixian.vflow.core.workflow.model

data class ActionStepExecutionSettings(
val policy: String = POLICY_STOP,
val retryCount: Int = DEFAULT_RETRY_COUNT,
val retryIntervalMillis: Long = DEFAULT_RETRY_INTERVAL_MS
) {
companion object {
const val KEY_ERROR_POLICY = "__error_policy"
const val KEY_RETRY_COUNT = "__retry_count"
const val KEY_RETRY_INTERVAL = "__retry_interval"

const val POLICY_STOP = "STOP"
const val POLICY_SKIP = "SKIP"
const val POLICY_RETRY = "RETRY"

const val DEFAULT_RETRY_COUNT = 3
const val DEFAULT_RETRY_INTERVAL_MS = 1000L

fun fromParameters(parameters: Map<String, Any?>): ActionStepExecutionSettings {
return ActionStepExecutionSettings(
policy = parameters[KEY_ERROR_POLICY] as? String ?: POLICY_STOP,
retryCount = (parameters[KEY_RETRY_COUNT] as? Number)?.toInt() ?: DEFAULT_RETRY_COUNT,
retryIntervalMillis = (parameters[KEY_RETRY_INTERVAL] as? Number)?.toLong()
?: DEFAULT_RETRY_INTERVAL_MS
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class CoreScreenOperationModuleUIProvider : ModuleUIProvider {
if (valStr.isMagicVariable() || valStr.isNamedVariable()) {
// 变量药丸(支持魔法变量和命名变量)
val pill = LayoutInflater.from(context).inflate(R.layout.magic_variable_pill, valueContainer, false)
val displayName = PillRenderer.getDisplayNameForVariableReference(valStr!!, allSteps ?: emptyList(), context)
val displayName = PillRenderer.resolveDisplayName(context, valStr!!, allSteps ?: emptyList())
pill.findViewById<TextView>(R.id.pill_text).text = displayName
pill.setOnClickListener { onMagicReq?.invoke(inputDef.id) }
valueContainer.addView(pill)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package com.chaomixian.vflow.core.workflow.module.data

import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.chaomixian.vflow.R
import com.chaomixian.vflow.core.execution.ExecutionContext
import com.chaomixian.vflow.core.module.*
import com.chaomixian.vflow.core.workflow.model.ActionStep
import com.chaomixian.vflow.ui.workflow_editor.PillRenderer
import com.chaomixian.vflow.ui.workflow_editor.PillUtil

class CommentModule : BaseModule() {
override val id = "vflow.data.comment"
Expand All @@ -24,8 +19,6 @@ class CommentModule : BaseModule() {
categoryId = "data"
)

override val uiProvider: ModuleUIProvider? = CommentUIProvider()

override fun getInputs(): List<InputDefinition> = listOf(
InputDefinition(
id = "content",
Expand All @@ -42,7 +35,14 @@ class CommentModule : BaseModule() {
override fun getOutputs(step: ActionStep?): List<OutputDefinition> = emptyList()

override fun getSummary(context: Context, step: ActionStep): CharSequence {
return context.getString(R.string.module_vflow_data_comment_name)
return PillUtil.buildSpannable(
context,
context.getString(R.string.module_vflow_data_comment_name),
PillUtil.richTextPreview(
rawText = step.parameters["content"]?.toString(),
onlyWhenComplex = false
)
)
}

override suspend fun execute(
Expand All @@ -53,44 +53,3 @@ class CommentModule : BaseModule() {
return ExecutionResult.Success(emptyMap())
}
}

/**
* 注释模块的UI提供器,使用默认的富文本输入框
* 同时重写createPreview以始终显示预览(无论内容是否复杂)
*/
class CommentUIProvider : ModuleUIProvider {

private val innerProvider = com.chaomixian.vflow.ui.workflow_editor.RichTextUIProvider("content")

override fun getHandledInputIds(): Set<String> = emptySet()

override fun createEditor(
context: Context, parent: ViewGroup, currentParameters: Map<String, Any?>,
onParametersChanged: () -> Unit, onMagicVariableRequested: ((String) -> Unit)?,
allSteps: List<ActionStep>?, onStartActivityForResult: ((Intent, (Int, Intent?) -> Unit) -> Unit)?
): CustomEditorViewHolder {
// 返回一个空的 ViewHolder,让系统使用默认的富文本输入框
return object : CustomEditorViewHolder(View(context)) {}
}

override fun readFromEditor(holder: CustomEditorViewHolder): Map<String, Any?> = emptyMap()

/**
* 始终创建预览视图,无论内容是否复杂
*/
override fun createPreview(
context: Context, parent: ViewGroup, step: ActionStep, allSteps: List<ActionStep>,
onStartActivityForResult: ((Intent, (resultCode: Int, data: Intent?) -> Unit) -> Unit)?
): View? {
val rawText = step.parameters["content"]?.toString() ?: ""

val inflater = LayoutInflater.from(context)
val previewView = inflater.inflate(R.layout.partial_rich_text_preview, parent, false)
val textView = previewView.findViewById<TextView>(R.id.rich_text_preview_content)

val spannable = PillRenderer.renderRichTextToSpannable(context, rawText, allSteps)
textView.text = spannable

return previewView
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,27 @@ class CreateVariableModule : BaseModule() {

// 优先检查是否为"复杂内容"。
// 如果是复杂的(包含多个变量或混合文本),则只返回简单标题。
// RichTextUIProvider 会负责渲染预览视图
// 复杂字符串值会由预览层补充展示
if (type == TYPE_STRING && VariableResolver.isComplex(rawText)) {
return if (name.isNullOrBlank()) {
context.getString(R.string.summary_vflow_data_create_anon, typeLabel, "")
PillUtil.buildSpannable(
context,
context.getString(R.string.summary_vflow_data_create_anon, typeLabel, ""),
PillUtil.richTextPreview(rawText)
)
} else {
val namePill = PillUtil.Pill("[[$name]]", "variableName")
PillUtil.buildSpannable(context, context.getString(R.string.summary_vflow_data_create_variable, "", typeLabel), namePill)
PillUtil.buildSpannable(
context,
context.getString(R.string.summary_vflow_data_create_variable, "", typeLabel),
namePill,
PillUtil.richTextPreview(rawText)
)
}
}

// 如果是字典、列表或坐标,且不是单纯的变量引用
// 此时 VariableValueUIProvider 会显示详细预览列表,摘要中隐藏 value pill 以防重复
// 此时内部的列表/字典预览会显示详细内容,摘要中隐藏 value pill 以防重复
if ((type == TYPE_DICTIONARY || type == TYPE_LIST || type == TYPE_COORDINATE) && !rawText.isMagicVariable() && !rawText.isNamedVariable()) {
return buildSimpleSummary(context, name, type)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import com.chaomixian.vflow.core.types.VTypeRegistry
import com.chaomixian.vflow.core.types.basic.VNull
import com.chaomixian.vflow.core.workflow.model.ActionStep
import com.chaomixian.vflow.ui.workflow_editor.PillUtil
import com.chaomixian.vflow.ui.workflow_editor.RichTextUIProvider

class ModifyVariableModule : BaseModule() {
override val id = "vflow.variable.modify"
Expand All @@ -35,8 +34,6 @@ class ModifyVariableModule : BaseModule() {
requiredInputIds = setOf("variable", "newValue")
)

override val uiProvider: ModuleUIProvider? = RichTextUIProvider("newValue")

override fun getInputs(): List<InputDefinition> = listOf(
InputDefinition(
id = "variable", // ID 从 "variableName" 改为 "variable"
Expand Down Expand Up @@ -67,12 +64,12 @@ class ModifyVariableModule : BaseModule() {
getInputs().find { it.id == "variable" }
)

// 如果新值是复杂内容,RichTextUIProvider 会显示预览,这里只显示变量名
// 如果新值是复杂内容,预览层会显示富文本内容,这里只显示变量名
val rawValue = step.parameters["newValue"]?.toString() ?: ""
if (VariableResolver.isComplex(rawValue)) {
val prefix = context.getString(R.string.summary_vflow_variable_modify_prefix)
val middle = context.getString(R.string.summary_vflow_variable_modify_middle)
return PillUtil.buildSpannable(context, prefix, namePill, middle)
return PillUtil.buildSpannable(context, prefix, namePill, middle, PillUtil.richTextPreview(rawValue))
}

val valuePill = PillUtil.createPillFromParam(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import com.chaomixian.vflow.core.types.basic.VList
import com.chaomixian.vflow.core.types.basic.VNull
import com.chaomixian.vflow.core.workflow.model.ActionStep
import com.chaomixian.vflow.ui.workflow_editor.PillUtil
import com.chaomixian.vflow.ui.workflow_editor.RichTextUIProvider
import org.json.JSONArray
import org.json.JSONObject

Expand Down Expand Up @@ -66,8 +65,6 @@ class ParseJsonModule : BaseModule() {
)
)

override val uiProvider: ModuleUIProvider? = RichTextUIProvider("json")

override fun getOutputs(step: ActionStep?): List<OutputDefinition> = listOf(
OutputDefinition(
id = "first_value",
Expand Down Expand Up @@ -99,7 +96,8 @@ class ParseJsonModule : BaseModule() {
context,
"使用",
pathPill,
"解析 JSON"
"解析 JSON",
PillUtil.richTextPreview(rawJson)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import com.chaomixian.vflow.core.types.VTypeRegistry
import com.chaomixian.vflow.core.types.basic.VList
import com.chaomixian.vflow.core.workflow.model.ActionStep
import com.chaomixian.vflow.ui.workflow_editor.PillUtil
import com.chaomixian.vflow.ui.workflow_editor.RichTextUIProvider
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import org.xml.sax.InputSource
Expand Down Expand Up @@ -53,8 +52,6 @@ class ParseXmlModule : BaseModule() {
requiredInputIds = setOf("xml", "xpath")
)

override val uiProvider: ModuleUIProvider? = RichTextUIProvider("xml")

override fun getInputs(): List<InputDefinition> = listOf(
InputDefinition(
id = "xml",
Expand Down Expand Up @@ -101,7 +98,7 @@ class ParseXmlModule : BaseModule() {
)

if (VariableResolver.isComplex(rawXml)) {
return PillUtil.buildSpannable(context, "使用", xpathPill, "解析 XML")
return PillUtil.buildSpannable(context, "使用", xpathPill, "解析 XML", PillUtil.richTextPreview(rawXml))
}

val xmlPill = PillUtil.createPillFromParam(
Expand Down
Loading
Loading