agent-starter-pack 0.15.7__py3-none-any.whl → 0.16.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of agent-starter-pack might be problematic. Click here for more details.

Files changed (101) hide show
  1. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.16.0.dist-info}/METADATA +2 -2
  2. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.16.0.dist-info}/RECORD +96 -94
  3. agents/adk_base/.template/templateconfig.yaml +1 -1
  4. agents/{live_api → adk_live}/.template/templateconfig.yaml +5 -7
  5. agents/adk_live/README.md +31 -0
  6. agents/adk_live/app/agent.py +48 -0
  7. agents/adk_live/tests/unit/test_dummy.py +38 -0
  8. agents/agentic_rag/.template/templateconfig.yaml +1 -1
  9. agents/crewai_coding_crew/app/agent.py +18 -57
  10. agents/langgraph_base_react/app/agent.py +7 -46
  11. llm.txt +1 -1
  12. src/base_template/GEMINI.md +1 -1
  13. src/base_template/Makefile +130 -61
  14. src/base_template/README.md +6 -6
  15. src/base_template/deployment/terraform/dev/apis.tf +1 -1
  16. src/base_template/deployment/terraform/dev/variables.tf +1 -1
  17. src/base_template/deployment/terraform/locals.tf +1 -1
  18. src/base_template/deployment/terraform/variables.tf +1 -1
  19. src/base_template/pyproject.toml +22 -21
  20. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +2 -2
  21. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +1 -1
  22. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +71 -8
  23. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +2 -2
  24. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/pr_checks.yaml +1 -1
  25. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +90 -8
  26. src/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +1 -1
  27. src/base_template/{{cookiecutter.agent_directory}}/utils/typing.py +4 -4
  28. src/cli/utils/template.py +12 -5
  29. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +205 -4
  30. src/deployment_targets/agent_engine/tests/load_test/README.md +47 -0
  31. src/deployment_targets/agent_engine/tests/load_test/load_test.py +132 -3
  32. src/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +11 -3
  33. src/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +5 -1
  34. src/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +461 -0
  35. src/deployment_targets/cloud_run/Dockerfile +3 -3
  36. src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +4 -4
  37. src/deployment_targets/cloud_run/deployment/terraform/service.tf +7 -7
  38. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +207 -5
  39. src/deployment_targets/cloud_run/tests/load_test/README.md +82 -0
  40. src/deployment_targets/cloud_run/tests/load_test/load_test.py +130 -3
  41. src/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/server.py +178 -146
  42. src/frontends/{live_api_react → adk_live_react}/frontend/package-lock.json +39 -1007
  43. src/frontends/{live_api_react → adk_live_react}/frontend/package.json +1 -9
  44. src/frontends/{live_api_react → adk_live_react}/frontend/src/App.tsx +1 -1
  45. src/frontends/{live_api_react → adk_live_react}/frontend/src/components/logger/Logger.tsx +8 -3
  46. src/frontends/{live_api_react → adk_live_react}/frontend/src/components/logger/logger.scss +26 -0
  47. src/frontends/{live_api_react → adk_live_react}/frontend/src/components/side-panel/SidePanel.tsx +11 -5
  48. src/frontends/{live_api_react → adk_live_react}/frontend/src/components/side-panel/side-panel.scss +146 -115
  49. src/frontends/adk_live_react/frontend/src/components/transcription-preview/TranscriptionPreview.tsx +106 -0
  50. src/frontends/adk_live_react/frontend/src/components/transcription-preview/transcription-preview.scss +150 -0
  51. src/frontends/{live_api_react → adk_live_react}/frontend/src/hooks/use-live-api.ts +8 -2
  52. src/frontends/{live_api_react → adk_live_react}/frontend/src/multimodal-live-types.ts +38 -2
  53. src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/audio-recorder.ts +1 -1
  54. src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/audio-streamer.ts +1 -1
  55. src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/multimodal-live-client.ts +204 -23
  56. src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/utils.ts +27 -5
  57. src/frontends/streamlit/frontend/utils/local_chat_history.py +2 -0
  58. src/resources/idx/.idx/dev.nix +25 -11
  59. src/resources/idx/idx-template.json +1 -16
  60. src/resources/idx/idx-template.nix +2 -3
  61. src/resources/locks/uv-adk_base-agent_engine.lock +434 -349
  62. src/resources/locks/uv-adk_base-cloud_run.lock +502 -409
  63. src/resources/locks/uv-adk_live-agent_engine.lock +4189 -0
  64. src/resources/locks/{uv-live_api-cloud_run.lock → uv-adk_live-cloud_run.lock} +884 -2219
  65. src/resources/locks/uv-agentic_rag-agent_engine.lock +473 -388
  66. src/resources/locks/uv-agentic_rag-cloud_run.lock +557 -464
  67. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +498 -515
  68. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +898 -687
  69. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +455 -483
  70. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +910 -645
  71. src/utils/generate_locks.py +8 -4
  72. agents/live_api/README.md +0 -37
  73. agents/live_api/app/agent.py +0 -72
  74. agents/live_api/tests/integration/test_server_e2e.py +0 -260
  75. agents/live_api/tests/load_test/load_test.py +0 -40
  76. agents/live_api/tests/unit/test_server.py +0 -144
  77. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.16.0.dist-info}/WHEEL +0 -0
  78. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.16.0.dist-info}/entry_points.txt +0 -0
  79. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.16.0.dist-info}/licenses/LICENSE +0 -0
  80. /src/frontends/{live_api_react → adk_live_react}/frontend/public/favicon.ico +0 -0
  81. /src/frontends/{live_api_react → adk_live_react}/frontend/public/index.html +0 -0
  82. /src/frontends/{live_api_react → adk_live_react}/frontend/public/robots.txt +0 -0
  83. /src/frontends/{live_api_react → adk_live_react}/frontend/src/App.scss +0 -0
  84. /src/frontends/{live_api_react → adk_live_react}/frontend/src/App.test.tsx +0 -0
  85. /src/frontends/{live_api_react → adk_live_react}/frontend/src/components/audio-pulse/AudioPulse.tsx +0 -0
  86. /src/frontends/{live_api_react → adk_live_react}/frontend/src/components/audio-pulse/audio-pulse.scss +0 -0
  87. /src/frontends/{live_api_react → adk_live_react}/frontend/src/components/logger/mock-logs.ts +0 -0
  88. /src/frontends/{live_api_react → adk_live_react}/frontend/src/contexts/LiveAPIContext.tsx +0 -0
  89. /src/frontends/{live_api_react → adk_live_react}/frontend/src/hooks/use-media-stream-mux.ts +0 -0
  90. /src/frontends/{live_api_react → adk_live_react}/frontend/src/hooks/use-screen-capture.ts +0 -0
  91. /src/frontends/{live_api_react → adk_live_react}/frontend/src/hooks/use-webcam.ts +0 -0
  92. /src/frontends/{live_api_react → adk_live_react}/frontend/src/index.css +0 -0
  93. /src/frontends/{live_api_react → adk_live_react}/frontend/src/index.tsx +0 -0
  94. /src/frontends/{live_api_react → adk_live_react}/frontend/src/react-app-env.d.ts +0 -0
  95. /src/frontends/{live_api_react → adk_live_react}/frontend/src/reportWebVitals.ts +0 -0
  96. /src/frontends/{live_api_react → adk_live_react}/frontend/src/setupTests.ts +0 -0
  97. /src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/audioworklet-registry.ts +0 -0
  98. /src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/store-logger.ts +0 -0
  99. /src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/worklets/audio-processing.ts +0 -0
  100. /src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/worklets/vol-meter.ts +0 -0
  101. /src/frontends/{live_api_react → adk_live_react}/frontend/tsconfig.json +0 -0
