把 websocket 处理流式数据部分放在 shinyApp 里问题基本得到解决。具体如下:
首先编制一个函数,这个函数只负责返回鉴权后的 url 以及 json 结构的请求体,代码如下:
xinghuo = function(prompt) {
# 设置KEY、密钥等
APIKey = "XXXXXXXXXX"
APISecret = "XXXXXXXX"
Spark_url = "wss://spark-api.xf-yun.com/v1.1/chat"
host = urltools::domain(Spark_url)
path = paste0("/", urltools::path(Spark_url))
# 生成符合条件的英文日期
now = Sys.time()
date = format(now, format = "%a, %d %b %Y %H:%M:%S GMT", tz = "GMT")
month_abbr = month.abb[as.integer(format(now, format = "%m"))]
date = gsub("\\d+月", month_abbr, date)
date = gsub("一", "Mon", date)
date = gsub("二", "Tue", date)
date = gsub("三", "Wed", date)
date = gsub("四", "Thu", date)
date = gsub("五", "Fri", date)
date = gsub("六", "Sat", date)
date = gsub("日", "Sun", date)
date = gsub("周", "", date)
# 生成签名
signature_origin = paste(
paste0("host: ", host),
paste0("date: ", date),
paste0("GET ", path, " HTTP/1.1"),
sep = "\n"
)
signature_origin = enc2utf8(signature_origin)
signature_sha = digest::hmac(
APISecret,
signature_origin,
algo = "sha256",
raw = TRUE
)
signature = jsonlite::base64_enc(signature_sha)
# 生成授权头部
authorization_origin = paste0(
glue::glue("api_key=", '"{APIKey}"'), ", ",
'algorithm="hmac-sha256"', ", ",
'headers="host date request-line"', ", ",
glue::glue("signature=", '"{signature}"')
)
authorization = jsonlite::base64_enc(authorization_origin)
# 生成鉴权后的url
date_chr = gsub("\\,", "%2C", date)
date_chr = gsub("\\ ", "+", date_chr)
date_chr = gsub("\\:", "%3A", date_chr)
url = paste0(
Spark_url,
"?authorization=", authorization,
"&date=", date_chr,
"&host=", host
)
url = gsub("\n", "", url)
# 构建API请求消息体
request = list(
header = list(app_id = "XXXXXXXX"),
parameter = list(
chat = list(domain = "general")
),
payload = list(
message = list(
text = data.frame(
role = "user",
content = prompt
)
)
)
)
request_json = jsonlite::toJSON(
x = request,
auto_unbox = TRUE
)
df = data.frame(
url = url,
request_json = request_json
)
return(df)
}
把上面构建的 xinghuo()
函数放在 API.R 文件里。然后在 shinyApp 里调用这个函数即可,代码如下:
#==========================================================
library(shiny)
library(websocket)
#==========================================================
ui = fluidPage(
fluidRow(
column(
6, offset = 3,
h1("讯飞星火", style = "text-align: center;"),
wellPanel(
textAreaInput(
inputId = "response",
label = "星火响应",
width = "100%",
height = "450px",
# rows = 26,
resize = "vertical",
value = ""
),
textAreaInput(
inputId = "prompt",
label = "用户输入:",
width = "100%",
rows = 4,
resize = "vertical",
value = ""
),
div(
style = "display: inline-block;
width: 100%;
text-align: center;",
actionButton(
"send", "提交问题",
class = "btn-success",
position = "center"
),
actionButton(
"close", "退出会话",
class = "btn-success",
position = "center"
)
),
)
)
)
)
#==========================================================
server = function(input, output, session) {
source("./assets/API.R")
history = reactiveVal(c())
prompt = reactive(input$prompt)
ws <- NULL
observeEvent(input$prompt, {
connect = function() {
info = xinghuo(prompt())
url = info$url
ws = WebSocket$new(url)
return(ws)
}
ws <<- connect()
history(NULL)
})
observeEvent(input$send, {
info = xinghuo(prompt())
request_json = info$request_json
ws$send(request_json)
ws$onMessage(function(event) {
old = isolate(history())
msg = jsonlite::fromJSON(event$data)
conn = msg$header$message
status = msg$payload$choices$status
text = msg$payload$choices$text
if (conn == "Success") {
if (status != 2) {
new = text[, "content"]
} else {
new = ""
}
} else {
ws$close()
}
history(paste0(old, new))
})
ws$onError(function(event) {
ws$close()
})
updateTextInput(session, "prompt", value = "")
})
observeEvent(history(), {
updateTextAreaInput(
session, "response",
value = history())
})
observeEvent(input$close, {
ws$close()
})
}
#==========================================================
shinyApp(ui, server)
#==========================================================
注意:
以上代码在本地运行没有问题,但是运行过程中,控制台时不时会反馈一些错误警告信息,不明所以,忽略即可,不影响程序正常运行。
[2023-09-29 18:28:28] [error] handle_read_frame error: websocketpp.transport:7 (End of File)
这个 shinyApp 部署到 shinyapps.io 上没有任何问题,但是用 Shiny Server 部署到阿里云服务器上始终显示“Disconnected from the server.” 也看不到关于这个 app 的日志,不知道问题出在哪里。