Merge pull request #290 from CJackHwang/dev

修复3.6.0docker构建问题
This commit is contained in:
CJACK.
2026-04-23 20:35:18 +08:00
committed by GitHub
8 changed files with 116 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ FROM node:24 AS webui-builder
WORKDIR /app/webui
COPY webui/package.json webui/package-lock.json ./
RUN npm ci
COPY config.example.json /app/config.example.json
COPY webui ./
RUN npm run build

View File

@@ -44,6 +44,7 @@ func startChatHistory(store *chathistory.Store, r *http.Request, a *auth.Request
Stream: stdReq.Stream,
UserInput: extractSingleUserInput(stdReq.Messages),
Messages: extractAllMessages(stdReq.Messages),
HistoryText: stdReq.HistoryText,
FinalPrompt: stdReq.FinalPrompt,
})
startParams := chathistory.StartParams{
@@ -53,6 +54,7 @@ func startChatHistory(store *chathistory.Store, r *http.Request, a *auth.Request
Stream: stdReq.Stream,
UserInput: extractSingleUserInput(stdReq.Messages),
Messages: extractAllMessages(stdReq.Messages),
HistoryText: stdReq.HistoryText,
FinalPrompt: stdReq.FinalPrompt,
}
session := &chatHistorySession{

View File

@@ -271,3 +271,56 @@ func TestChatCompletionsSkipsHistoryWhenDisabled(t *testing.T) {
t.Fatalf("expected disabled history to stay empty, got %#v", snapshot.Items)
}
}
func TestChatCompletionsHistorySplitPersistsHistoryText(t *testing.T) {
historyStore := newTestChatHistoryStore(t)
ds := &inlineUploadDSStub{}
h := &Handler{
Store: mockOpenAIConfig{
wideInput: true,
historySplitEnabled: true,
historySplitTurns: 1,
},
Auth: streamStatusAuthStub{},
DS: ds,
ChatHistory: historyStore,
}
reqBody := `{"model":"deepseek-chat","messages":[{"role":"system","content":"system instructions"},{"role":"user","content":"first user turn"},{"role":"assistant","content":"","reasoning_content":"hidden reasoning","tool_calls":[{"name":"search","arguments":{"query":"docs"}}]},{"role":"tool","name":"search","tool_call_id":"call-1","content":"tool result"},{"role":"user","content":"latest user turn"}],"stream":false}`
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(reqBody))
req.Header.Set("Authorization", "Bearer direct-token")
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
h.ChatCompletions(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
}
snapshot, err := historyStore.Snapshot()
if err != nil {
t.Fatalf("snapshot failed: %v", err)
}
if len(snapshot.Items) != 1 {
t.Fatalf("expected one history item, got %d", len(snapshot.Items))
}
full, err := historyStore.Get(snapshot.Items[0].ID)
if err != nil {
t.Fatalf("expected detail item, got %v", err)
}
if full.HistoryText == "" {
t.Fatalf("expected history text to be persisted")
}
if !strings.Contains(full.HistoryText, "first user turn") || !strings.Contains(full.HistoryText, "tool result") {
t.Fatalf("expected earlier turns in history text, got %q", full.HistoryText)
}
if strings.Contains(full.HistoryText, "latest user turn") {
t.Fatalf("expected latest turn to stay out of persisted history text, got %q", full.HistoryText)
}
if len(ds.uploadCalls) != 1 {
t.Fatalf("expected history upload to happen, got %d", len(ds.uploadCalls))
}
if full.HistoryText != string(ds.uploadCalls[0].Data) {
t.Fatalf("expected persisted history text to match uploaded HISTORY.txt contents")
}
}

View File

@@ -51,6 +51,7 @@ func (h *Handler) applyHistorySplit(ctx context.Context, a *auth.RequestAuth, st
}
stdReq.Messages = promptMessages
stdReq.HistoryText = historyText
stdReq.RefFileIDs = prependUniqueRefFileID(stdReq.RefFileIDs, fileID)
stdReq.FinalPrompt, stdReq.ToolNames = buildHistorySplitPrompt(promptMessages, reasoningContent, stdReq.ToolsRaw, stdReq.ToolChoice, stdReq.Thinking)
return stdReq, nil

