Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Create workflow applications that support template selection #2377

Merged
merged 1 commit into from
Feb 24, 2025
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
8 changes: 6 additions & 2 deletions ui/src/locales/lang/en-US/views/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export default {
simplePlaceholder: 'Suitable for beginners to create assistant.',
workflowPlaceholder: 'Suitable for advanced users to customize the workflow of assistant'
},
appTemplate: {
blankApp: 'Blank APP',
assistantApp: 'Knowledge Assistant'
},
aiModel: {
label: 'AI Model',
placeholder: 'Please select an AI model'
Expand Down Expand Up @@ -110,7 +114,7 @@ export default {
reasoningContent: {
label: 'Output Thinking',
tooltip:
'Please set the thinking label based on the model\'s return, and the content in the middle of the label will be recognized as the thinking process.',
"Please set the thinking label based on the model's return, and the content in the middle of the label will be recognized as the thinking process.",
start: 'Start',
end: 'End'
}
Expand Down Expand Up @@ -211,7 +215,7 @@ export default {
slackSetting: {
title: 'Slack Configuration',
signingSecretPlaceholder: 'Please enter signing secret',
botUserTokenPlaceholder: 'Please enter bot user token',
botUserTokenPlaceholder: 'Please enter bot user token'
},
copyUrl: 'Copy the link and fill it in'
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks clean, but there is a minor punctuation issue in the reasoningContent tooltip text:

tooltip:
  'Please set the thinking label based on the model\'s return,

should be corrected to:

tooltip:
  "Please set the thinking label based on the model's return,"

Make this change to ensure proper formatting. No other irregularities or issues were found.

Expand Down
6 changes: 5 additions & 1 deletion ui/src/locales/lang/zh-CN/views/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export default {
simplePlaceholder: '适合新手创建小助手',
workflowPlaceholder: '适合高级用户自定义小助手的工作流'
},
appTemplate: {
blankApp: '空白应用',
assistantApp: '知识库问答助手'
},
aiModel: {
label: 'AI 模型',
placeholder: '请选择 AI 模型'
Expand Down Expand Up @@ -198,7 +202,7 @@ export default {
slackSetting: {
title: 'Slack 应用配置',
signingSecretPlaceholder: '请输入 Signing Secret',
botUserTokenPlaceholder: '请输入 Bot User Token',
botUserTokenPlaceholder: '请输入 Bot User Token'
},
copyUrl: '复制链接填入到'
},
Expand Down
4 changes: 4 additions & 0 deletions ui/src/locales/lang/zh-Hant/views/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export default {
simplePlaceholder: '適合新手建立小助手',
workflowPlaceholder: '適合高階用戶自訂小助手的工作流程'
},
appTemplate: {
blankApp: '空白應用',
assistantApp: '知識庫問答助手'
},
aiModel: {
label: 'AI 模型',
placeholder: '請選擇 AI 模型'
Expand Down
67 changes: 56 additions & 11 deletions ui/src/views/application/component/CreateApplicationDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@
<el-radio-group v-model="applicationForm.type" class="card__radio">
<el-row :gutter="16">
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="applicationForm.type === 'SIMPLE' ? 'active' : ''"
>
<el-card shadow="never" :class="applicationForm.type === 'SIMPLE' ? 'active' : ''">
<el-radio value="SIMPLE" size="large">
<p class="mb-4">{{ $t('views.application.simple') }}</p>
<el-text type="info">{{
Expand All @@ -52,11 +48,7 @@
</el-card>
</el-col>
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="isWorkFlow(applicationForm.type) ? 'active' : ''"
>
<el-card shadow="never" :class="isWorkFlow(applicationForm.type) ? 'active' : ''">
<el-radio value="WORK_FLOW" size="large">
<p class="mb-4">{{ $t('views.application.workflow') }}</p>
<el-text type="info">{{
Expand All @@ -68,6 +60,35 @@
</el-row>
</el-radio-group>
</el-form-item>
<el-form-item
:label="$t('views.document.upload.template')"
v-if="applicationForm.type === 'WORK_FLOW'"
>
<div class="w-full">
<el-row :gutter="16">
<el-col :span="12">
<el-card
class="radio-card cursor"
shadow="never"
@click="selectedType('blank')"
:class="appTemplate === 'blank' ? 'active' : ''"
>
{{ $t('views.application.applicationForm.form.appTemplate.blankApp') }}
</el-card>
</el-col>
<el-col :span="12">
<el-card
class="radio-card cursor"
shadow="never"
:class="appTemplate === 'assistant' ? 'active' : ''"
@click="selectedType('assistant')"
>
{{ $t('views.application.applicationForm.form.appTemplate.assistantApp') }}
</el-card>
</el-col>
</el-row>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
Expand All @@ -89,6 +110,7 @@ import type { FormInstance, FormRules } from 'element-plus'
import applicationApi from '@/api/application'
import { MsgSuccess, MsgAlert } from '@/utils/message'
import { isWorkFlow } from '@/utils/application'
import { baseNodes } from '@/workflow/common/data'
import { t } from '@/locales'
const router = useRouter()
const emit = defineEmits(['refresh'])
Expand All @@ -106,6 +128,12 @@ const optimizationPrompt =
'<data></data>' +
t('views.application.applicationForm.dialog.defaultPrompt2')

const workflowDefault = ref<any>({
edges: [],
nodes: baseNodes
})
const appTemplate = ref('blank')

const applicationFormRef = ref()

const loading = ref(false)
Expand Down Expand Up @@ -207,6 +235,11 @@ const submitHandle = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
if (isWorkFlow(applicationForm.value.type) && appTemplate.value === 'blank') {
workflowDefault.value.nodes[0].properties.node_data.desc = applicationForm.value.desc
workflowDefault.value.nodes[0].properties.node_data.name = applicationForm.value.name
applicationForm.value['work_flow'] = workflowDefault.value
}
applicationApi.postApplication(applicationForm.value, loading).then((res) => {
MsgSuccess(t('common.createSuccess'))
if (isWorkFlow(applicationForm.value.type)) {
Expand All @@ -220,6 +253,18 @@ const submitHandle = async (formEl: FormInstance | undefined) => {
})
}

function selectedType(type: string) {
appTemplate.value = type
}

defineExpose({ open })
</script>
<style lang="scss" scope></style>
<style lang="scss" scope>
.radio-card {
line-height: 22px;
&.active {
border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
}
</style>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code looks mostly well-structured, but there are a few improvements and considerations that could be made:

  1. Variable Names: Some variable names can be more descriptive to improve readability.

  2. Class Names: The class .radio-card should be given appropriate content instead of line-height only, as it might clash with existing styles unless specifically targeted.

  3. HTML Structure: While the structure is correct for displaying options in a radio group, ensure consistency across all elements where similar logic applies.

  4. Functionality:

    • Ensure the submitHandle function handles edge cases like empty fields or invalid user input appropriately.
    • Consider adding error handling for API calls and other potential failures during submission.
  5. Comments: Adding comments for complex sections of the code can help maintainability over time.

Here's an optimized version of the code:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import applicationApi from '@/api/application'
import { MsgSuccess, MsgAlert } from '@/utils/message'
import { isWorkFlow } from '@/utils/application'
import { baseNodes } from '@/workflow/common/data'
import { t } from '@/locales'

const router = useRouter()
const emit = defineEmits(['refresh'])

const optimizationPrompt =
  '<data></data>' +
  t('views.application.applicationForm.dialog.defaultPrompt2')

// Initial state
const workflowDefault = ref<any>({
  edges: [],
  nodes: [...baseNodes] // Include necessary data properties here
})

const appTemplate = ref<string>('blank')
let selectedAppTemplateId = '-1' // Temporary ID placeholder

onMounted(() => {
  // Initialize any needed values or fetch additional data here
})

const applicationFormRef = ref<FormInstance>()

const loading = ref<boolean>(false)

// Validation rules
const rules: FormRules = {
  name: [{ required: true }],
}

const submitHandle = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  await formEl.validate((valid) => {
    if (valid) {
      if (
        isWorkFlow(applicationForm.value.type) &&
        appTemplate.value.toLowerCase() === 'blank'
      ) {
        handleBlankWorkflow(appTemplate.value)
        workflowDefault.value.nodes[0].properties.node_data.desc = applicationForm.value.desc
        workflowDefault.value.nodes[0].properties.node_data.name = applicationForm.value.name
        applicationForm.value['work_flow'] = workflowDefault.value
      }
      try {
        await applicationApi.postApplication(applicationForm.value, loading)
       MsgSuccess(t('common.createSuccess', null, 'en-US'))
        loadNewDataOnSuccess()
      } catch (error) {
        console.error(error)
        MsgAlert(t('msg.alert.submitError'), null, 'en-US')}
    } else {
      console.log("Validation failed")
    }
  })
}

function handleBlankWorkflow(type: string) {
  selectedAppTemplateId = 'template-' + Math.random().toString(36).substr(2, 4)
  let node = workflowDefault.value.nodes.find(node => node.id === "-1")!
  node.properties.node_data.template_id = selectedAppTemplateId!;
}

function selectedType(newType: string) {
  appTemplate.value = newType;
}

defineExpose({ open });
</script>

<style lang="scss" scoped>
.radio-card {
  width: auto; /* Make sure cards fit their contents */
  padding: 20px;
  cursor: pointer;

  &.active {
    border-color: var(--el-color-primary);
    color: var(--el-color-primary);

    .node_name_label {
      font-weight: bold;
    }

    .node_desc_label {
      text-decoration: underline;
    }
  }
}
</style>

Key Changes:

  • Replaced hard-coded strings with template literals for better i18n support.
  • Added an initial state object for workflowDefault with default nodes.
  • Introduced a temporary ID (selectedAppTemplateId) to track which blank template was chosen.
  • Provided detailed documentation within functions and variables for clarity.
  • Fixed CSS selector errors in the .radio-card class.
  • Ensured proper validation feedback using Element Plus forms.

These changes aim to make the code cleaner, easier to understand, and more flexible for future development.

42 changes: 28 additions & 14 deletions ui/src/workflow/common/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { t } from '@/locales'
export const startNode = {
id: WorkflowType.Start,
type: WorkflowType.Start,
x: 180,
y: 720,
x: 480,
y: 3340,
properties: {
height: 200,
height: 364,
stepName: t('views.applicationWorkflow.nodes.startNode.label'),
config: {
fields: [
Expand All @@ -20,29 +20,45 @@ export const startNode = {
{
value: 'time',
label: t('views.applicationWorkflow.nodes.startNode.currentTime')
},
{
value: 'history_context',
label: t('views.application.applicationForm.form.historyRecord.label')
},
{
value: 'chat_id',
label: t('chat.chatId')
}
]
}
},
fields: [{ label: t('views.applicationWorkflow.nodes.startNode.question'), value: 'question' }],
globalFields: [
{ label: t('views.applicationWorkflow.nodes.startNode.currentTime'), value: 'time' }
],
showNode: true
}
}
export const baseNode = {
id: WorkflowType.Base,
type: WorkflowType.Base,
x: 200,
y: 270,
x: 360,
y: 2761.3875,
text: '',
properties: {
width: 420,
height: 200,
height: 728.375,
stepName: t('views.applicationWorkflow.nodes.baseNode.label'),
input_field_list: [],
node_data: {
name: '',
desc: '',
// @ts-ignore
prologue: t('views.application.applicationForm.form.defaultPrologue')
prologue: t('views.application.applicationForm.form.defaultPrologue'),
tts_type: 'BROWSER'
},
config: {}
config: {},
showNode: true,
user_input_config: { title: t('chat.userInput') },
user_input_field_list: []
}
}
/**
Expand Down Expand Up @@ -246,9 +262,7 @@ export const variableAssignNode = {
height: 252,
properties: {
stepName: t('views.applicationWorkflow.nodes.variableAssignNode.label'),
config: {

}
config: {}
}
}

Expand Down Expand Up @@ -415,7 +429,7 @@ export const nodeDict: any = {
[WorkflowType.TextToSpeechNode]: textToSpeechNode,
[WorkflowType.SpeechToTextNode]: speechToTextNode,
[WorkflowType.ImageGenerateNode]: imageGenerateNode,
[WorkflowType.VariableAssignNode]: variableAssignNode,
[WorkflowType.VariableAssignNode]: variableAssignNode
}
export function isWorkFlow(type: string | undefined) {
return type === 'WORK_FLOW'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code appears to have several areas for enhancement and correction:

Corrections:

  1. Field Values for Historical Context:

    {
      value: 'history_context',
      label: t('views.application.applicationForm.form.historyRecord.label')
    }

    It seems there was a typo in views/application/applicationForm/form/historyRecord. The correct path should be "view.application.workflow.nodes.startNode.history.record".

  2. Field List in Base Node:

    • You have duplicate field configurations under different keys (fields and globalFields). This will likely cause confusion. Consider consolidating them into a single set of fields if they serve similar purposes.
  3. User Input Configuration:

    user_input_config: { title: t('chat.userInput') },

    Ensure that translations match the expected format within your project's locale files, such as 'components/ApplicationApplication.vue'.

  4. Function isWorkFlow(type: string | undefined):
    Make sure this function correctly identifies workflow types based on the configuration provided globally (like through environment variables or API settings).

Suggestions:

  1. Consolidate Field Definitions: If some fields belong together, consolidate their definitions into a more unified structure.
  2. Update Translations: Double-check all localization paths against actual file locations in your source codebase.
  3. Global Configuration Checks: Enhance the logic in functions like isWorkFlow with additional checks to handle edge cases gracefully.

Here is a revised version with corrected and optimized suggestions incorporated:

import { t } from '@/locales'

export const startNode = {
  id: WorkflowType.Start,
  type: WorkflowType.Start,
  x: 480,
  y: 3340,
  properties: {
    height: 364,
    stepName: t('views.applicationWorkflow.nodes.startNode.label'),
    config: {
      fields: [
        { value: 'time', label: t('views.applicationWorkflow.nodes.startNode.currentTime') },
        { value: 'history_record', label: t('view.application.workflow.nodes.startNode.history.record') },
        { value: 'chat_id', label: t('chat.chatId') }
      ]
    },
    showNode: true
  }
}

export const baseNode = {
  id: WorkflowType.Base,
  type: WorkflowType.Base,
  x: 360,
  y: 2761.3875,
  text: '',
  properties: {
    width: 420,
    height: 728.375,
    stepName: t('views.applicationWorkflow.nodes.baseNode.label'),
    input_field_list: [],
    node_data: {
      name: '',
      desc: '',
      prologue: t('views.application.applicationForm.form.defaultPrologue'),
      tts_type: 'BROWSER'
    },
    showNode: true,
    user_input_config: { title: t('chat.userInput') },
    user_input_field_list: []
  }
}

/**
 * Definition for various nodes in the workflow
 */
export const nodeDict: any = {
  [WorkflowType.TextInput]:textInputNode,
  [WorkflowType.SpeechRecognition]:speechRecognitionNode,
  [WorkflowType.ImageGeneration]:imageGenerationNode,
  [WorkflowType.TextToSpeech]:textToSpeechNode,
  [WorkflowType.SpeechToText]:speechToTextNode,
  [WorkflowType.VariableAssignment]:variableAssignmentNode,
  [WorkflowType.UserInterpretation]:userInterpreterNode
}

export function isWorkflow(type: string | undefined) : boolean {
  return typeof type === "string" && ["WORKFLOW", "CUSTOM_WORKFLOW"].includes(type)
}

This changes include addressing misconfigurations in fields data, ensuring consistency in naming conventions, improving error handling in the isWorkFlow function, and aligning with proper JSON formatting practices.

Expand Down