@@ -3,7 +3,6 @@
3
3
  "version": "0.1.0",
4
4
  "dependencies": {
5
5
  "classnames": "^2.5.1",
6
- "dotenv-flow": "^4.1.0",
7
6
  "eventemitter3": "^5.0.1",
8
7
  "lodash": "^4.17.21",
9
8
  "react": "^18.3.1",
@@ -12,9 +11,6 @@
12
11
  "react-scripts": "5.0.1",
13
12
  "react-select": "^5.8.3",
14
13
  "sass": "^1.80.6",
15
- "vega": "^5.33.0",
16
- "vega-embed": "^6.29.0",
17
- "vega-lite": "^5.22.0",
18
14
  "web-vitals": "^2.1.4",
19
15
  "zustand": "^5.0.1"
20
16
  },
@@ -41,13 +37,9 @@
41
37
  "@google/generative-ai": "^0.21.0",
42
38
  "@testing-library/jest-dom": "^5.17.0",
43
39
  "@testing-library/react": "^13.4.0",
44
- "@testing-library/user-event": "^13.5.0",
45
- "@types/jest": "^27.5.2",
46
- "@types/node": "^16.18.119",
40
+ "@types/lodash": "^4.17.13",
47
41
  "@types/react": "^18.3.12",
48
42
  "@types/react-dom": "^18.3.1",