View File

@@ -195,6 +195,37 @@ func TestApplyHistorySplitSkipsFirstTurn(t *testing.T) {
}
}
func TestApplyHistorySplitCarriesHistoryText(t *testing.T) {
ds := &inlineUploadDSStub{}
h := &Handler{
Store: mockOpenAIConfig{
wideInput: true,
historySplitEnabled: true,
historySplitTurns: 1,
},
DS: ds,
}
req := map[string]any{
"model": "deepseek-chat",
"messages": historySplitTestMessages(),
}
stdReq, err := normalizeOpenAIChatRequest(h.Store, req, "")
if err != nil {
t.Fatalf("normalize failed: %v", err)
}
out, err := h.applyHistorySplit(context.Background(), &auth.RequestAuth{DeepSeekToken: "token"}, stdReq)
if err != nil {
t.Fatalf("apply history split failed: %v", err)
}
if len(ds.uploadCalls) != 1 {
t.Fatalf("expected 1 upload call, got %d", len(ds.uploadCalls))
}
if out.HistoryText != string(ds.uploadCalls[0].Data) {
t.Fatalf("expected history text to be preserved on normalized request")
}
}
func TestChatCompletionsHistorySplitUploadsHistoryAndKeepsLatestPrompt(t *testing.T) {
ds := &inlineUploadDSStub{}
h := &Handler{

View File

@@ -46,6 +46,7 @@ type Entry struct {
Stream bool `json:"stream"`
UserInput string `json:"user_input,omitempty"`
Messages []Message `json:"messages,omitempty"`
HistoryText string `json:"history_text,omitempty"`
FinalPrompt string `json:"final_prompt,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
Content string `json:"content,omitempty"`
@@ -94,6 +95,7 @@ type StartParams struct {
Stream bool
UserInput string
Messages []Message
HistoryText string
FinalPrompt string
}
@@ -244,6 +246,7 @@ func (s *Store) Start(params StartParams) (Entry, error) {
Stream: params.Stream,
UserInput: strings.TrimSpace(params.UserInput),
Messages: cloneMessages(params.Messages),
HistoryText: params.HistoryText,
FinalPrompt: strings.TrimSpace(params.FinalPrompt),
}
s.details[entry.ID] = entry

View File

@@ -8,6 +8,7 @@ type StandardRequest struct {
ResolvedModel string
ResponseModel string
Messages []any
HistoryText string
ToolsRaw any
FinalPrompt string
ToolNames []string

View File

@@ -181,11 +181,35 @@ function MergedPromptView({ item, t }) {
)
}
function HistoryTextView({ item, t }) {
const historyText = (item?.history_text || '').trim()
if (!historyText) return null
return (
<div className="max-w-4xl mx-auto rounded-2xl border border-border bg-background px-5 py-4">
<div className="text-[11px] uppercase tracking-[0.12em] text-muted-foreground mb-3 text-left">
HISTORY
</div>
<div className="text-sm leading-7 text-foreground whitespace-pre-wrap break-words font-mono">
<ExpandableText
text={historyText}
threshold={Math.floor(MESSAGE_COLLAPSE_AT / 4)}
expandLabel={t('chatHistory.expand')}
collapseLabel={t('chatHistory.collapse')}
buttonClassName="text-foreground hover:text-muted-foreground"
/>
</div>
</div>
)
}
function DetailConversation({ selectedItem, t, viewMode, detailScrollRef, assistantStartRef, bottomButtonClassName }) {
if (!selectedItem) return null
return (
<>
<HistoryTextView item={selectedItem} t={t} />
{viewMode === 'list'
? <RequestMessages item={selectedItem} t={t} />
: <MergedPromptView item={selectedItem} t={t} />}