feat: simplify SOP to immediate-effect configuration
This commit is contained in:
@@ -2,37 +2,173 @@ package run
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
knowledgecontent "git.iwork-ai.com/xdc/iqudo-top1/internal/knowledge"
|
||||
"git.iwork-ai.com/xdc/iqudo-top1/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var knowledgePlaceholderPattern = regexp.MustCompile(`\{\{\s*(?:input|derived|form)\.([A-Za-z][A-Za-z0-9_]*)\s*\}\}`)
|
||||
|
||||
type KnowledgeView struct {
|
||||
CardID uint64 `json:"card_id"`
|
||||
CardVersionID uint64 `json:"card_version_id"`
|
||||
Version int `json:"version"`
|
||||
Title string `json:"title"`
|
||||
StandardCopy string `json:"standard_copy"`
|
||||
ForbiddenCopy string `json:"forbidden_copy"`
|
||||
RiskNote string `json:"risk_note"`
|
||||
CardID uint64 `json:"card_id"`
|
||||
CardVersionID uint64 `json:"card_version_id"`
|
||||
Version int `json:"version"`
|
||||
Title string `json:"title"`
|
||||
StandardCopy string `json:"standard_copy"`
|
||||
ForbiddenCopy string `json:"forbidden_copy"`
|
||||
RiskNote string `json:"risk_note"`
|
||||
Copies []KnowledgeCopy `json:"copies"`
|
||||
}
|
||||
|
||||
type KnowledgeCopy struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type NodeView struct {
|
||||
model.SOPNode
|
||||
Knowledge *KnowledgeView `json:"knowledge,omitempty"`
|
||||
NodeKey string `json:"node_key"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Config json.RawMessage `json:"config,omitempty"`
|
||||
Fields []PublicFieldView `json:"fields,omitempty"`
|
||||
Presentation *NodePresentation `json:"presentation,omitempty"`
|
||||
Knowledge *KnowledgeView `json:"knowledge,omitempty"`
|
||||
Outputs []KnowledgeGroup `json:"outputs"`
|
||||
Collection *KnowledgeCollectionView `json:"collection,omitempty"`
|
||||
}
|
||||
|
||||
type PublicFieldView struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Required bool `json:"required"`
|
||||
Options json.RawMessage `json:"options"`
|
||||
Validation json.RawMessage `json:"validation"`
|
||||
}
|
||||
|
||||
type KnowledgeGroup struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content json.RawMessage `json:"content"`
|
||||
Relations map[string][]KnowledgeItemView `json:"relations"`
|
||||
Suggested bool `json:"suggested,omitempty"`
|
||||
}
|
||||
|
||||
type KnowledgeItemView struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content json.RawMessage `json:"content"`
|
||||
}
|
||||
|
||||
type knowledgeNodeConfig struct {
|
||||
KnowledgeCardID uint64 `json:"knowledge_card_id"`
|
||||
KnowledgeCardVersionID uint64 `json:"knowledge_card_version_id"`
|
||||
KnowledgeCardID uint64 `json:"knowledge_card_id"`
|
||||
KnowledgeCardVersionID uint64 `json:"knowledge_card_version_id"`
|
||||
KnowledgeSelector *knowledgeSelector `json:"knowledge_selector"`
|
||||
KnowledgeCollection *knowledgeCollectionConfig `json:"knowledge_collection"`
|
||||
}
|
||||
|
||||
func (h *Handler) nodeView(db *gorm.DB, node model.SOPNode, tenantID uint64) (NodeView, error) {
|
||||
view := NodeView{SOPNode: node}
|
||||
type knowledgeCollectionConfig struct {
|
||||
ContextFieldKeys []string `json:"context_field_keys"`
|
||||
ContextTitle string `json:"context_title"`
|
||||
ContextHint string `json:"context_hint"`
|
||||
SelectionTitle string `json:"selection_title"`
|
||||
SelectionHint string `json:"selection_hint"`
|
||||
Steps []knowledgeCollectionStep `json:"steps"`
|
||||
}
|
||||
|
||||
type knowledgeCollectionStep struct {
|
||||
FieldKey string `json:"field_key"`
|
||||
Name string `json:"name"`
|
||||
Root bool `json:"root"`
|
||||
CandidateScope string `json:"candidate_scope"`
|
||||
KnowledgeTypes []string `json:"knowledge_types"`
|
||||
FromField string `json:"from_field"`
|
||||
RelationType string `json:"relation_type"`
|
||||
Required bool `json:"required"`
|
||||
Multiple bool `json:"multiple"`
|
||||
}
|
||||
|
||||
type KnowledgeCollectionView struct {
|
||||
ContextFields []PublicFieldView `json:"context_fields"`
|
||||
ContextTitle string `json:"context_title"`
|
||||
ContextHint string `json:"context_hint"`
|
||||
SelectionTitle string `json:"selection_title"`
|
||||
SelectionHint string `json:"selection_hint"`
|
||||
Steps []KnowledgeCollectionStepView `json:"steps"`
|
||||
}
|
||||
|
||||
type KnowledgeCollectionStepView struct {
|
||||
FieldKey string `json:"field_key"`
|
||||
Name string `json:"name"`
|
||||
Required bool `json:"required"`
|
||||
Multiple bool `json:"multiple"`
|
||||
FromField string `json:"from_field,omitempty"`
|
||||
Options []KnowledgeCollectionOption `json:"options"`
|
||||
}
|
||||
|
||||
type KnowledgeCollectionOption struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
Parents []string `json:"parents,omitempty"`
|
||||
Suggested bool `json:"suggested,omitempty"`
|
||||
}
|
||||
|
||||
type knowledgeSelector struct {
|
||||
DerivedField string `json:"derived_field"`
|
||||
AnswerField string `json:"answer_field"`
|
||||
CandidateScope string `json:"candidate_scope"`
|
||||
KnowledgeTypes []string `json:"knowledge_types"`
|
||||
KnowledgeKeys []string `json:"knowledge_keys"`
|
||||
RelationTypes []string `json:"relation_types"`
|
||||
RelationLabels map[string]string `json:"relation_labels"`
|
||||
}
|
||||
|
||||
func (h *Handler) nodeView(db *gorm.DB, node model.SOPNode, tenantID uint64, answers map[string]interface{}) (NodeView, error) {
|
||||
view := NodeView{NodeKey: node.NodeKey, Type: node.Type, Title: node.Title, Content: node.Content, Config: json.RawMessage(node.Config)}
|
||||
if node.Type == "start" {
|
||||
presentation, err := loadStartPresentation(db, node, tenantID, answers)
|
||||
if err != nil {
|
||||
return view, err
|
||||
}
|
||||
view.Presentation = presentation
|
||||
}
|
||||
if node.Type == "question" || node.Type == "choice" || node.Type == "form" {
|
||||
fields, err := loadPublicNodeFields(db, node, tenantID)
|
||||
if err != nil {
|
||||
return view, err
|
||||
}
|
||||
view.Fields = fields
|
||||
}
|
||||
if node.Type != "knowledge" {
|
||||
return view, nil
|
||||
}
|
||||
knowledge, err := loadKnowledgeView(db, node, tenantID)
|
||||
var config knowledgeNodeConfig
|
||||
if err := json.Unmarshal(node.Config, &config); err != nil {
|
||||
return view, err
|
||||
}
|
||||
if config.KnowledgeSelector != nil {
|
||||
outputs, err := loadKnowledgeOutputs(db, node, tenantID, answers, *config.KnowledgeSelector)
|
||||
view.Outputs = outputs
|
||||
if err != nil {
|
||||
return view, err
|
||||
}
|
||||
if config.KnowledgeCollection != nil {
|
||||
collection, err := loadKnowledgeCollection(db, node, tenantID, *config.KnowledgeCollection, outputs, answers)
|
||||
view.Collection = &collection
|
||||
return view, err
|
||||
}
|
||||
return view, nil
|
||||
}
|
||||
knowledge, err := loadKnowledgeView(db, node, tenantID, answers)
|
||||
if err != nil {
|
||||
return view, err
|
||||
}
|
||||
@@ -40,7 +176,366 @@ func (h *Handler) nodeView(db *gorm.DB, node model.SOPNode, tenantID uint64) (No
|
||||
return view, nil
|
||||
}
|
||||
|
||||
func loadKnowledgeView(db *gorm.DB, node model.SOPNode, tenantID uint64) (KnowledgeView, error) {
|
||||
func loadKnowledgeCollection(db *gorm.DB, node model.SOPNode, tenantID uint64, config knowledgeCollectionConfig, roots []KnowledgeGroup, context map[string]interface{}) (KnowledgeCollectionView, error) {
|
||||
fields, err := loadPublicFieldsByKeys(db, node, tenantID, config.ContextFieldKeys)
|
||||
if err != nil {
|
||||
return KnowledgeCollectionView{}, err
|
||||
}
|
||||
view := KnowledgeCollectionView{ContextFields: fields, ContextTitle: config.ContextTitle, ContextHint: config.ContextHint, SelectionTitle: config.SelectionTitle, SelectionHint: config.SelectionHint, Steps: make([]KnowledgeCollectionStepView, 0, len(config.Steps))}
|
||||
childrenByParent := map[string]map[string][]string{}
|
||||
allItems := make([]model.KnowledgeItem, 0)
|
||||
if raw, ok := context["__knowledge_snapshot"].(json.RawMessage); ok && len(raw) > 0 {
|
||||
snapshot, parseErr := parseKnowledgeSnapshot(raw)
|
||||
if parseErr != nil {
|
||||
return KnowledgeCollectionView{}, parseErr
|
||||
}
|
||||
byID := map[uint64]model.KnowledgeItem{}
|
||||
for _, item := range snapshot.Items {
|
||||
byID[item.ID] = item
|
||||
if item.Status == "active" {
|
||||
allItems = append(allItems, item)
|
||||
}
|
||||
}
|
||||
for _, relation := range snapshot.Relations {
|
||||
from, fromOK := byID[relation.FromKnowledgeID]
|
||||
to, toOK := byID[relation.ToKnowledgeID]
|
||||
if !fromOK || !toOK || from.Status != "active" || to.Status != "active" {
|
||||
continue
|
||||
}
|
||||
if childrenByParent[from.Name] == nil {
|
||||
childrenByParent[from.Name] = map[string][]string{}
|
||||
}
|
||||
childrenByParent[from.Name][relation.RelationType] = appendUnique(childrenByParent[from.Name][relation.RelationType], to.Name)
|
||||
}
|
||||
}
|
||||
suggestedRoots := make(map[string]bool, len(roots))
|
||||
rootOptions := make([]KnowledgeCollectionOption, 0, len(roots))
|
||||
for _, root := range roots {
|
||||
suggestedRoots[root.Name] = root.Suggested
|
||||
rootOptions = append(rootOptions, KnowledgeCollectionOption{Value: root.Name, Label: root.Name, Suggested: root.Suggested})
|
||||
}
|
||||
for _, step := range config.Steps {
|
||||
item := KnowledgeCollectionStepView{FieldKey: step.FieldKey, Name: step.Name, Required: step.Required, Multiple: step.Multiple, FromField: step.FromField, Options: []KnowledgeCollectionOption{}}
|
||||
if step.Root {
|
||||
item.Options = append(item.Options, rootOptions...)
|
||||
if step.CandidateScope == "all" {
|
||||
seen := map[string]bool{}
|
||||
for _, option := range item.Options {
|
||||
seen[option.Value] = true
|
||||
}
|
||||
for _, knowledgeItem := range allItems {
|
||||
if seen[knowledgeItem.Name] || (len(step.KnowledgeTypes) > 0 && !containsString(step.KnowledgeTypes, knowledgeItem.Type)) {
|
||||
continue
|
||||
}
|
||||
item.Options = append(item.Options, KnowledgeCollectionOption{Value: knowledgeItem.Name, Label: knowledgeItem.Name, Suggested: suggestedRoots[knowledgeItem.Name]})
|
||||
}
|
||||
sort.SliceStable(item.Options, func(i, j int) bool {
|
||||
if item.Options[i].Suggested != item.Options[j].Suggested {
|
||||
return item.Options[i].Suggested
|
||||
}
|
||||
return item.Options[i].Label < item.Options[j].Label
|
||||
})
|
||||
}
|
||||
} else {
|
||||
seen := map[string]*KnowledgeCollectionOption{}
|
||||
var parentStep *KnowledgeCollectionStepView
|
||||
for index := range view.Steps {
|
||||
if view.Steps[index].FieldKey == step.FromField {
|
||||
parentStep = &view.Steps[index]
|
||||
break
|
||||
}
|
||||
}
|
||||
if parentStep != nil {
|
||||
for _, parent := range parentStep.Options {
|
||||
for _, childName := range childrenByParent[parent.Value][step.RelationType] {
|
||||
option := seen[childName]
|
||||
if option == nil {
|
||||
option = &KnowledgeCollectionOption{Value: childName, Label: childName}
|
||||
seen[childName] = option
|
||||
}
|
||||
option.Parents = appendUnique(option.Parents, parent.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, option := range seen {
|
||||
item.Options = append(item.Options, *option)
|
||||
}
|
||||
sort.Slice(item.Options, func(i, j int) bool { return item.Options[i].Label < item.Options[j].Label })
|
||||
}
|
||||
view.Steps = append(view.Steps, item)
|
||||
}
|
||||
return view, nil
|
||||
}
|
||||
|
||||
func loadPublicFieldsByKeys(db *gorm.DB, node model.SOPNode, tenantID uint64, keys []string) ([]PublicFieldView, error) {
|
||||
if len(keys) == 0 {
|
||||
return []PublicFieldView{}, nil
|
||||
}
|
||||
var fields []model.ScenarioField
|
||||
if err := db.Table("scenario_fields sf").Select("sf.*").Joins("JOIN sops s ON s.scenario_id = sf.scenario_id").Joins("JOIN sop_versions sv ON sv.sop_id = s.id").Where("sv.id = ? AND sf.tenant_id = ? AND sf.field_key IN ?", node.SOPVersionID, tenantID, keys).Find(&fields).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byKey := map[string]model.ScenarioField{}
|
||||
for _, field := range fields {
|
||||
byKey[field.FieldKey] = field
|
||||
}
|
||||
result := make([]PublicFieldView, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
if field, ok := byKey[key]; ok {
|
||||
result = append(result, PublicFieldView{Key: field.FieldKey, Name: field.FieldName, Type: field.FieldType, Required: field.Required, Options: json.RawMessage(field.Options), Validation: json.RawMessage(field.Validation)})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func appendUnique(values []string, value string) []string {
|
||||
for _, existing := range values {
|
||||
if existing == value {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return append(values, value)
|
||||
}
|
||||
|
||||
func loadPublicNodeFields(db *gorm.DB, node model.SOPNode, tenantID uint64) ([]PublicFieldView, error) {
|
||||
var config answerNodeConfig
|
||||
if err := json.Unmarshal(node.Config, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys := config.FieldKeys
|
||||
if config.FieldKey != "" {
|
||||
keys = []string{config.FieldKey}
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var fields []model.ScenarioField
|
||||
if err := db.Table("scenario_fields sf").Select("sf.*").Joins("JOIN sops s ON s.scenario_id = sf.scenario_id").Joins("JOIN sop_versions sv ON sv.sop_id = s.id").Where("sv.id = ? AND sf.tenant_id = ? AND sf.field_key IN ?", node.SOPVersionID, tenantID, keys).Find(&fields).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
byKey := make(map[string]model.ScenarioField, len(fields))
|
||||
for _, field := range fields {
|
||||
byKey[field.FieldKey] = field
|
||||
}
|
||||
views := make([]PublicFieldView, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
field, ok := byKey[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
views = append(views, PublicFieldView{Key: field.FieldKey, Name: field.FieldName, Type: field.FieldType, Required: field.Required || config.Required || containsString(config.RequiredFieldKeys, key), Options: json.RawMessage(field.Options), Validation: json.RawMessage(field.Validation)})
|
||||
}
|
||||
return views, nil
|
||||
}
|
||||
|
||||
func loadKnowledgeOutputs(db *gorm.DB, node model.SOPNode, tenantID uint64, context map[string]interface{}, selector knowledgeSelector) ([]KnowledgeGroup, error) {
|
||||
var sopRow struct{ ScenarioID uint64 }
|
||||
if err := db.Table("sop_nodes n").Select("s.scenario_id").Joins("JOIN sop_versions sv ON sv.id = n.sop_version_id").Joins("JOIN sops s ON s.id = sv.sop_id").Where("n.id = ? AND n.tenant_id = ?", node.ID, tenantID).Scan(&sopRow).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loadKnowledgeOutputsForScenario(db, sopRow.ScenarioID, tenantID, context, selector)
|
||||
}
|
||||
|
||||
func loadKnowledgeOutputsForScenario(db *gorm.DB, scenarioID, tenantID uint64, context map[string]interface{}, selector knowledgeSelector) ([]KnowledgeGroup, error) {
|
||||
keys := append([]string{}, selector.KnowledgeKeys...)
|
||||
if selector.DerivedField != "" {
|
||||
value, _ := lookupContextValue(context, "derived."+selector.DerivedField)
|
||||
keys = append(keys, stringSlice(value)...)
|
||||
}
|
||||
if selector.AnswerField != "" {
|
||||
value, _ := lookupContextValue(context, "form."+selector.AnswerField)
|
||||
keys = append(keys, stringSlice(value)...)
|
||||
}
|
||||
if len(keys) == 0 && selector.CandidateScope != "all" {
|
||||
return []KnowledgeGroup{}, nil
|
||||
}
|
||||
keySet := make(map[string]bool, len(keys))
|
||||
for _, key := range keys {
|
||||
keySet[key] = true
|
||||
}
|
||||
items := make([]model.KnowledgeItem, 0)
|
||||
relations := make([]model.KnowledgeRelation, 0)
|
||||
snapshotTargets := map[uint64]model.KnowledgeItem{}
|
||||
if raw, ok := context["__knowledge_snapshot"].(json.RawMessage); ok && len(raw) > 0 {
|
||||
snapshot, err := parseKnowledgeSnapshot(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range snapshot.Items {
|
||||
snapshotTargets[item.ID] = item
|
||||
if item.Status != "active" {
|
||||
continue
|
||||
}
|
||||
if selector.CandidateScope != "all" && len(keys) > 0 && !knowledgeCandidateMatches(keySet, item) {
|
||||
continue
|
||||
}
|
||||
if len(selector.KnowledgeTypes) > 0 && !containsString(selector.KnowledgeTypes, item.Type) {
|
||||
continue
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
for _, relation := range snapshot.Relations {
|
||||
if len(selector.RelationTypes) > 0 && !containsString(selector.RelationTypes, relation.RelationType) {
|
||||
continue
|
||||
}
|
||||
relations = append(relations, relation)
|
||||
}
|
||||
} else {
|
||||
query := db.Where("tenant_id = ? AND scenario_id = ? AND status = ?", tenantID, scenarioID, "active")
|
||||
if selector.CandidateScope != "all" && len(keys) > 0 {
|
||||
query = query.Where("item_key IN ? OR name IN ?", keys, keys)
|
||||
}
|
||||
if len(selector.KnowledgeTypes) > 0 {
|
||||
query = query.Where("type IN ?", selector.KnowledgeTypes)
|
||||
}
|
||||
if err := query.Order("sort_order, id").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ids := make([]uint64, 0, len(items))
|
||||
for _, item := range items {
|
||||
if selector.CandidateScope == "all" || len(keySet) == 0 || knowledgeCandidateMatches(keySet, item) {
|
||||
ids = append(ids, item.ID)
|
||||
}
|
||||
}
|
||||
if len(relations) == 0 && len(ids) > 0 {
|
||||
rq := db.Where("tenant_id = ? AND scenario_id = ? AND from_knowledge_id IN ?", tenantID, scenarioID, ids)
|
||||
if len(selector.RelationTypes) > 0 {
|
||||
rq = rq.Where("relation_type IN ?", selector.RelationTypes)
|
||||
}
|
||||
if err := rq.Order("sort_order,id").Find(&relations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
targetIDs := make([]uint64, 0, len(relations))
|
||||
for _, rel := range relations {
|
||||
targetIDs = append(targetIDs, rel.ToKnowledgeID)
|
||||
}
|
||||
targets := make([]model.KnowledgeItem, 0)
|
||||
if len(snapshotTargets) > 0 {
|
||||
for _, id := range targetIDs {
|
||||
if target, ok := snapshotTargets[id]; ok && target.Status == "active" {
|
||||
targets = append(targets, target)
|
||||
}
|
||||
}
|
||||
} else if len(targetIDs) > 0 {
|
||||
if err := db.Where("tenant_id = ? AND id IN ? AND status = ?", tenantID, targetIDs, "active").Find(&targets).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
targetMap := map[uint64]model.KnowledgeItem{}
|
||||
for _, target := range targets {
|
||||
targetMap[target.ID] = target
|
||||
}
|
||||
relationMap := map[uint64]map[string][]KnowledgeItemView{}
|
||||
for _, rel := range relations {
|
||||
matched, err := matchCondition(json.RawMessage(rel.Condition), context)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("知识关系 %d 条件不正确: %w", rel.ID, err)
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
target, ok := targetMap[rel.ToKnowledgeID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if relationMap[rel.FromKnowledgeID] == nil {
|
||||
relationMap[rel.FromKnowledgeID] = map[string][]KnowledgeItemView{}
|
||||
}
|
||||
relationMap[rel.FromKnowledgeID][rel.RelationType] = append(relationMap[rel.FromKnowledgeID][rel.RelationType], KnowledgeItemView{Key: target.ItemKey, Name: target.Name, Type: target.Type, Content: renderKnowledgeContent(target.Content, context)})
|
||||
}
|
||||
outputs := make([]KnowledgeGroup, 0, len(items))
|
||||
for _, item := range items {
|
||||
outputs = append(outputs, KnowledgeGroup{Key: item.ItemKey, Name: item.Name, Type: item.Type, Content: renderKnowledgeContent(item.Content, context), Relations: relationMap[item.ID], Suggested: knowledgeCandidateMatches(keySet, item)})
|
||||
}
|
||||
return outputs, nil
|
||||
}
|
||||
|
||||
func knowledgeCandidateMatches(candidates map[string]bool, item model.KnowledgeItem) bool {
|
||||
return candidates[item.ItemKey] || candidates[item.Name]
|
||||
}
|
||||
|
||||
func containsString(values []string, target string) bool {
|
||||
for _, value := range values {
|
||||
if value == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func renderKnowledgeContent(raw []byte, context map[string]interface{}) json.RawMessage {
|
||||
var value interface{}
|
||||
if json.Unmarshal(raw, &value) != nil {
|
||||
return json.RawMessage(raw)
|
||||
}
|
||||
value = renderKnowledgeValue(value, context)
|
||||
rendered, _ := json.Marshal(value)
|
||||
return rendered
|
||||
}
|
||||
func renderKnowledgeValue(value interface{}, context map[string]interface{}) interface{} {
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return knowledgePlaceholderPattern.ReplaceAllStringFunc(typed, func(token string) string {
|
||||
match := knowledgePlaceholderPattern.FindStringSubmatch(token)
|
||||
if len(match) != 2 {
|
||||
return token
|
||||
}
|
||||
resolved, ok := lookupContextValue(context, tokenNamespaceKey(token, match[1]))
|
||||
if !ok || resolved == nil {
|
||||
return "未提供"
|
||||
}
|
||||
if values, ok := resolved.([]interface{}); ok {
|
||||
parts := make([]string, 0, len(values))
|
||||
for _, item := range values {
|
||||
parts = append(parts, fmt.Sprint(item))
|
||||
}
|
||||
return strings.Join(parts, "、")
|
||||
}
|
||||
return fmt.Sprint(resolved)
|
||||
})
|
||||
case []interface{}:
|
||||
for index, item := range typed {
|
||||
typed[index] = renderKnowledgeValue(item, context)
|
||||
}
|
||||
return typed
|
||||
case map[string]interface{}:
|
||||
for key, item := range typed {
|
||||
typed[key] = renderKnowledgeValue(item, context)
|
||||
}
|
||||
return typed
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
func tokenNamespaceKey(token, key string) string {
|
||||
trimmed := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(token, "{{"), "}}"))
|
||||
if strings.Contains(trimmed, ".") {
|
||||
return trimmed
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func stringSlice(value interface{}) []string {
|
||||
result := []string{}
|
||||
switch values := value.(type) {
|
||||
case []interface{}:
|
||||
for _, item := range values {
|
||||
if text, ok := item.(string); ok {
|
||||
result = append(result, text)
|
||||
}
|
||||
}
|
||||
case []string:
|
||||
return values
|
||||
case string:
|
||||
return []string{values}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func loadKnowledgeView(db *gorm.DB, node model.SOPNode, tenantID uint64, answers map[string]interface{}) (KnowledgeView, error) {
|
||||
var config knowledgeNodeConfig
|
||||
if err := json.Unmarshal(node.Config, &config); err != nil {
|
||||
return KnowledgeView{}, err
|
||||
@@ -61,16 +556,21 @@ func loadKnowledgeView(db *gorm.DB, node model.SOPNode, tenantID uint64) (Knowle
|
||||
if err := query.First(&version).Error; err != nil {
|
||||
return KnowledgeView{}, err
|
||||
}
|
||||
var content struct {
|
||||
StandardCopy string `json:"standard_copy"`
|
||||
ForbiddenCopy string `json:"forbidden_copy"`
|
||||
RiskNote string `json:"risk_note"`
|
||||
}
|
||||
if err := json.Unmarshal(version.Content, &content); err != nil {
|
||||
content, err := knowledgecontent.ParseContent(version.Content)
|
||||
if err != nil {
|
||||
return KnowledgeView{}, err
|
||||
}
|
||||
rendered := knowledgecontent.Render(content, answers)
|
||||
copies := make([]KnowledgeCopy, 0, len(rendered))
|
||||
for _, copy := range rendered {
|
||||
copies = append(copies, KnowledgeCopy{ID: copy.ID, Title: copy.Title, Content: copy.Content})
|
||||
}
|
||||
standardCopy := ""
|
||||
if len(copies) > 0 {
|
||||
standardCopy = copies[0].Content
|
||||
}
|
||||
return KnowledgeView{
|
||||
CardID: version.KnowledgeCardID, CardVersionID: version.ID, Version: version.Version, Title: version.Title,
|
||||
StandardCopy: content.StandardCopy, ForbiddenCopy: content.ForbiddenCopy, RiskNote: content.RiskNote,
|
||||
StandardCopy: standardCopy, ForbiddenCopy: content.ForbiddenCopy, RiskNote: content.RiskNote, Copies: copies,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user