diff --git a/DEVELOPER.md b/DEVELOPER.md index e5754210..7f62a042 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -1138,7 +1138,8 @@ The following are the update records for the SRS Stack server. * Room: AI-Talk allow disable ASR/TTS, enable text. v5.13.19 * Room: AI-Talk support dictation mode. v5.13.20 * FFmpeg: Restart if time and speed abnormal. v5.13.21 - * Transcript: Fix panic bug for sync goroutines. v5.13.21 + * Transcript: Fix panic bug for sync goroutines. [v5.13.21](https://github.com/ossrs/srs-stack/releases/tag/v5.13.21) + * Support OpenAI organization for billing. v5.13.22 * v5.12 * Refine local variable name conf to config. v5.12.1 * Add forced exit on timeout for program termination. v5.12.1 diff --git a/platform/ai-talk.go b/platform/ai-talk.go index 62c30e61..9f99b776 100644 --- a/platform/ai-talk.go +++ b/platform/ai-talk.go @@ -171,7 +171,6 @@ func (v *openaiChatService) RequestChat(ctx context.Context, sreq *StageRequest, system := stage.prompt system += fmt.Sprintf(" Keep your reply neat, limiting the reply to %v words.", stage.replyLimit) - logger.Tf(ctx, "AI system prompt: %v", system) messages := []openai.ChatCompletionMessage{ {Role: openai.ChatMessageRoleSystem, Content: system}, } @@ -189,8 +188,8 @@ func (v *openaiChatService) RequestChat(ctx context.Context, sreq *StageRequest, model := stage.chatModel maxTokens := 1024 temperature := float32(0.9) - logger.Tf(ctx, "AIChat is OPENAI_PROXY: %v, AIT_CHAT_MODEL: %v, AIT_MAX_TOKENS: %v, AIT_TEMPERATURE: %v, window=%v, histories=%v", - v.conf.BaseURL, model, maxTokens, temperature, stage.chatWindow, len(stage.histories)) + logger.Tf(ctx, "AIChat is baseURL=%v, org=%v, model=%v, maxTokens=%v, temperature=%v, window=%v, histories=%v, system is %v", + v.conf.BaseURL, v.conf.OrgID, model, maxTokens, temperature, stage.chatWindow, len(stage.histories), system) client := openai.NewClientWithConfig(v.conf) gptChatStream, err := client.CreateChatCompletionStream( @@ -973,6 +972,7 @@ func (v *Stage) UpdateFromRoom(room *SrsLiveRoom) { // Initialize the AI services. v.aiConfig = openai.DefaultConfig(room.AISecretKey) + v.aiConfig.OrgID = room.AIOrganization v.aiConfig.BaseURL = room.AIBaseURL // Bind stage to room. diff --git a/platform/live-room.go b/platform/live-room.go index 8f5df6f6..4f63dc9e 100644 --- a/platform/live-room.go +++ b/platform/live-room.go @@ -284,6 +284,8 @@ type SrsAssistant struct { AIProvider string `json:"aiProvider"` // The AI secret key. AISecretKey string `json:"aiSecretKey"` + // The AI organization. + AIOrganization string `json:"aiOrganization"` // The AI base URL. AIBaseURL string `json:"aiBaseURL"` diff --git a/platform/transcript.go b/platform/transcript.go index 8c8c8b4f..aea971b2 100644 --- a/platform/transcript.go +++ b/platform/transcript.go @@ -1014,13 +1014,15 @@ type TranscriptConfig struct { SecretKey string `json:"secretKey"` // The base URL for AI service. BaseURL string `json:"baseURL"` + // The AI organization. + Organization string `json:"organization"` // The language of the stream. Language string `json:"lang"` } func (v TranscriptConfig) String() string { - return fmt.Sprintf("all=%v, key=%vB, base=%v, lang=%v", - v.All, len(v.SecretKey), v.BaseURL, v.Language) + return fmt.Sprintf("all=%v, key=%vB, organization=%v, base=%v, lang=%v", + v.All, len(v.SecretKey), v.Organization, v.BaseURL, v.Language) } func (v *TranscriptConfig) Load(ctx context.Context) error { diff --git a/scripts/setup-aapanel/info.json b/scripts/setup-aapanel/info.json index deeea8e1..5f75ec52 100644 --- a/scripts/setup-aapanel/info.json +++ b/scripts/setup-aapanel/info.json @@ -1,7 +1,7 @@ { "title": "SRS Stack", "name": "srs_stack", - "ps": "SRS Stack is an all-in-one, out-of-the-box, and open-source video solution for creating online video services, including live streaming and WebRTC, on the cloud or through self-hosting. Built with SRS, FFmpeg, and WebRTC, it supports various protocols and offers features like authentication, multi-platform streaming, recording, transcoding, virtual live events, transcription, automatic HTTPS, and HTTP Open API.", + "ps": "SRS Stack is an all-in-one, out-of-the-box, and open-source video solution for creating online video services, including live streaming and WebRTC, on the cloud or through self-hosting. Built with SRS, FFmpeg, and WebRTC, it supports various protocols and offers features like authentication, multi-platform streaming, recording, transcoding, virtual live events, transcription, AI assistant, automatic HTTPS, and HTTP Open API.", "versions": "5.13.21", "checks": "/www/server/panel/plugin/srs_stack", "author": "Winlin", diff --git a/scripts/setup-bt/info.json b/scripts/setup-bt/info.json index 8d2b05f0..2ebcfc30 100644 --- a/scripts/setup-bt/info.json +++ b/scripts/setup-bt/info.json @@ -1,7 +1,7 @@ { "title": "SRS音视频服务器", "name": "srs_stack", - "ps": "SRS Stack让你一键拥有自己的视频云解决方案,可以在云上或私有化部署,支持丰富的音视频协议,提供鉴权、私人直播间、多平台转播、录制、转码、虚拟直播、AI字幕、自动HTTPS、开放API等丰富功能,基于SRS、FFmpeg和WebRTC构建。", + "ps": "SRS Stack让你一键拥有自己的视频云解决方案,可以在云上或私有化部署,支持丰富的音视频协议,提供鉴权、私人直播间、多平台转播、录制、转码、虚拟直播、AI字幕、直播间AI助手、自动HTTPS、开放API等丰富功能,基于SRS、FFmpeg和WebRTC构建。", "versions": "5.13.21", "checks": "/www/server/panel/plugin/srs_stack", "author": "Winlin", diff --git a/ui/src/components/OpenAISettings.js b/ui/src/components/OpenAISettings.js index 41eff27a..8f889ab0 100644 --- a/ui/src/components/OpenAISettings.js +++ b/ui/src/components/OpenAISettings.js @@ -5,7 +5,7 @@ import {Button, Form, Spinner} from "react-bootstrap"; import {useTranslation} from "react-i18next"; import {useErrorHandler} from "react-error-boundary"; -export function OpenAISecretSettings({baseURL, setBaseURL, secretKey, setSecretKey}) { +export function OpenAISecretSettings({baseURL, setBaseURL, secretKey, setSecretKey, organization, setOrganization}) { const {t} = useTranslation(); const handleError = useErrorHandler(); @@ -50,7 +50,13 @@ export function OpenAISecretSettings({baseURL, setBaseURL, secretKey, setSecretK {t('transcript.test')}   {checking && } +

+ + {t('transcript.org')} + * {t('transcript.org2')}, {t('helper.link')} + setOrganization(e.target.value)} /> + ); } diff --git a/ui/src/pages/ScenarioLiveRoom.js b/ui/src/pages/ScenarioLiveRoom.js index 847cea33..8ffe1266 100644 --- a/ui/src/pages/ScenarioLiveRoom.js +++ b/ui/src/pages/ScenarioLiveRoom.js @@ -413,6 +413,7 @@ function LiveRoomAssistant({room, requesting, updateRoom}) { const [aiName, setAiName] = React.useState(room.aiName); const [aiProvider, setAiProvider] = React.useState(room.aiProvider || 'openai'); const [aiSecretKey, setAiSecretKey] = React.useState(room.aiSecretKey); + const [aiOrganization, setAiOrganization] = React.useState(room.aiOrganization); const [aiBaseURL, setAiBaseURL] = React.useState(room.aiBaseURL || (language === 'zh' ? '' : 'https://api.openai.com/v1')); const [aiAsrEnabled, setAiAsrEnabled] = React.useState(room.aiAsrEnabled); const [aiChatEnabled, setAiChatEnabled] = React.useState(room.aiChatEnabled); @@ -439,7 +440,7 @@ function LiveRoomAssistant({room, requesting, updateRoom}) { e.preventDefault(); updateRoom({ ...room, assistant: true, - aiName, aiProvider, aiSecretKey, aiBaseURL, aiAsrLanguage, aiChatModel, + aiName, aiProvider, aiSecretKey, aiOrganization, aiBaseURL, aiAsrLanguage, aiChatModel, aiChatPrompt, aiChatMaxWindow: parseInt(aiChatMaxWindow), aiChatMaxWords: parseInt(aiChatMaxWords), aiAsrEnabled: !!aiAsrEnabled, aiChatEnabled: !!aiChatEnabled, aiTtsEnabled: !!aiTtsEnabled, @@ -447,7 +448,7 @@ function LiveRoomAssistant({room, requesting, updateRoom}) { }) }, [ updateRoom, room, aiName, aiProvider, aiSecretKey, aiBaseURL, aiAsrLanguage, aiChatModel, aiChatPrompt, - aiChatMaxWindow, aiChatMaxWords, aiAsrEnabled, aiChatEnabled, aiTtsEnabled, aiAsrPrompt + aiChatMaxWindow, aiChatMaxWords, aiAsrEnabled, aiChatEnabled, aiTtsEnabled, aiAsrPrompt, aiOrganization ]); const onDisableRoom = React.useCallback((e) => { @@ -538,7 +539,7 @@ function LiveRoomAssistant({room, requesting, updateRoom}) {

diff --git a/ui/src/pages/ScenarioTranscript.js b/ui/src/pages/ScenarioTranscript.js index 4f61d73b..f37b8338 100644 --- a/ui/src/pages/ScenarioTranscript.js +++ b/ui/src/pages/ScenarioTranscript.js @@ -49,6 +49,7 @@ function ScenarioTranscriptImpl({activeKey, defaultEnabled, defaultConf, default const [refreshNow, setRefreshNow] = React.useState(); const [transcriptEnabled, setTranscriptEnabled] = React.useState(defaultEnabled); const [secretKey, setSecretKey] = React.useState(defaultConf.secretKey); + const [organization, setOrganization] = React.useState(defaultConf.organization); const [baseURL, setBaseURL] = React.useState(defaultConf.baseURL || (language === 'zh' ? '' : 'https://api.openai.com/v1')); const [targetLanguage, setTargetLanguage] = React.useState(defaultConf.lang || language); @@ -80,7 +81,7 @@ function ScenarioTranscriptImpl({activeKey, defaultEnabled, defaultConf, default if (!baseURL) return alert(`Invalid base url ${baseURL}`); axios.post('/terraform/v1/ai/transcript/apply', { - uuid, all: !!enabled, secretKey, baseURL, lang: targetLanguage, + uuid, all: !!enabled, secretKey, organization, baseURL, lang: targetLanguage, }, { headers: Token.loadBearerHeader(), }).then(res => { @@ -88,7 +89,7 @@ function ScenarioTranscriptImpl({activeKey, defaultEnabled, defaultConf, default console.log(`Transcript: Apply config ok, uuid=${uuid}.`); success && success(); }).catch(handleError); - }, [t, handleError, secretKey, baseURL, targetLanguage, uuid]); + }, [t, handleError, secretKey, baseURL, targetLanguage, uuid, organization]); const resetTask = React.useCallback(() => { setOperating(true); @@ -280,7 +281,10 @@ function ScenarioTranscriptImpl({activeKey, defaultEnabled, defaultConf, default {t('transcript.service')}
- +

{t('transcript.lang')} diff --git a/ui/src/resources/locale.json b/ui/src/resources/locale.json index c8e6a9b5..7faf70e5 100644 --- a/ui/src/resources/locale.json +++ b/ui/src/resources/locale.json @@ -385,6 +385,8 @@ "key2": "请输入访问AI服务的密钥", "base": "OpenAI 服务接入地址", "base2": "请输入访问AI服务的地址,可以设置为OpenAI的地址或者代理地址", + "org": "组织ID", + "org2": "(可选)请输入AI的组织信息,用于区分用户和账单", "start": "开启AI字幕", "stop": "关闭AI字幕", "live": "直播切片队列", @@ -479,7 +481,7 @@ "provider": "AI服务商", "provider2": "请选择AI服务商", "model": "AI模型", - "model2": "请输入AI模型名称,比如: gpt-3.5-turbo, gpt-3.5-turbo-1106, gpt-4-turbo-preview", + "model2": "请输入AI模型名称,比如: gpt-3.5-turbo, gpt-3.5-turbo-0125, gpt-4-turbo-preview", "prompt": "提示词", "prompt2": "这个助手做什么用的?应该具备什么行为?应该避免什么行为?", "window": "AI上下文消息", @@ -726,7 +728,7 @@ "window": "AI Context Window", "prompt": "Instructions", "prompt2": "What does this assistant do? How does it behave? What should it avoid being?", - "model2": "Please input AI model name, e.g. gpt-3.5-turbo, gpt-3.5-turbo-1106, gpt-4-turbo-preview", + "model2": "Please input AI model name, e.g. gpt-3.5-turbo, gpt-3.5-turbo-0125, gpt-4-turbo-preview", "model": "AI Model Name", "provider2": "Please select AI service provider", "provider": "AI Provider", @@ -821,6 +823,8 @@ "live": "Live Segments Queue", "stop": "Stop Transcription", "start": "Start Transcription", + "org": "Organization ID", + "org2": "(Optional) Please input the organization information of AI, used to distinguish users and bills", "base2": "Please input the service base URL or proxy URL for AI service provider", "base": "OpenAI Service Base URL", "key2": "Please input the secret key for AI service provider",