157 lines
6.7 KiB
Go
157 lines
6.7 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package smart
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
|
|
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/shortcut/minutesdata"
|
|
)
|
|
|
|
// Transcript: fetch the verbatim transcript (逐字稿 / 语音转写原文) of MY most
|
|
// recent minutes (听记) in one step.
|
|
//
|
|
// Steps:
|
|
// 1. list my minutes via list_by_keyword_and_time_range (belongingConditionId
|
|
// = "created"), optionally filtered by --keyword;
|
|
// 2. pick the newest entry (largest create time, falling back to the first
|
|
// item) and read its taskUuid — reusing latestMinutesTaskUUID;
|
|
// 3. print that minute's verbatim transcript via get_minutes_transcription
|
|
// (taskUuid + direction, mirroring helpers.minutesGetTranscriptionCmd).
|
|
//
|
|
// If the list is empty it reports "暂无妙记" instead of failing obscurely.
|
|
//
|
|
// dws minutes +transcript
|
|
// dws minutes +transcript --keyword 周会
|
|
// dws minutes +transcript --direction 1
|
|
var Transcript = shortcut.Shortcut{
|
|
Service: "minutes",
|
|
Command: "+transcript",
|
|
Product: "minutes",
|
|
Description: "读取指定或我最新一条听记的完整逐字稿,并交付分页完整性证据",
|
|
Intent: "当你要读取已知 taskUuid(--id)的完整逐字稿,或不传 --id 自动选择自己最新听记时使用;" +
|
|
"默认追完 nextToken、跨页去重并输出 complete/pages/nextToken,只有显式 --single-page 才停在一页。" +
|
|
"可用 --direction 控制排序,--keyword 仅在自动选择最新听记时缩小候选;任何分页漂移或中途失败都返回非零而不是把部分原文当成全集。",
|
|
Risk: shortcut.RiskRead,
|
|
Safety: contract.SafetySpec{
|
|
Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent",
|
|
},
|
|
Contract: minutesSmartContract(
|
|
"+transcript",
|
|
"读取指定或最新听记的完整逐字稿",
|
|
"需要读取逐字稿并自动追完 nextToken、去重段落,同时看到 complete/pages/nextToken 完整性证据时使用;不传 --id 时严格选择最新听记。",
|
|
[]string{"只需单个原始分页响应时使用底层转写命令;需要汇总多个制品时使用 +detail"},
|
|
[]string{"dws minutes +transcript --id <taskUuid>", "dws minutes +transcript --keyword 周会"},
|
|
nil,
|
|
),
|
|
Flags: []shortcut.Flag{
|
|
{Name: "id", Type: shortcut.FlagString, Desc: "听记 taskUuid;不传时选择我最新的一条"},
|
|
{Name: "keyword", Type: shortcut.FlagString, Desc: "按关键字过滤听记(可选)", Required: false},
|
|
{Name: "direction", Type: shortcut.FlagString, Desc: "排序方向: 0=正序(默认), 1=倒序(可选)", Required: false, Enum: []string{"0", "1"}},
|
|
{Name: "cursor", Type: shortcut.FlagString, Desc: "单页/续拉的起始 nextToken"},
|
|
{Name: "single-page", Type: shortcut.FlagBool, Desc: "只读取一页;输出 complete/nextToken"},
|
|
{Name: "page-limit", Type: shortcut.FlagInt, Default: "100", Desc: "自动翻页安全上限"},
|
|
},
|
|
Constraints: []shortcut.Constraint{{Kind: shortcut.ConstraintCustom, Flags: []string{"page-limit"}, Description: "--page-limit 必须大于 0"}},
|
|
Tips: []string{
|
|
`dws minutes +transcript --id <taskUuid>`,
|
|
`dws minutes +transcript --keyword 周会`,
|
|
},
|
|
Validate: func(rt *shortcut.RuntimeContext) error {
|
|
if rt.Int("page-limit") <= 0 {
|
|
return apperrors.NewValidation("--page-limit 必须大于 0")
|
|
}
|
|
return nil
|
|
},
|
|
Execute: func(rt *shortcut.RuntimeContext) error {
|
|
taskUUID := rt.Str("id")
|
|
if taskUUID == "" {
|
|
// Step 1 — list my minutes (newest first). belongingConditionId /
|
|
// maxResults / keyword mirror helpers.callListByKeywordRange.
|
|
listArgs := map[string]any{
|
|
"belongingConditionId": "created",
|
|
"maxResults": float64(20),
|
|
}
|
|
if kw := rt.Str("keyword"); kw != "" {
|
|
listArgs["keyword"] = kw
|
|
}
|
|
data, err := rt.CallMCPData("minutes", "list_by_keyword_and_time_range", listArgs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Step 2 — locate the newest minute's taskUuid.
|
|
taskUUID, err = latestMinutesTaskUUID(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if taskUUID == "" {
|
|
return apperrors.NewValidation("暂无妙记")
|
|
}
|
|
}
|
|
|
|
// Step 3 — print its verbatim transcript. taskUuid + direction mirror
|
|
// helpers.minutesGetTranscriptionCmd (get_minutes_transcription).
|
|
// direction defaults to "0" (正序) when not provided.
|
|
direction := rt.Str("direction")
|
|
if direction == "" {
|
|
direction = "0"
|
|
}
|
|
result, readErr := collectMinutesTranscript(rt, taskUUID, direction, rt.Str("cursor"), rt.Bool("single-page"), rt.Int("page-limit"))
|
|
payload := minutesdata.TranscriptPayload(taskUUID, direction, result)
|
|
if err := rt.Output(payload); err != nil {
|
|
return err
|
|
}
|
|
if readErr != nil {
|
|
return minutesTranscriptReadError(taskUUID, result, readErr)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func collectMinutesTranscript(rt *shortcut.RuntimeContext, taskUUID, direction, cursor string, singlePage bool, pageLimit int) (minutesdata.TranscriptResult, error) {
|
|
return minutesdata.CollectTranscript(func(nextToken string) (map[string]any, error) {
|
|
params := map[string]any{"taskUuid": taskUUID, "direction": direction}
|
|
if nextToken != "" {
|
|
params["nextToken"] = nextToken
|
|
}
|
|
return rt.CallMCPData("minutes", "get_minutes_transcription", params)
|
|
}, cursor, singlePage, pageLimit)
|
|
}
|
|
|
|
func minutesTranscriptReadError(taskUUID string, result minutesdata.TranscriptResult, cause error) error {
|
|
return apperrors.NewAPI(
|
|
fmt.Sprintf("逐字稿读取不完整:已读取 %d 页、%d 个段落", result.Pages, len(result.Paragraphs)),
|
|
apperrors.WithOperation("minutes/get_minutes_transcription"),
|
|
apperrors.WithOrigin("mcp"),
|
|
apperrors.WithFailureStage("pagination"),
|
|
apperrors.WithReason("minutes_transcript_incomplete"),
|
|
apperrors.WithRetryable(false),
|
|
apperrors.WithDetails(map[string]any{
|
|
"taskUuid": taskUUID,
|
|
"pages": result.Pages,
|
|
"paragraphCount": len(result.Paragraphs),
|
|
"nextToken": result.NextToken,
|
|
"cause": cause.Error(),
|
|
}),
|
|
)
|
|
}
|
|
|
|
func init() {
|
|
shortcut.Register(finalizeMinutesSmartShortcut(Transcript))
|
|
}
|