Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 190 additions & 2 deletions cmd/nlm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type ChatSession struct {

// ChatMessage represents a single message in the conversation
type ChatMessage struct {
Role string `json:"role"` // "user" or "assistant"
Role string `json:"role"` // "user" or "assistant"
Content string `json:"content"`
Timestamp time.Time `json:"timestamp"`
}
Expand Down Expand Up @@ -102,6 +102,10 @@ func init() {
fmt.Fprintf(os.Stderr, " video-create <id> <instructions> Create video overview\n")
fmt.Fprintf(os.Stderr, " video-download <id> [filename] Download video file (requires --direct-rpc)\n\n")

fmt.Fprintf(os.Stderr, "PPT Commands:\n")
fmt.Fprintf(os.Stderr, " ppt-list <id> List all PPT overviews for a notebook with status\n")
fmt.Fprintf(os.Stderr, " ppt-create <id> [source-ids...] Create PPT overview (uses all sources if none specified)\n")

fmt.Fprintf(os.Stderr, "Artifact Commands:\n")
fmt.Fprintf(os.Stderr, " create-artifact <id> <type> Create artifact (note|audio|report|app)\n")
fmt.Fprintf(os.Stderr, " get-artifact <artifact-id> Get artifact details\n")
Expand Down Expand Up @@ -259,6 +263,21 @@ func validateArgs(cmd string, args []string) error {
fmt.Fprintf(os.Stderr, "usage: nlm video-download <notebook-id> [filename]\n")
return fmt.Errorf("invalid arguments")
}
case "ppt-list":
if len(args) != 1 {
fmt.Fprintf(os.Stderr, "usage: nlm ppt-list <notebook-id>\n")
return fmt.Errorf("invalid arguments")
}
case "ppt-download":
if len(args) < 1 || len(args) > 3 {
fmt.Fprintf(os.Stderr, "usage: nlm ppt-download <notebook-id> [filename]\n")
return fmt.Errorf("invalid arguments")
}
case "ppt-create":
if len(args) < 1 {
fmt.Fprintf(os.Stderr, "usage: nlm ppt-create <notebook-id> [source-id...]\n")
return fmt.Errorf("invalid arguments")
}
case "audio-create":
if len(args) != 2 {
fmt.Fprintf(os.Stderr, "usage: nlm audio-create <notebook-id> <instructions>\n")
Expand Down Expand Up @@ -417,7 +436,7 @@ func isValidCommand(cmd string) bool {
"list", "ls", "create", "rm", "analytics", "list-featured",
"sources", "add", "rm-source", "rename-source", "refresh-source", "check-source", "discover-sources",
"notes", "new-note", "update-note", "rm-note",
"audio-create", "audio-get", "audio-rm", "audio-share", "audio-list", "audio-download", "video-create", "video-list", "video-download",
"audio-create", "audio-get", "audio-rm", "audio-share", "audio-list", "audio-download", "video-create", "video-list", "video-download", "ppt-create", "ppt-list", "ppt-download",
"create-artifact", "get-artifact", "list-artifacts", "artifacts", "rename-artifact", "delete-artifact",
"generate-guide", "generate-outline", "generate-section", "generate-magic", "generate-mindmap", "generate-chat", "chat", "chat-list",
"rephrase", "expand", "summarize", "critique", "brainstorm", "verify", "explain", "outline", "study-guide", "faq", "briefing-doc", "mindmap", "timeline", "toc",
Expand Down Expand Up @@ -738,6 +757,21 @@ func runCmd(client *api.Client, cmd string, args ...string) error {
}
err = downloadVideoOverview(client, args[0], filename)

// PPT operations
case "ppt-create":
sourceIDs := []string{}
if len(args) > 1 {
sourceIDs = args[1:]
}
err = createPPTOverview(client, args[0], sourceIDs)
case "ppt-list":
err = listPPTOverviews(client, args[0])
case "ppt-download":
filename := ""
if len(args) > 2 {
filename = args[2]
}
err = downloadPPTOverview(client, args[0], args[1], filename)
// Artifact operations
case "create-artifact":
err = createArtifact(client, args[0], args[1])
Expand Down Expand Up @@ -2301,3 +2335,157 @@ func downloadVideoOverview(c *api.Client, notebookID string, filename string) er

return nil
}

// PPT operations
func createPPTOverview(c *api.Client, projectID string, sourceIDs []string) error {
fmt.Printf("Creating PPT overview for notebook %s...\n", projectID)
if len(sourceIDs) > 0 {
fmt.Printf("Using %d specified source(s)\n", len(sourceIDs))
} else {
fmt.Printf("Using all sources from notebook\n")
}

result, err := c.CreatePPTOverview(projectID, sourceIDs)
if err != nil {
return fmt.Errorf("create PPT overview: %w", err)
}

if !result.IsReady {
fmt.Println("✅ PPT overview creation started. PPT generation may take several minutes.")
fmt.Printf(" Project ID: %s\n", result.ProjectID)
if result.PPTID != "" {
fmt.Printf(" PPT ID: %s\n", result.PPTID)
}
return nil
}

// If the result is immediately ready (unlikely but possible)
fmt.Printf("✅ PPT Overview created:\n")
fmt.Printf(" Title: %s\n", result.Title)
fmt.Printf(" PPT ID: %s\n", result.PPTID)

if result.PPTData != "" {
if strings.HasPrefix(result.PPTData, "http://") || strings.HasPrefix(result.PPTData, "https://") {
fmt.Printf(" PPT URL: %s\n", result.PPTData)
} else {
fmt.Printf(" PPT data available\n")
}
}

return nil
}

func listPPTOverviews(c *api.Client, notebookID string) error {
fmt.Printf("Listing PPT overviews for notebook %s...\n", notebookID)

pptOverviews, err := c.ListPPTOverviews(notebookID)
if err != nil {
return fmt.Errorf("list PPT overviews: %w", err)
}

if len(pptOverviews) == 0 {
fmt.Println("No PPT overviews found.")
return nil
}

// 初始化 tabwriter,设置合适的最小列宽和间距
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)

// 1. 在表头增加 DOWNLOAD_URL
fmt.Fprintln(w, "PPT_ID\tTITLE\tSTATUS")
for _, ppt := range pptOverviews {
status := "pending"
if ppt.IsReady {
status = "ready"
}
title := ppt.Title
if title == "" {
title = "(untitled)"
}

// // 2. 获取下载链接,如果为空则显示横线或 N/A
// downloadURL := ppt.PPTData
// if downloadURL == "" {
// downloadURL = "-"
// }

// 3. 在 Fprintf 中增加对应的格式化占位符
fmt.Fprintf(w, "%s\t%s\t%s\t\n",
ppt.PPTID,
title,
status,
// downloadURL,
)
}
return w.Flush()
}

func downloadPPTOverview(c *api.Client, notebookID string, pptID string, filename string) error {
fmt.Printf("Fetching PPT overviews for notebook %s...\n", notebookID)

// 获取该笔记本下所有的 PPT 列表
results, err := c.ListPPTOverviews(notebookID)
if err != nil {
return fmt.Errorf("list PPT overviews: %w", err)
}

if len(results) == 0 {
return fmt.Errorf("no PPT overviews found for notebook %s", notebookID)
}

// 确定需要下载的任务列表
var tasks []*api.PPTOverviewResult
if pptID != "" {
// 如果指定了 PPTID,进行过滤
for _, r := range results {
if r.PPTID == pptID {
tasks = append(tasks, r)
break
}
}
if len(tasks) == 0 {
return fmt.Errorf("PPT with ID %s not found", pptID)
}
} else {
// 如果没有指定 PPTID,下载全部
tasks = results
fmt.Printf("No PPT ID specified, downloading all %d PPT(s)...\n", len(tasks))
}

// 执行下载逻辑
for _, ppt := range tasks {
// 处理文件名
targetFile := filename
if targetFile == "" || len(tasks) > 1 {
// 如果是下载多个,或者没指定文件名,则根据 ppt.Title 生成文件名
targetFile = fmt.Sprintf("%s.pdf", ppt.Title)
}

fmt.Printf("Downloading PPT: %s (ID: %s)...\n", ppt.Title, ppt.PPTID)

// 检查下载链接
if ppt.PPTData != "" && (strings.HasPrefix(ppt.PPTData, "http://") || strings.HasPrefix(ppt.PPTData, "https://")) {
// 使用认证下载
if err := c.DownloadPPTWithAuth(ppt.PPTData, targetFile); err != nil {
fmt.Printf("❌ Failed to download %s: %v\n", ppt.PPTID, err)
continue
}
} else {
// 尝试保存 base64 数据
if err := ppt.SavePPTToFile(targetFile); err != nil {
fmt.Printf("❌ Failed to save %s: %v\n", ppt.PPTID, err)
continue
}
}

fmt.Printf("✅ %s Saved to: %s", ppt.Title, targetFile)
// 显示文件大小
if stat, err := os.Stat(targetFile); err == nil {
fmt.Printf(" (%.2f MB)\n", float64(stat.Size())/(1024*1024))
} else {
fmt.Println()
}
}

return nil
}
Loading