49
- "@types/lodash": "^4.17.13",
50
- "ts-node": "^10.9.2",
51
43
  "typescript": "^5.6.3"
52
44
  },
53
45
  "overrides": {
@@ -35,7 +35,7 @@ function App() {
35
35
  <div className="App">
36
36
  <LiveAPIProvider url={serverUrl} userId={userId}>
37
37
  <div className="streaming-console">
38
- <SidePanel
38
+ <SidePanel
39
39
  videoRef={videoRef}
40
40
  supportsVideo={true}
41
41
  onVideoStreamChange={setVideoStream}
@@ -115,10 +115,15 @@ const ToolCallLog = ({ message }: Message) => {
115
115
  const { toolCall } = message as ToolCallMessage;
116
116
  return (
117
117
  <div className={cn("rich-log tool-call")}>
118
- {toolCall.functionCalls.map((fc, i) => (
118
+ <h4 className="role-tool">Tool Call</h4>
119
+ {toolCall.functionCalls.map((fc) => (
119
120
  <div key={fc.id} className="part part-functioncall">
120
- <h5>Function call: {fc.name}</h5>
121
- <pre>{JSON.stringify(fc, null, " ")}</pre>
121
+ <h5>Function: {fc.name}</h5>
122
+ <div className="function-details">
123
+ <div><strong>ID:</strong> {fc.id}</div>
124
+ <div><strong>Args:</strong></div>
125
+ <pre>{JSON.stringify(fc.args, null, 2)}</pre>
126
+ </div>
122
127
  </div>
123
128
  ))}
124
129
  </div>
@@ -35,6 +35,10 @@
35
35
  color: var(--Blue-500);
36
36
  }
37
37
 
38
+ .tool-call h4 {
39
+ color: var(--Orange-500, #ff9800);
40
+ }
41
+
38
42
  .rich-log {
39
43
  display: flex;
40
44
  justify-content: center;
@@ -66,6 +70,28 @@
66
70
  color: var(--Neutral-90);
67
71
  border-radius: 8px;
68
72
  }
73
+
74
+ .part-functioncall {
75
+ background: rgba(255, 152, 0, 0.1);
76
+ border-left: 3px solid var(--Orange-500, #ff9800);
77
+
78
+ .function-details {
79
+ margin-top: 8px;
80
+ font-size: 13px;
81
+
82
+ strong {
83
+ color: var(--Orange-500, #ff9800);
84
+ }
85
+
86
+ pre {
87
+ background: var(--Neutral-10);
88
+ padding: 8px;
89
+ border-radius: 4px;
90
+ margin: 4px 0;
91
+ font-size: 12px;
92
+ }
93
+ }
94
+ }
69
95
  }
70
96
 
71
97
  .plain-log {
@@ -26,6 +26,7 @@ import { useWebcam } from "../../hooks/use-webcam";
26
26
  import { AudioRecorder } from "../../utils/audio-recorder";
27
27
  import AudioPulse from "../audio-pulse/AudioPulse";
28
28
  import Logger, { LoggerFilterType } from "../logger/Logger";
29
+ import TranscriptionPreview from "../transcription-preview/TranscriptionPreview";
29
30
  import "./side-panel.scss";
30
31
 
31
32
  const filterOptions = [
@@ -256,20 +257,22 @@ function SidePanel({
256
257
  };
257
258
 
258
259
  return (
259
- <div className={`side-panel ${open ? "open" : ""}`}>
260
- <canvas style={{ display: "none" }} ref={renderCanvasRef} />
261
- <header className="top">
260
+ <div className={`console-container ${open ? "open" : ""}`}>
261
+ <header className="console-header">
262
262
  <h2>Console</h2>
263
263
  {open ? (
264
- <button className="opener" onClick={() => setOpen(false)}>
264
+ <button className="toggle-button" onClick={() => setOpen(!open)}>
265
265
  <RiSidebarFoldLine color="#b4b8bb" />
266
266
  </button>
267
267
  ) : (
268
- <button className="opener" onClick={() => setOpen(true)}>
268
+ <button className="toggle-button" onClick={() => setOpen(!open)}>
269
269
  <RiSidebarUnfoldLine color="#b4b8bb" />
270
270
  </button>
271
271
  )}
272
272
  </header>
273
+ <div className="console-content">
274
+ <div className="side-panel">
275
+ <canvas style={{ display: "none" }} ref={renderCanvasRef} />
273
276
 
274
277
  {/* Connection Settings Section */}
275
278
  <section className="connection-settings">
@@ -503,6 +506,9 @@ function SidePanel({
503
506
  </button>
504
507
  </div>
505
508
  )}
509
+ </div>
510
+ <TranscriptionPreview open={open} />
511
+ </div>
506
512
  </div>
507
513
  );
508
514
  }
@@ -15,7 +15,153 @@
15
15
  */
16
16
 
17
17
  /* stylelint-disable */
18
+ .console-container {
19
+ display: flex;
20
+ flex-direction: column;
21
+ height: 100vh;
22
+ transition: all 0.2s ease-in;
23
+
24
+ .console-header {
25
+ display: flex;
26
+ justify-content: space-between;
27
+ align-items: center;
28
+ padding: 6px 20px;
29
+ border-bottom: 1px solid var(--Neutral-20);
30
+ background: var(--Neutral-00);
31
+ flex-shrink: 0;
32
+ min-height: 38px;
33
+
34
+ h2 {
35
+ color: var(--Neutral-90, #e1e2e3);
36
+ font-family: "Google Sans";
37
+ font-size: 16px;
38
+ font-style: normal;
39
+ font-weight: 500;
40
+ line-height: 16px;
41
+ margin: 0;
42
+ opacity: 0;
43
+ display: none;
44
+ transition: opacity 0.2s ease-in;
45
+ }
46
+
47
+ .toggle-button {
48
+ background: transparent;
49
+ border: 0;
50
+ cursor: pointer;
51
+ padding: 4px;
52
+ height: 30px;
53
+ width: 30px;
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ color: #b4b8bb;
58
+ flex-shrink: 0;
59
+
60
+ svg {
61
+ width: 20px;
62
+ height: 20px;
63
+ }
64
+
65
+ &:hover {
66
+ opacity: 0.7;
67
+ }
68
+ }
69
+ }
70
+
71
+ .console-content {
72
+ display: flex;
73
+ flex-direction: row;
74
+ flex: 1;
75
+ overflow: hidden;
76
+ }
77
+
78
+ &.open {
79
+ .console-header h2 {
80
+ display: block;
81
+ opacity: 1;
82
+ }
83
+
84
+ .side-panel {
85
+ width: 400px;
86
+ }
87
+ }
88
+
89
+ &:not(.open) {
90
+ .console-header {
91
+ padding: 6px 5px;
92
+
93
+ .toggle-button {
94
+ margin: 0 auto;
95
+ }
96
+ }
97
+
98
+ .side-panel {
99
+ width: 40px;
100
+
101
+ .side-panel-container {
102
+ opacity: 0;
103
+ display: none;
104
+ transition: all 0.2s ease-in allow-discrete;
105
+ transition-delay: 0.1s;
106
+ }
107
+
108
+ .connection-settings {
109
+ opacity: 0;
110
+ display: none;
111
+ transition: all 0.2s ease-in allow-discrete;
112
+ }
113
+
114
+ .control-tray {
115
+ opacity: 0;
116
+ display: none;
117
+ transition: all 0.2s ease-in allow-discrete;
118
+
119
+ .actions-nav {
120
+ opacity: 0;
121
+ display: none;
122
+ }
123
+
124
+ .connection-status {
125
+ opacity: 0;
126
+ display: none;
127
+ }
128
+ }
129
+
130
+ .indicators {
131
+ .react-select {
132
+ opacity: 0;
133
+ display: none;
134
+ transition: all 0.2s ease-in allow-discrete;
135
+ }
136
+
137
+ .streaming-indicator {
138
+ width: 30px;
139
+ opacity: 0;
140
+ }
141
+ }
142
+
143
+ .input-container {
144
+ opacity: 0;
145
+ display: none;
146
+ transition: all 0.2s ease-in allow-discrete;
147
+ }
148
+
149
+ .audio-pulse-bottom {
150
+ .pulse-container {
151
+ .pulse-label {
152
+ opacity: 0;
153
+ display: none;
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+
18
161
  .side-panel {
162
+ width: 400px;
163
+ transition: width 0.2s ease-in;
164
+
19
165
  .react-select {
20
166
  background: var(--Neutral-20);
21
167
  color: var(--Neutral-90);
@@ -47,7 +193,6 @@
47
193
  }
48
194
  }
49
195
  background: var(--Neutral-00);
50
- width: 40px; /* when closed */
51
196
  display: flex;
52
197
  flex-direction: column;
53
198
  height: 100vh;
@@ -65,116 +210,6 @@
65
210
  display: none !important;
66
211
  }
67
212
 
68
- &.open {
69
- .top {
70
- h2 {
71
- left: 0%;
72
- display: block;
73
- opacity: 1;
74
- }
75
- }
76
- }
77
-
78
- .top {
79
- display: flex;
80
- width: calc(100% - 45px);
81
- justify-content: space-between;
82
- align-items: center;
83
- padding: 12px 20px 12px 25px;
84
- border-bottom: 1px solid var(--Neutral-20);
85
-
86
- h2 {
87
- position: relative;
88
- color: var(--Neutral-90, #e1e2e3);
89
- font-family: "Google Sans";
90
- font-size: 21px;
91
- font-style: normal;
92
- font-weight: 500;
93
- line-height: 16px; /* 100% */
94
-
95
- opacity: 0;
96
- display: none;
97
- left: -100%;
98
- transition:
99
- opacity 0.2s ease-in,
100
- left 0.2s ease-in,
101
- display 0.2s ease-in;
102
- transition-behavior: allow-discrete;
103
-
104
- @starting-style {
105
- left: 0%;
106
- opacity: 1;
107
- }
108
- }
109
- }
110
-
111
- .opener {
112
- height: 30px;
113
- transition: transform 0.2s ease-in;
114
- }
115
-
116
- &:not(.open) {
117
- .side-panel-container {
118
- opacity: 0;
119
- display: none;
120
- transition: all 0.2s ease-in allow-discrete;
121
- transition-delay: 0.1s;
122
- }
123
-
124
- .connection-settings {
125
- opacity: 0;
126
- display: none;
127
- transition: all 0.2s ease-in allow-discrete;
128
- }
129
-
130
- .control-tray {
131
- opacity: 0;
132
- display: none;
133
- transition: all 0.2s ease-in allow-discrete;
134
-
135
- .actions-nav {
136
- opacity: 0;
137
- display: none;
138
- }
139
-
140
- .connection-status {
141
- opacity: 0;
142
- display: none;
143
- }
144
- }
145
-
146
- .indicators {
147
- .react-select {
148
- opacity: 0;
149
- display: none;
150
- transition: all 0.2s ease-in allow-discrete;
151
- }
152
-
153
- .streaming-indicator {
154
- width: 30px;
155
- opacity: 0;
156
- }
157
- }
158
-
159
- .opener {
160
- transform: translate(-50%, 0);
161
- }
162
-
163
- .input-container {
164
- opacity: 0;
165
- display: none;
166
- transition: all 0.2s ease-in allow-discrete;
167
- }
168
-
169
- .audio-pulse-bottom {
170
- .pulse-container {
171
- .pulse-label {
172
- opacity: 0;
173
- display: none;
174
- }
175
- }
176
- }
177
- }
178
213
 
179
214
  .indicators {
180
215
  display: flex;
@@ -496,10 +531,6 @@
496
531
  }
497
532
  }
498
533
  }
499
- .side-panel.open {
500
- width: 400px;
501
- height: 100vh;
502
- }
503
534
 
504
535
  .side-panel-responses,
505
536
  .side-panel-requests {
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Copyright 2024 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { useEffect, useRef, useState } from "react";
18
+ import { useLiveAPIContext } from "../../contexts/LiveAPIContext";
19
+ import cn from "classnames";
20
+ import "./transcription-preview.scss";
21
+
22
+ export type TranscriptionPreviewProps = {
23
+ open: boolean;
24
+ };
25
+
26
+ export default function TranscriptionPreview({ open }: TranscriptionPreviewProps) {
27
+ const { client } = useLiveAPIContext();
28
+ const [inputTexts, setInputTexts] = useState<string[]>([]);
29
+ const [outputTexts, setOutputTexts] = useState<string[]>([]);
30
+ const inputRef = useRef<HTMLDivElement>(null);
31
+ const outputRef = useRef<HTMLDivElement>(null);
32
+
33
+ useEffect(() => {
34
+ const handleInputTranscription = (text: string) => {
35
+ setInputTexts((prev) => [...prev, text]);
36
+ // Auto-scroll to bottom
37
+ setTimeout(() => {
38
+ if (inputRef.current) {
39
+ inputRef.current.scrollTop = inputRef.current.scrollHeight;
40
+ }
41
+ }, 0);
42
+ };
43
+
44
+ const handleOutputTranscription = (text: string) => {
45
+ setOutputTexts((prev) => [...prev, text]);
46
+ // Auto-scroll to bottom
47
+ setTimeout(() => {
48
+ if (outputRef.current) {
49
+ outputRef.current.scrollTop = outputRef.current.scrollHeight;
50
+ }
51
+ }, 0);
52
+ };
53
+
54
+ client.on("inputtranscription", handleInputTranscription);
55
+ client.on("outputtranscription", handleOutputTranscription);
56
+
57
+ return () => {
58
+ client.off("inputtranscription", handleInputTranscription);
59
+ client.off("outputtranscription", handleOutputTranscription);
60
+ };
61
+ }, [client]);
62
+
63
+ return (
64
+ <div className={cn("transcription-preview", { open })}>
65
+ <div className="transcription-section input-section">
66
+ <div className="transcription-header">
67
+ <span className="material-symbols-outlined">mic</span>
68
+ <h3>Input</h3>
69
+ </div>
70
+ <div className="transcription-content" ref={inputRef}>
71
+ {inputTexts.length > 0 ? (
72
+ <>
73
+ {inputTexts.map((text, index) => (
74
+ <p key={index} className={index === inputTexts.length - 1 ? "current" : "previous"}>
75
+ {text}
76
+ </p>
77
+ ))}
78
+ </>
79
+ ) : (
80
+ <p className="placeholder">Listening...</p>
81
+ )}
82
+ </div>
83
+ </div>
84
+
85
+ <div className="transcription-section output-section">
86
+ <div className="transcription-header">
87
+ <span className="material-symbols-outlined">volume_up</span>
88
+ <h3>Output</h3>
89
+ </div>
90
+ <div className="transcription-content" ref={outputRef}>
91
+ {outputTexts.length > 0 ? (
92
+ <>
93
+ {outputTexts.map((text, index) => (
94
+ <p key={index} className={index === outputTexts.length - 1 ? "current" : "previous"}>
95
+ {text}
96
+ </p>
97
+ ))}
98
+ </>
99
+ ) : (
100
+ <p className="placeholder">Waiting for response...</p>
101
+ )}
102
+ </div>
103
+ </div>
104
+ </div>
105
+ );
106
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Copyright 2025 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ .transcription-preview {
18
+ width: 0;
19
+ height: 100%;
20
+ background: var(--Neutral-00);
21
+ border-left: 1px solid var(--Neutral-20);
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: 0;
25
+ overflow: hidden;
26
+ transition: width 0.2s ease-in-out;
27
+ flex-shrink: 0;
28
+
29
+ &.open {
30
+ width: 300px;
31
+ }
32
+
33
+ .transcription-section {
34
+ flex: 1;
35
+ display: flex;
36
+ flex-direction: column;
37
+ overflow: hidden;
38
+ opacity: 0;
39
+ transition: opacity 0.2s ease-in-out 0.1s;
40
+
41
+ &:first-child {
42
+ border-bottom: 1px solid var(--Neutral-20);
43
+ }
44
+
45
+ .transcription-header {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 8px;
49
+ padding: 12px 20px;
50
+ background: var(--Neutral-05);
51
+ border-bottom: 1px solid var(--Neutral-20);
52
+ flex-shrink: 0;
53
+
54
+ .material-symbols-outlined {
55
+ font-size: 20px;
56
+ color: var(--Neutral-70);
57
+ }
58
+
59
+ h3 {
60
+ margin: 0;
61
+ font-size: 12px;
62
+ font-weight: 600;
63
+ color: var(--Neutral-90);
64
+ text-transform: uppercase;
65
+ letter-spacing: 0.5px;
66
+ }
67
+ }
68
+
69
+ .transcription-content {
70
+ flex: 1;
71
+ padding: 12px 16px;
72
+ overflow-y: auto;
73
+ overflow-x: hidden;
74
+ word-wrap: break-word;
75
+
76
+ p {
77
+ margin: 0 0 12px 0;
78
+ color: var(--Neutral-90);
79
+ font-size: 13px;
80
+ line-height: 1.5;
81
+ white-space: pre-wrap;
82
+ word-break: break-word;
83
+ font-family: var(--font-family);
84
+
85
+ &.placeholder {
86
+ color: var(--Neutral-50);
87
+ font-style: italic;
88
+ }
89
+
90
+ &.previous {
91
+ color: var(--Neutral-60);
92
+ opacity: 0.7;
93
+ }
94
+
95
+ &.current {
96
+ color: var(--Neutral-90);
97
+ font-weight: 500;
98
+ }
99
+
100
+ &:last-child {
101
+ margin-bottom: 0;
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ &.open .transcription-section {
108
+ opacity: 1;
109
+ }
110
+
111
+ .input-section {
112
+ .transcription-header {
113
+ .material-symbols-outlined {
114
+ color: var(--Green-500);
115
+ }
116
+
117
+ h3 {
118
+ color: var(--Green-500);
119
+ }
120
+ }
121
+
122
+ .transcription-content {
123
+ background: linear-gradient(
124
+ to bottom,
125
+ rgba(0, 200, 100, 0.02),
126
+ transparent
127
+ );
128
+ }
129
+ }
130
+
131
+ .output-section {
132
+ .transcription-header {
133
+ .material-symbols-outlined {
134
+ color: var(--Blue-500);
135
+ }
136
+
137
+ h3 {
138
+ color: var(--Blue-500);
139
+ }
140
+ }
141
+
142
+ .transcription-content {
143
+ background: linear-gradient(
144
+ to bottom,
145
+ rgba(0, 100, 255, 0.02),
146
+ transparent
147
+ );
148
+ }
149
+ }
150
+ }