通义千问聊天机器人

show with app
  • global.R
  • server.R
  • ui.R
library(reticulate)
library(stringr)

# Load OpenAI API
print(getwd())
path<- "../../scripts/qianwen.py"
print(path)
#使用指定的Conda虚拟环境"openai"运行Python程序
use_condaenv(condaenv="base", conda = "/usr/lib64/anaconda3/bin/conda", required = TRUE)
source_python(path)

context<-data.frame("role"=character(),"content"=character())
context<-rbind(context, list(role="system",content="You are a helpful assistant."))
function(input, output, session) {
  
  ifContext<- reactive({
    input$ifContext
  })
  
  prompt<- reactive({
    input$prompt
  })
  
  observeEvent(input$clearContext,{
    session$userData$context<- context
    updateTextAreaInput(session, "context", value = "")
  })
  
  observeEvent(input$sendout,{
    if(ifContext() && ! is.null(session$userData$context)){
      temp<-session$userData$context
    } else{
      temp<- context
    }
    temp<-rbind(temp, list(role="user",content=prompt()))      
    # Show notification while querying.
    id <- showNotification("正在咨询通义千问...", duration = NULL, closeButton = FALSE)
    on.exit(removeNotification(id), add = TRUE)
    
    response<- CallAPI(temp)
    #browser()
    temp<-rbind(temp, list(role="assistant",content=response))
    session$userData$context<- temp
    # 格式化输出,插入对话者身份前缀。
    outStr<-""
    for(i in 2: dim(temp)[[1]]){
      if (i%%2==0){
        outStr<-paste(outStr,"User: ",temp[i,2],"\n",sep="")
      }else{
        outStr<-paste(outStr,"AI: ",temp[i,2],"\n\n",sep="")
      }
    }
    updateTextAreaInput(session, "context", value = outStr)
    updateTextAreaInput(session, "prompt", value = "")
    # 对话上下文自动滚动到底部
    session$sendCustomMessage(type = 'scrollToBottom', message = '')
  })
  
  observeEvent(input$restoreContext,{
    if(! is.null(session$userData$context)){
      temp<-session$userData$context
      # 格式化输出,插入对话者身份前缀。
      outStr<-""
      for(i in 2: dim(temp)[[1]]){
        if (i%%2==0){
          outStr<-paste(outStr,"User: ",temp[i,2],"\n",sep="")
        }else{
          outStr<-paste(outStr,"AI: ",temp[i,2],"\n\n",sep="")
        }
      }
      updateTextAreaInput(session, "context", value = outStr)
      updateTextAreaInput(session, "prompt", value = "")      
    }
  })
  
}
# 在输出文本中包含滚动条的另一种方式。
# https://stackoverflow.com/questions/61007632/container-settings-in-text-output-shiny
fluidPage(
  # 对话上下文自动滚动到底部的JavaScript函数
  tags$script(HTML("
    function scrollToBottom() {
      var textarea = document.getElementById('context');
      console.log('Before scroll:', textarea.scrollTop, textarea.scrollHeight);
      setTimeout(function() {
        textarea.scrollTop = textarea.scrollHeight;
      }, 100); // 可以根据需要调整延迟时间
      console.log('After scroll:', textarea.scrollTop, textarea.scrollHeight);
    }

    Shiny.addCustomMessageHandler('scrollToBottom', function(message) {
      scrollToBottom();
    });
  ")),
  
  sidebarLayout(
    # Sidebar 
    sidebarPanel(
      # Application title
      tags$h3("通义千问聊天机器人"),
      checkboxInput("ifContext","是否包括上下文:", value = TRUE),
      actionButton("clearContext", "清除上下文", class = "btn-cleear"),
      actionButton("restoreContext", "恢复中断对话", class = "btn-success"),
      # 1/6
      width = 3
    ),
    # Main 
    mainPanel(
      textAreaInput("context","对话记录",width="100%", rows =26, resize="vertical", value =""),  
      # 插入javascript,禁止自己修改 context textAreaInput
      tags$script(HTML("
        var context = document.getElementById('context');
        context.readOnly = true;
      ")),
      tags$h6(" "),
      textAreaInput("prompt","输入:",width="100%", rows =4, resize="vertical", value =""),  
      actionButton("sendout", "提交", class = "btn-success"),
      # 5/6
      # width = 10
    )
  )
)