import React, { useCallback, useEffect, useState } from 'react'; import { Table, Button, Space, Modal, Form, Input, Select, Switch, Tag, Popconfirm, message, Empty, Tooltip, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, BranchesOutlined } from '@ant-design/icons'; import { useApp } from '@nocobase/client-v2'; import { useGitManager } from '../context/GitManagerContext'; import { AIEmployeeSelect } from './AIEmployeeSelect'; import { LLMServiceSelect } from './LLMServiceSelect'; import { LLMModelSelect } from './LLMModelSelect'; import { REVIEW_PROMPT_TEMPLATES } from '../promptTemplates'; import { useT } from '../locale'; const TRIGGER_LABELS: Record = { manual: 'Manual', onMergeRequestCreated: 'On MR Created', both: 'Both', }; const POST_LABELS: Record = { auto: 'Auto', manual: 'Manual', disabled: 'Disabled', }; export const ReviewFlows: React.FC = () => { const t = useT(); const api = useApp().apiClient; const { repos } = useGitManager(); const [flows, setFlows] = useState([]); const [loading, setLoading] = useState(false); const [editing, setEditing] = useState(null); const [open, setOpen] = useState(false); const [templateKey, setTemplateKey] = useState(); const [form] = Form.useForm(); const load = useCallback(async () => { setLoading(true); try { const { data } = await api.request({ url: 'gitReviewFlows:list', params: { pageSize: 100 } }); setFlows(data?.data || []); } finally { setLoading(false); } }, [api]); useEffect(() => { load(); }, [load]); const handleSave = async () => { try { const values = await form.validateFields(); // Normalize empty string to null for repositoryId if (values.repositoryId === undefined || values.repositoryId === '') { values.repositoryId = null; } if (editing?.id) { await api.request({ url: 'gitReviewFlows:update', method: 'post', params: { filterByTk: editing.id }, data: values, }); message.success(t('Flow updated')); } else { await api.request({ url: 'gitReviewFlows:create', method: 'post', data: values }); message.success(t('Flow created')); } setOpen(false); setEditing(null); setTemplateKey(undefined); form.resetFields(); load(); } catch (err: any) { if (err?.errorFields) return; message.error(err?.response?.data?.errors?.[0]?.message || err?.message || t('Failed to save')); } }; const handleDelete = async (id: number) => { try { await api.request({ url: 'gitReviewFlows:destroy', method: 'post', params: { filterByTk: id } }); message.success(t('Flow deleted')); load(); } catch (err: any) { message.error(err?.message || t('Failed to delete')); } }; const openCreate = () => { setEditing(null); form.resetFields(); setTemplateKey('application'); form.setFieldsValue({ enabled: true, triggerMode: 'manual', postMode: 'manual', instructions: REVIEW_PROMPT_TEMPLATES.find((tpl) => tpl.key === 'application')?.text, }); setOpen(true); }; const openEdit = (record: any) => { setEditing(record); setTemplateKey(undefined); form.setFieldsValue(record); setOpen(true); }; const columns = [ { title: t('Name'), dataIndex: 'name', render: (v: string, r: any) => ( {v} {!r.enabled && {t('Disabled')}} ), }, { title: t('Repository'), dataIndex: 'repositoryId', render: (v: number | null) => { if (v == null) return {t('Global')}; const repo = repos.find((r: any) => r.id === v); return repo ? ( {repo.name} ) : ( `#${v}` ); }, }, { title: t('AI Employee'), dataIndex: 'aiEmployeeUsername', render: (v: string) => (v ? @{v} : ), }, { title: t('Trigger'), dataIndex: 'triggerMode', render: (v: string) => {t(TRIGGER_LABELS[v] || v)}, }, { title: t('Post'), dataIndex: 'postMode', render: (v: string) => { const color = v === 'auto' ? 'green' : v === 'manual' ? 'orange' : 'default'; return {t(POST_LABELS[v] || v)}; }, }, { title: '', key: 'actions', width: 100, render: (_: any, record: any) => ( }} pagination={{ pageSize: 20 }} /> { setOpen(false); setEditing(null); setTemplateKey(undefined); form.resetFields(); }} onOk={handleSave} width={640} destroyOnClose >
)} ); }}